Compare commits
130 Commits
v1.0.0
...
v1.7.0-pre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b8874902a | ||
|
|
127eab683b | ||
|
|
1efc47f9cd | ||
|
|
7f2e186d06 | ||
|
|
7d5419d33c | ||
|
|
ce9ab91e5b | ||
|
|
eac35ee8a3 | ||
|
|
49f36a2b99 | ||
|
|
47c2fc8069 | ||
|
|
9a215123f6 | ||
|
|
40597b4386 | ||
|
|
eb9c1ce1a4 | ||
|
|
7be1ec7080 | ||
|
|
311c7733fa | ||
|
|
ea0a5a6a71 | ||
|
|
d014a176ce | ||
|
|
b9d9b0ea16 | ||
|
|
7e3e4991dc | ||
|
|
57248cd467 | ||
|
|
722c153e40 | ||
|
|
b691eab0ee | ||
|
|
907b3e42c4 | ||
|
|
128bbcc1df | ||
|
|
789ec7fc17 | ||
|
|
32051493a8 | ||
|
|
7b241ee13e | ||
|
|
84017639eb | ||
|
|
9e346910f4 | ||
|
|
e7239c428b | ||
|
|
9288eb8f12 | ||
|
|
e32e7ad36f | ||
|
|
3de551be95 | ||
|
|
8e3fa4f8d5 | ||
|
|
b564edb158 | ||
|
|
0bf2ba5d96 | ||
|
|
688af162f5 | ||
|
|
7e4fccb0c1 | ||
|
|
fcdc5cdbde | ||
|
|
f85f246a26 | ||
|
|
006f337cb3 | ||
|
|
401a354f04 | ||
|
|
3c25adfc15 | ||
|
|
5d0222397f | ||
|
|
5840d02265 | ||
|
|
97d3a65131 | ||
|
|
3d2f94681a | ||
|
|
2ade60c551 | ||
|
|
1cc7eb22dc | ||
|
|
da654c616d | ||
|
|
b76ee71751 | ||
|
|
c2c2831cda | ||
|
|
a4e5835e34 | ||
|
|
ac97652683 | ||
|
|
7ae6eb82f0 | ||
|
|
8688e1d5c0 | ||
|
|
ffc8be3c12 | ||
|
|
7c504ba8fd | ||
|
|
a8bf5a9412 | ||
|
|
a190f31ac5 | ||
|
|
b0e74a2010 | ||
|
|
1ade1922df | ||
|
|
f9d6cc4bdc | ||
|
|
1934558595 | ||
|
|
fc09db60ab | ||
|
|
906456782a | ||
|
|
d6d6a5d808 | ||
|
|
586f2c69d8 | ||
|
|
45e4666c51 | ||
|
|
d94c921815 | ||
|
|
fff22ea8d9 | ||
|
|
9b930b2a51 | ||
|
|
a0408a1d1d | ||
|
|
f2c58bb172 | ||
|
|
7eb418d6a2 | ||
|
|
60fab488a2 | ||
|
|
5a5a2f94fb | ||
|
|
6e30660953 | ||
|
|
61ad6b9f3a | ||
|
|
b37ac0e069 | ||
|
|
183f7f6a3d | ||
|
|
ef1d531714 | ||
|
|
67447d49b5 | ||
|
|
031edd7088 | ||
|
|
09dece08f3 | ||
|
|
61d9dd16eb | ||
|
|
98547a9df6 | ||
|
|
e8016405b6 | ||
|
|
d0f46a06f2 | ||
|
|
e37b62725a | ||
|
|
f8a8268cf6 | ||
|
|
b25155ef36 | ||
|
|
e658227c04 | ||
|
|
73bcfe2458 | ||
|
|
d67c17528a | ||
|
|
9b77dee37b | ||
|
|
d2959b41ab | ||
|
|
f75672a264 | ||
|
|
7da17b91a0 | ||
|
|
bd7124a5be | ||
|
|
6b391bc357 | ||
|
|
fa8f123f2b | ||
|
|
129fd6d4e4 | ||
|
|
28823d940f | ||
|
|
4b6174e3e2 | ||
|
|
8e922182e2 | ||
|
|
25f1895fe5 | ||
|
|
07caab074a | ||
|
|
f7906f692d | ||
|
|
14f836d46a | ||
|
|
c396a81c1b | ||
|
|
59df041cfd | ||
|
|
2355c5ac97 | ||
|
|
7891acd218 | ||
|
|
4ad51e98e2 | ||
|
|
8a001f9fa4 | ||
|
|
98fb360483 | ||
|
|
56cbc441f5 | ||
|
|
113de6a009 | ||
|
|
dc2a6b4638 | ||
|
|
9081b0bed9 | ||
|
|
d64877543a | ||
|
|
649fba1bfa | ||
|
|
7dfb97608f | ||
|
|
35bb2f18d9 | ||
|
|
ad84f5ae58 | ||
|
|
922c429716 | ||
|
|
a2d634d96f | ||
|
|
7a8e445471 | ||
|
|
b010f9742b | ||
|
|
afd70e6b02 |
15
.github/release.yml
vendored
Normal file
15
.github/release.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
changelog:
|
||||||
|
categories:
|
||||||
|
- title: What's New
|
||||||
|
labels:
|
||||||
|
- '*'
|
||||||
|
exclude:
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
- dependencies
|
||||||
|
- title: Bug Fixes
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
- title: Dependencies
|
||||||
|
labels:
|
||||||
|
- dependencies
|
||||||
38
.github/workflows/BuildDockerOnTag-Prerelease.yml
vendored
Normal file
38
.github/workflows/BuildDockerOnTag-Prerelease.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: Build Pre-release Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v[0-9]+.[0-9]+.[0-9]+-preview.[0-9]'
|
||||||
|
- 'v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]'
|
||||||
|
|
||||||
|
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 ${{ github.ref }}
|
||||||
|
- name: Sign in to Nuget
|
||||||
|
run: dotnet nuget add source --username michael-j-green --password ${{ secrets.NUGETKEY }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/gaseous-project/index.json"
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64 #,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: gaseousgames/gaseousserver:${{ github.ref_name}}
|
||||||
37
.github/workflows/BuildDockerOnTag-Release.yml
vendored
Normal file
37
.github/workflows/BuildDockerOnTag-Release.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Build Release Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||||
|
|
||||||
|
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 ${{ github.ref }}
|
||||||
|
- name: Sign in to Nuget
|
||||||
|
run: dotnet nuget add source --username michael-j-green --password ${{ secrets.NUGETKEY }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/gaseous-project/index.json"
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64 #,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: gaseousgames/gaseousserver:latest,gaseousgames/gaseousserver:${{ github.ref_name}}
|
||||||
34
.github/workflows/BuildDockerOnTag.yml
vendored
34
.github/workflows/BuildDockerOnTag.yml
vendored
@@ -1,34 +0,0 @@
|
|||||||
name: Build Docker Image on New Tag
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- '*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
docker:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
-
|
|
||||||
name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
-
|
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
-
|
|
||||||
name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
-
|
|
||||||
name: Build and push
|
|
||||||
uses: docker/build-push-action@v4
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64 #,linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: gaseousgames/gaseousserver:latest,gaseousgames/gaseousserver:${{ github.ref_name}}
|
|
||||||
85
.github/workflows/codeql.yml
vendored
Normal file
85
.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# 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}}"
|
||||||
24
.github/workflows/dotnet.yml
vendored
Normal file
24
.github/workflows/dotnet.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: .NET
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v1
|
||||||
|
with:
|
||||||
|
dotnet-version: 7.0.x
|
||||||
|
- name: Sign in to Nuget
|
||||||
|
run: dotnet nuget add source --username michael-j-green --password ${{ secrets.NUGETKEY }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/gaseous-project/index.json"
|
||||||
|
- name: Restore dependencies
|
||||||
|
run: dotnet restore
|
||||||
|
- name: Build
|
||||||
|
run: dotnet build --no-restore
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -403,3 +403,5 @@ ASALocalRun/
|
|||||||
|
|
||||||
# Local History for Visual Studio
|
# Local History for Visual Studio
|
||||||
.localhistory/
|
.localhistory/
|
||||||
|
gaseous-server/.DS_Store
|
||||||
|
gaseous-server/wwwroot/emulators/EmulatorJS
|
||||||
|
|||||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the
|
||||||
|
overall community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or
|
||||||
|
advances of any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email
|
||||||
|
address, without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
michael.green@mrgtech.net.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or
|
||||||
|
permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.0, available at
|
||||||
|
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||||
|
enforcement ladder](https://github.com/mozilla/diversity).
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
https://www.contributor-covenant.org/faq. Translations are available at
|
||||||
|
https://www.contributor-covenant.org/translations.
|
||||||
3
Directory.Build.props
Normal file
3
Directory.Build.props
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<Project>
|
||||||
|
<Import Project="build\Version.props" />
|
||||||
|
</Project>
|
||||||
@@ -9,6 +9,12 @@ RUN dotnet restore "gaseous-server/gaseous-server.csproj"
|
|||||||
# Build and publish a release
|
# Build and publish a release
|
||||||
RUN dotnet publish "gaseous-server/gaseous-server.csproj" --use-current-runtime --self-contained false -c Release -o out
|
RUN dotnet publish "gaseous-server/gaseous-server.csproj" --use-current-runtime --self-contained 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
|
# Build runtime image
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:7.0
|
FROM mcr.microsoft.com/dotnet/aspnet:7.0
|
||||||
WORKDIR /App
|
WORKDIR /App
|
||||||
|
|||||||
34
Gaseous.sln
34
Gaseous.sln
@@ -1,18 +1,8 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 16
|
# Visual Studio Version 16
|
||||||
VisualStudioVersion = 25.0.1704.4
|
VisualStudioVersion = 25.0.1704.4
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-identifier-testapp", "gaseous-identifier\gaseous-identifier-testapp.csproj", "{F5C42134-5372-430A-A9AE-1871981850DB}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-signature-parser", "gaseous-signature-parser\gaseous-signature-parser.csproj", "{DAEBBB82-5051-43FD-A406-F9D64A38F468}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-romsignatureobject", "gaseous-romsignatureobject\gaseous-romsignatureobject.csproj", "{9DCD243D-37CE-4562-8411-B5242B687D4F}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-signature-ingestor", "gaseous-signature-ingestor\gaseous-signature-ingestor.csproj", "{86DF6E45-2C2B-4C30-AEC1-E7EF8C5CEA7D}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-tools", "gaseous-tools\gaseous-tools.csproj", "{08FE408A-5EC1-4110-ABD8-D19A1155B8CE}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-server", "gaseous-server\gaseous-server.csproj", "{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "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}"
|
||||||
@@ -21,6 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
Dockerfile = Dockerfile
|
Dockerfile = Dockerfile
|
||||||
README.MD = README.MD
|
README.MD = README.MD
|
||||||
LICENSE = LICENSE
|
LICENSE = LICENSE
|
||||||
|
.gitignore = .gitignore
|
||||||
|
.github\dependabot.yml = .github\dependabot.yml
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "screenshots", "screenshots", "{F1A847C7-57BC-4DA9-8F83-CD060A7F5122}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "screenshots", "screenshots", "{F1A847C7-57BC-4DA9-8F83-CD060A7F5122}"
|
||||||
@@ -35,34 +27,14 @@ Global
|
|||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{F5C42134-5372-430A-A9AE-1871981850DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{F5C42134-5372-430A-A9AE-1871981850DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{F5C42134-5372-430A-A9AE-1871981850DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{F5C42134-5372-430A-A9AE-1871981850DB}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{08699C93-15CD-4E39-9053-D3C8056CE938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{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}.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.ActiveCfg = Release|Any CPU
|
||||||
{08699C93-15CD-4E39-9053-D3C8056CE938}.Release|Any CPU.Build.0 = Release|Any CPU
|
{08699C93-15CD-4E39-9053-D3C8056CE938}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{DAEBBB82-5051-43FD-A406-F9D64A38F468}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{DAEBBB82-5051-43FD-A406-F9D64A38F468}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{DAEBBB82-5051-43FD-A406-F9D64A38F468}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{DAEBBB82-5051-43FD-A406-F9D64A38F468}.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.ActiveCfg = Debug|Any CPU
|
||||||
{FFCEC386-033F-4772-A45B-D33579F2E5EE}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
|
||||||
{FFCEC386-033F-4772-A45B-D33579F2E5EE}.Release|Any CPU.Build.0 = Release|Any CPU
|
{FFCEC386-033F-4772-A45B-D33579F2E5EE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{9DCD243D-37CE-4562-8411-B5242B687D4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{9DCD243D-37CE-4562-8411-B5242B687D4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{9DCD243D-37CE-4562-8411-B5242B687D4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{9DCD243D-37CE-4562-8411-B5242B687D4F}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{86DF6E45-2C2B-4C30-AEC1-E7EF8C5CEA7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{86DF6E45-2C2B-4C30-AEC1-E7EF8C5CEA7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{86DF6E45-2C2B-4C30-AEC1-E7EF8C5CEA7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{86DF6E45-2C2B-4C30-AEC1-E7EF8C5CEA7D}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{08FE408A-5EC1-4110-ABD8-D19A1155B8CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{08FE408A-5EC1-4110-ABD8-D19A1155B8CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{08FE408A-5EC1-4110-ABD8-D19A1155B8CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{08FE408A-5EC1-4110-ABD8-D19A1155B8CE}.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
|
||||||
|
|||||||
117
README.MD
117
README.MD
@@ -1,13 +1,25 @@
|
|||||||
# Gaseous Server
|
# Gaseous Server
|
||||||
|
|
||||||
This is the server for the Gaseous system. All your games and metadata are stored within.
|
This is the server for the Gaseous system. It offers ROM and title management, as well as some basic in browser emulation of those ROMs.
|
||||||
|
|
||||||
|
## Warning
|
||||||
|
|
||||||
|
This project is currently not suitable for being exposed to the internet.
|
||||||
|
1. there is currently no authentication support, meaning anyone could trash your library
|
||||||
|
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
|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
* MySQL Server 8+
|
* MariaDB 11.1.2 or MySQL Server 8+
|
||||||
|
* These are the database versions Gaseous has been tested and developed against. Your mileage may vary with earlier versions.
|
||||||
|
* Currently MariaDB is the preferred database server, while MySQL will continue to be supported for existing users (they should be interchangable).
|
||||||
|
* 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
|
## Third Party Projects
|
||||||
@@ -16,6 +28,12 @@ The following projects are used by Gaseous
|
|||||||
* https://github.com/JamesNK/Newtonsoft.Json
|
* https://github.com/JamesNK/Newtonsoft.Json
|
||||||
* https://www.nuget.org/packages/MySql.Data/8.0.32.1
|
* https://www.nuget.org/packages/MySql.Data/8.0.32.1
|
||||||
* https://github.com/kamranayub/igdb-dotnet
|
* https://github.com/kamranayub/igdb-dotnet
|
||||||
|
* https://github.com/EmulatorJS/EmulatorJS
|
||||||
|
|
||||||
|
## Discord Server
|
||||||
|
[](https://discord.gg/Nhu7wpT3k4)
|
||||||
|
|
||||||
|
# Setup
|
||||||
|
|
||||||
## Configuration File
|
## 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).
|
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).
|
||||||
@@ -49,44 +67,108 @@ When Gaseous-Server is started for the first time, it creates a configuration fi
|
|||||||
},
|
},
|
||||||
"LoggingConfiguration": {
|
"LoggingConfiguration": {
|
||||||
"DebugLogging": false,
|
"DebugLogging": false,
|
||||||
"LogFormat": "text"
|
"LogRetention": 7
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Deploy with Docker
|
## 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.
|
Dockerfile and docker-compose.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"
|
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
|
2. Change into the gaseous-server directory
|
||||||
3. Open the docker-compose.yml file and edit the igdbclientid and igdbclientsecret to the values retrieved from your IGDB account
|
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 up -d"
|
4. Run the command ```docker-compose --file docker-compose-{database}-build.yml up -d```
|
||||||
5. Connect to the host on port 5198
|
5. Connect to the host on port 5198
|
||||||
|
|
||||||
## Adding Content
|
## Source
|
||||||
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 ROM to game.
|
### 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
|
||||||
|
While games can be added to the server without them, it is recommended adding some signature DAT files beforehand to allow for better matching of ROMs to games.
|
||||||
|
|
||||||
These signature DAT files contain a list of titles with hashes for many of the ROM images that have been found by the community.
|
These signature DAT files contain a list of titles with hashes for many of the ROM images that have been found by the community.
|
||||||
|
|
||||||
Currently only TOSEC is supported, though more will be added.
|
Currently supported DAT's:
|
||||||
|
* TOSEC: https://www.tosecdev.org/downloads/category/56-2023-01-23
|
||||||
|
* MAME Arcade and MAME Mess: https://www.progettosnaps.net/dats/MAME
|
||||||
|
|
||||||
### Adding signature DAT files
|
If there are other DAT's you'd like to see support for, please raise an issue with a link to the DAT's.
|
||||||
|
|
||||||
|
## Adding signature DAT files
|
||||||
|
### TOSEC
|
||||||
1. Download the DAT files from the source website. For example; from https://www.tosecdev.org/downloads/category/56-2023-01-23
|
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
|
2. Extract the archive
|
||||||
3. Copy the DAT files to ~/.gaseous-server/Data/Signatures/TOSEC/
|
3. Copy the DAT files to ~/.gaseous-server/Data/Signatures/TOSEC/
|
||||||
|
|
||||||
### Adding game image files
|
### MAME Arcade
|
||||||
1. Ensure your game image file is unzipped, and clearly named. 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.
|
1. Download the DAT files from the source website. For example; from https://www.progettosnaps.net/dats/MAME
|
||||||
2. Copy the file to ~/.gaseous-server/Data/Import
|
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:
|
Image to game matching follows the following order of operations, stopping the process at the first match:
|
||||||
### Get the file signature
|
### Get the file signature
|
||||||
1. Attempt a hash search
|
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
|
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
|
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
|
### Create a list of search candidates
|
||||||
Before beginning, remove any version numbers.
|
Before beginning, remove any version numbers, and anything in the search string that is between ()
|
||||||
1. Add the full name of the image
|
1. Add the full name of the image
|
||||||
2. Add the name of the image with any " - " replaced by ": "
|
2. Add the name of the image with any " - " replaced by ": "
|
||||||
3. Add the name of the image with text after a " - " removed
|
3. Add the name of the image with text after a " - " removed
|
||||||
@@ -97,5 +179,8 @@ Loop through each of the search candidates searching using:
|
|||||||
1. "where" - exact match as the search candidate
|
1. "where" - exact match as the search candidate
|
||||||
2. "wherefuzzy" - partial match using wildcards
|
2. "wherefuzzy" - partial match using wildcards
|
||||||
3. "search" - uses a more flexible search method
|
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: that if more than one result is found, the image will be set as "Unknown" as there is no way for Gaseous to know which title is the correct one.
|
**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.
|
||||||
|
|||||||
5
build/Version.props
Normal file
5
build/Version.props
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<Version>1.5.0</Version>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
39
docker-compose-mariadb-build.yml
Normal file
39
docker-compose-mariadb-build.yml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
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: 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:
|
||||||
38
docker-compose-mariadb.yml
Normal file
38
docker-compose-mariadb.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
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:
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
// parse command line
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Xml;
|
|
||||||
using System.Xml.Serialization;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using gaseous_romsignatureobject;
|
|
||||||
using gaseous_signature_parser.parsers;
|
|
||||||
|
|
||||||
string[] commandLineArgs = Environment.GetCommandLineArgs();
|
|
||||||
|
|
||||||
string scanPath = "./";
|
|
||||||
string tosecXML = "";
|
|
||||||
|
|
||||||
string inArgument = "";
|
|
||||||
foreach (string commandLineArg in commandLineArgs)
|
|
||||||
{
|
|
||||||
if (commandLineArg != commandLineArgs[0])
|
|
||||||
{
|
|
||||||
if (inArgument == "")
|
|
||||||
{
|
|
||||||
switch (commandLineArg.ToLower())
|
|
||||||
{
|
|
||||||
case "-scanpath":
|
|
||||||
inArgument = commandLineArg.ToLower();
|
|
||||||
break;
|
|
||||||
case "-tosecpath":
|
|
||||||
inArgument = commandLineArg.ToLower();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
switch (inArgument)
|
|
||||||
{
|
|
||||||
case "-scanpath":
|
|
||||||
scanPath = commandLineArg;
|
|
||||||
break;
|
|
||||||
case "-tosecpath":
|
|
||||||
tosecXML = commandLineArg;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
inArgument = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scanPath = Path.GetFullPath(scanPath);
|
|
||||||
Console.WriteLine("ROM search path: " + scanPath);
|
|
||||||
|
|
||||||
List<RomSignatureObject> romSignatures = new List<RomSignatureObject>();
|
|
||||||
System.Collections.ArrayList availablePlatforms = new System.Collections.ArrayList();
|
|
||||||
|
|
||||||
// load TOSEC XML files
|
|
||||||
if (tosecXML != null && tosecXML.Length > 0)
|
|
||||||
{
|
|
||||||
tosecXML = Path.GetFullPath(tosecXML);
|
|
||||||
Console.WriteLine("TOSEC is enabled");
|
|
||||||
Console.WriteLine("TOSEC XML search path: " + tosecXML);
|
|
||||||
|
|
||||||
string[] tosecPathContents = Directory.GetFiles(tosecXML);
|
|
||||||
int lastCLILineLength = 0;
|
|
||||||
for (UInt16 i = 0; i < tosecPathContents.Length; ++i)
|
|
||||||
{
|
|
||||||
string tosecXMLFile = tosecPathContents[i];
|
|
||||||
|
|
||||||
TosecParser tosecParser = new TosecParser();
|
|
||||||
RomSignatureObject tosecObject = tosecParser.Parse(tosecXMLFile);
|
|
||||||
|
|
||||||
string statusOutput = i + " / " + tosecPathContents.Length + " : " + Path.GetFileName(tosecXMLFile);
|
|
||||||
Console.Write("\r " + statusOutput.PadRight(lastCLILineLength, ' ') + "\r");
|
|
||||||
lastCLILineLength = statusOutput.Length;
|
|
||||||
|
|
||||||
foreach (RomSignatureObject.Game gameRom in tosecObject.Games)
|
|
||||||
{
|
|
||||||
if (!availablePlatforms.Contains(gameRom.System))
|
|
||||||
{
|
|
||||||
availablePlatforms.Add(gameRom.System);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
romSignatures.Add(tosecObject);
|
|
||||||
}
|
|
||||||
Console.WriteLine("");
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
Console.WriteLine("TOSEC is disabled.");
|
|
||||||
}
|
|
||||||
Console.WriteLine(romSignatures.Count + " TOSEC files loaded");
|
|
||||||
|
|
||||||
// Summarise signatures
|
|
||||||
if (availablePlatforms.Count > 0)
|
|
||||||
{
|
|
||||||
availablePlatforms.Sort();
|
|
||||||
Console.WriteLine("Platforms loaded:");
|
|
||||||
foreach (string platform in availablePlatforms)
|
|
||||||
{
|
|
||||||
Console.WriteLine(" * " + platform);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine("Examining files");
|
|
||||||
string[] romPathContents = Directory.GetFiles(scanPath);
|
|
||||||
foreach (string romFile in romPathContents)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Checking " + romFile);
|
|
||||||
|
|
||||||
var stream = File.OpenRead(romFile);
|
|
||||||
|
|
||||||
var md5 = MD5.Create();
|
|
||||||
byte[] md5HashByte = md5.ComputeHash(stream);
|
|
||||||
string md5Hash = BitConverter.ToString(md5HashByte).Replace("-", "").ToLowerInvariant();
|
|
||||||
|
|
||||||
var sha1 = SHA1.Create();
|
|
||||||
byte[] sha1HashByte = sha1.ComputeHash(stream);
|
|
||||||
string sha1Hash = BitConverter.ToString(sha1HashByte).Replace("-", "").ToLowerInvariant();
|
|
||||||
|
|
||||||
bool gameFound = false;
|
|
||||||
foreach (RomSignatureObject tosecList in romSignatures)
|
|
||||||
{
|
|
||||||
foreach (RomSignatureObject.Game gameObject in tosecList.Games)
|
|
||||||
{
|
|
||||||
foreach (RomSignatureObject.Game.Rom romObject in gameObject.Roms)
|
|
||||||
{
|
|
||||||
if (romObject.Md5 != null)
|
|
||||||
{
|
|
||||||
if (md5Hash == romObject.Md5.ToLowerInvariant())
|
|
||||||
{
|
|
||||||
// match
|
|
||||||
gameFound = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (romObject.Sha1 != null)
|
|
||||||
{
|
|
||||||
if (md5Hash == romObject.Sha1.ToLowerInvariant())
|
|
||||||
{
|
|
||||||
// match
|
|
||||||
gameFound = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (gameFound == true)
|
|
||||||
{
|
|
||||||
Console.WriteLine(romObject.Name);
|
|
||||||
|
|
||||||
RomSignatureObject.Game gameSignature = gameObject;
|
|
||||||
gameSignature.Roms.Clear();
|
|
||||||
gameSignature.Roms.Add(romObject);
|
|
||||||
|
|
||||||
var jsonSerializerSettings = new JsonSerializerSettings();
|
|
||||||
jsonSerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
|
|
||||||
jsonSerializerSettings.NullValueHandling = NullValueHandling.Ignore;
|
|
||||||
Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(gameSignature, Newtonsoft.Json.Formatting.Indented, jsonSerializerSettings));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (gameFound == true) { break; }
|
|
||||||
}
|
|
||||||
if (gameFound == true) { break; }
|
|
||||||
}
|
|
||||||
if (gameFound == false)
|
|
||||||
{
|
|
||||||
Console.WriteLine("File not found in TOSEC library");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string SearchTitle = "California Games";
|
|
||||||
foreach (RomSignatureObject romSignatureObject in romSignatures)
|
|
||||||
{
|
|
||||||
foreach (RomSignatureObject.Game gameObject in romSignatureObject.Games)
|
|
||||||
{
|
|
||||||
if (gameObject.Name == SearchTitle)
|
|
||||||
{
|
|
||||||
var jsonSerializerSettings = new JsonSerializerSettings();
|
|
||||||
jsonSerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
|
|
||||||
jsonSerializerSettings.NullValueHandling = NullValueHandling.Ignore;
|
|
||||||
Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(gameObject, Newtonsoft.Json.Formatting.Indented, jsonSerializerSettings));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
|
||||||
<RootNamespace>gaseous_identifier</RootNamespace>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Remove="Newtonsoft.Json" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\gaseous-romsignatureobject\gaseous-romsignatureobject.csproj" />
|
|
||||||
<ProjectReference Include="..\gaseous-signature-parser\gaseous-signature-parser.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace gaseous_romsignatureobject
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Object returned by all signature engines containing metadata about the ROM's in the data files
|
|
||||||
///
|
|
||||||
/// This class was based on the TOSEC dataset, so may need to be expanded as new signature engines are added
|
|
||||||
/// </summary>
|
|
||||||
public class RomSignatureObject
|
|
||||||
{
|
|
||||||
public string? Name { get; set; }
|
|
||||||
public string? Description { 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 Uri? Url { get; set; }
|
|
||||||
public string? SourceType { get; set; }
|
|
||||||
public string SourceMd5 { get; set; } = "";
|
|
||||||
public string SourceSHA1 { get; set; } = "";
|
|
||||||
|
|
||||||
public List<Game> Games { get; set; } = new List<Game>();
|
|
||||||
|
|
||||||
public class Game
|
|
||||||
{
|
|
||||||
public string? Name { get; set; }
|
|
||||||
public string? Description { get; set; }
|
|
||||||
public string? Year { get; set; }
|
|
||||||
public string? Publisher { get; set; }
|
|
||||||
public DemoTypes Demo { get; set; }
|
|
||||||
public string? System { get; set; }
|
|
||||||
public string? SystemVariant { get; set; }
|
|
||||||
public string? Video { get; set; }
|
|
||||||
public string? Country { get; set; }
|
|
||||||
public string? Language { get; set; }
|
|
||||||
public string? Copyright { get; set; }
|
|
||||||
public List<Rom> Roms { get; set; } = new List<Rom>();
|
|
||||||
public int RomCount
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Roms.Count();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum DemoTypes
|
|
||||||
{
|
|
||||||
NotDemo = 0,
|
|
||||||
demo = 1,
|
|
||||||
demo_kiosk = 2,
|
|
||||||
demo_playable = 3,
|
|
||||||
demo_rolling = 4,
|
|
||||||
demo_slideshow = 5
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Rom
|
|
||||||
{
|
|
||||||
public string? Name { get; set; }
|
|
||||||
public UInt64? Size { get; set; }
|
|
||||||
public string? Crc { get; set; }
|
|
||||||
public string? Md5 { get; set; }
|
|
||||||
public string? Sha1 { get; set; }
|
|
||||||
|
|
||||||
public string? DevelopmentStatus { get; set; }
|
|
||||||
|
|
||||||
public List<string> flags { get; set; } = new List<string>();
|
|
||||||
|
|
||||||
public RomTypes RomType { get; set; }
|
|
||||||
public string? RomTypeMedia { get; set; }
|
|
||||||
public string? MediaLabel { get; set; }
|
|
||||||
|
|
||||||
public SignatureSourceType SignatureSource { get; set; }
|
|
||||||
|
|
||||||
public enum SignatureSourceType
|
|
||||||
{
|
|
||||||
None = 0,
|
|
||||||
TOSEC = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum RomTypes
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Media type is unknown
|
|
||||||
/// </summary>
|
|
||||||
Unknown = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Optical media
|
|
||||||
/// </summary>
|
|
||||||
Disc = 1,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Magnetic media
|
|
||||||
/// </summary>
|
|
||||||
Disk = 2,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Individual files
|
|
||||||
/// </summary>
|
|
||||||
File = 3,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Individual pars
|
|
||||||
/// </summary>
|
|
||||||
Part = 4,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tape base media
|
|
||||||
/// </summary>
|
|
||||||
Tape = 5,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Side of the media
|
|
||||||
/// </summary>
|
|
||||||
Side = 6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
|
||||||
<RootNamespace>gaseous_romsignatureobject</RootNamespace>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
||||||
BIN
gaseous-server/.DS_Store
vendored
BIN
gaseous-server/.DS_Store
vendored
Binary file not shown.
BIN
gaseous-server/Assets/.DS_Store
vendored
BIN
gaseous-server/Assets/.DS_Store
vendored
Binary file not shown.
BIN
gaseous-server/Assets/Ratings/.DS_Store
vendored
BIN
gaseous-server/Assets/Ratings/.DS_Store
vendored
Binary file not shown.
16
gaseous-server/Classes/Auth/Classes/IdentityRole.cs
Normal file
16
gaseous-server/Classes/Auth/Classes/IdentityRole.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using gaseous_server.Classes;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace Authentication
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class that implements the ASP.NET Identity
|
||||||
|
/// IRole interface
|
||||||
|
/// </summary>
|
||||||
|
public class ApplicationRole : IdentityRole
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
16
gaseous-server/Classes/Auth/Classes/IdentityUser.cs
Normal file
16
gaseous-server/Classes/Auth/Classes/IdentityUser.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using gaseous_server.Classes;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Authentication
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class that implements the ASP.NET Identity
|
||||||
|
/// IUser interface
|
||||||
|
/// </summary>
|
||||||
|
public class ApplicationUser : IdentityUser
|
||||||
|
{
|
||||||
|
public SecurityProfileViewModel SecurityProfile { get; set; }
|
||||||
|
public List<UserPreferenceViewModel> UserPreferences { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
171
gaseous-server/Classes/Auth/Classes/RoleStore.cs
Normal file
171
gaseous-server/Classes/Auth/Classes/RoleStore.cs
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
using System;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using gaseous_server.Classes;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using MySqlConnector;
|
||||||
|
|
||||||
|
namespace Authentication
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class that implements the key ASP.NET Identity role store iterfaces
|
||||||
|
/// </summary>
|
||||||
|
public class RoleStore : IQueryableRoleStore<ApplicationRole>
|
||||||
|
{
|
||||||
|
private RoleTable roleTable;
|
||||||
|
public Database Database { get; private set; }
|
||||||
|
|
||||||
|
public IQueryable<ApplicationRole> Roles
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
List<ApplicationRole> roles = roleTable.GetRoles();
|
||||||
|
return roles.AsQueryable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoleStore()
|
||||||
|
{
|
||||||
|
Database = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
roleTable = new RoleTable(Database);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor that takes a MySQLDatabase as argument
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="database"></param>
|
||||||
|
public RoleStore(Database database)
|
||||||
|
{
|
||||||
|
Database = database;
|
||||||
|
roleTable = new RoleTable(database);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IdentityResult> CreateAsync(ApplicationRole role, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (role == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("role");
|
||||||
|
}
|
||||||
|
|
||||||
|
roleTable.Insert(role);
|
||||||
|
|
||||||
|
return Task.FromResult<IdentityResult>(IdentityResult.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IdentityResult> DeleteAsync(ApplicationRole role, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (role == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
roleTable.Delete(role.Id);
|
||||||
|
|
||||||
|
return Task.FromResult<IdentityResult>(IdentityResult.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ApplicationRole> FindByIdAsync(string roleId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ApplicationRole result = roleTable.GetRoleById(roleId) as ApplicationRole;
|
||||||
|
|
||||||
|
return Task.FromResult<ApplicationRole>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> RoleExistsAsync(string roleId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ApplicationRole? result = roleTable.GetRoleById(roleId) as ApplicationRole;
|
||||||
|
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
return Task.FromResult<bool>(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Task.FromResult<bool>(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ApplicationRole?> FindByNameAsync(string roleName, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ApplicationRole? result = roleTable.GetRoleByName(roleName) as ApplicationRole;
|
||||||
|
|
||||||
|
return Task.FromResult<ApplicationRole?>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IdentityResult> UpdateAsync(ApplicationRole role, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (role == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
roleTable.Update(role);
|
||||||
|
|
||||||
|
return Task.FromResult<IdentityResult>(IdentityResult.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (Database != null)
|
||||||
|
{
|
||||||
|
Database = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string> GetRoleIdAsync(ApplicationRole role, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (role != null)
|
||||||
|
{
|
||||||
|
return Task.FromResult<string>(roleTable.GetRoleId(role.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<string>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string?> GetRoleNameAsync(ApplicationRole role, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (role != null)
|
||||||
|
{
|
||||||
|
return Task.FromResult<string?>(roleTable.GetRoleName(role.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<string?>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetRoleNameAsync(ApplicationRole role, string? roleName, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (role == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("role");
|
||||||
|
}
|
||||||
|
|
||||||
|
role.Name = roleName;
|
||||||
|
roleTable.Update(role);
|
||||||
|
|
||||||
|
return Task.FromResult<IdentityResult>(IdentityResult.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string?> GetNormalizedRoleNameAsync(ApplicationRole role, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (role != null)
|
||||||
|
{
|
||||||
|
return Task.FromResult<string?>(roleTable.GetRoleName(role.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<string?>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetNormalizedRoleNameAsync(ApplicationRole role, string? normalizedName, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (role == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("role");
|
||||||
|
}
|
||||||
|
|
||||||
|
role.Name = normalizedName;
|
||||||
|
roleTable.Update(role);
|
||||||
|
|
||||||
|
return Task.FromResult<IdentityResult>(IdentityResult.Success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
168
gaseous-server/Classes/Auth/Classes/RoleTable.cs
Normal file
168
gaseous-server/Classes/Auth/Classes/RoleTable.cs
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using gaseous_server.Classes;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace Authentication
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class that represents the Role table in the MySQL Database
|
||||||
|
/// </summary>
|
||||||
|
public class RoleTable
|
||||||
|
{
|
||||||
|
private Database _database;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor that takes a MySQLDatabase instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="database"></param>
|
||||||
|
public RoleTable(Database database)
|
||||||
|
{
|
||||||
|
_database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deltes a role from the Roles table
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="roleId">The role Id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int Delete(string roleId)
|
||||||
|
{
|
||||||
|
string commandText = "Delete from Roles where Id = @id";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("@id", roleId);
|
||||||
|
|
||||||
|
return (int)_database.ExecuteNonQuery(commandText, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a new Role in the Roles table
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="roleName">The role's name</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int Insert(ApplicationRole role)
|
||||||
|
{
|
||||||
|
string commandText = "Insert into Roles (Id, Name) values (@id, @name)";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("@name", role.Name);
|
||||||
|
parameters.Add("@id", role.Id);
|
||||||
|
|
||||||
|
return (int)_database.ExecuteNonQuery(commandText, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a role name given the roleId
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="roleId">The role Id</param>
|
||||||
|
/// <returns>Role name</returns>
|
||||||
|
public string? GetRoleName(string roleId)
|
||||||
|
{
|
||||||
|
string commandText = "Select Name from Roles where Id = @id";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("@id", roleId);
|
||||||
|
|
||||||
|
DataTable table = _database.ExecuteCMD(commandText, parameters);
|
||||||
|
|
||||||
|
if (table.Rows.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (string)table.Rows[0][0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the role Id given a role name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="roleName">Role's name</param>
|
||||||
|
/// <returns>Role's Id</returns>
|
||||||
|
public string? GetRoleId(string roleName)
|
||||||
|
{
|
||||||
|
string? roleId = null;
|
||||||
|
string commandText = "Select Id from Roles where Name = @name";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@name", roleName } };
|
||||||
|
|
||||||
|
DataTable result = _database.ExecuteCMD(commandText, parameters);
|
||||||
|
if (result.Rows.Count > 0)
|
||||||
|
{
|
||||||
|
return Convert.ToString(result.Rows[0][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return roleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ApplicationRole given the role Id
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="roleId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public ApplicationRole? GetRoleById(string roleId)
|
||||||
|
{
|
||||||
|
var roleName = GetRoleName(roleId);
|
||||||
|
ApplicationRole? role = null;
|
||||||
|
|
||||||
|
if(roleName != null)
|
||||||
|
{
|
||||||
|
role = new ApplicationRole();
|
||||||
|
role.Id = roleId;
|
||||||
|
role.Name = roleName;
|
||||||
|
role.NormalizedName = roleName.ToUpper();
|
||||||
|
}
|
||||||
|
|
||||||
|
return role;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ApplicationRole given the role name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="roleName"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public ApplicationRole? GetRoleByName(string roleName)
|
||||||
|
{
|
||||||
|
var roleId = GetRoleId(roleName);
|
||||||
|
ApplicationRole role = null;
|
||||||
|
|
||||||
|
if (roleId != null)
|
||||||
|
{
|
||||||
|
role = new ApplicationRole();
|
||||||
|
role.Id = roleId;
|
||||||
|
role.Name = roleName;
|
||||||
|
role.NormalizedName = roleName.ToUpper();
|
||||||
|
}
|
||||||
|
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Update(ApplicationRole role)
|
||||||
|
{
|
||||||
|
string commandText = "Update Roles set Name = @name where Id = @id";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("@id", role.Id);
|
||||||
|
|
||||||
|
return (int)_database.ExecuteNonQuery(commandText, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ApplicationRole> GetRoles()
|
||||||
|
{
|
||||||
|
List<ApplicationRole> roles = new List<ApplicationRole>();
|
||||||
|
|
||||||
|
string commandText = "Select Name from Roles";
|
||||||
|
|
||||||
|
var rows = _database.ExecuteCMDDict(commandText);
|
||||||
|
foreach(Dictionary<string, object> row in rows)
|
||||||
|
{
|
||||||
|
ApplicationRole role = (ApplicationRole)Activator.CreateInstance(typeof(ApplicationRole));
|
||||||
|
role.Id = (string)row["Id"];
|
||||||
|
role.Name = (string)row["Name"];
|
||||||
|
role.NormalizedName = ((string)row["Name"]).ToUpper();
|
||||||
|
roles.Add(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
95
gaseous-server/Classes/Auth/Classes/UserClaimsTable.cs
Normal file
95
gaseous-server/Classes/Auth/Classes/UserClaimsTable.cs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using gaseous_server.Classes;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace Authentication
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class that represents the UserClaims table in the MySQL Database
|
||||||
|
/// </summary>
|
||||||
|
public class UserClaimsTable
|
||||||
|
{
|
||||||
|
private Database _database;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor that takes a MySQLDatabase instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="database"></param>
|
||||||
|
public UserClaimsTable(Database database)
|
||||||
|
{
|
||||||
|
_database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a ClaimsIdentity instance given a userId
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user's id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public ClaimsIdentity FindByUserId(string userId)
|
||||||
|
{
|
||||||
|
ClaimsIdentity claims = new ClaimsIdentity();
|
||||||
|
string commandText = "Select * from UserClaims where UserId = @userId";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@UserId", userId } };
|
||||||
|
|
||||||
|
var rows = _database.ExecuteCMD(commandText, parameters).Rows;
|
||||||
|
foreach (DataRow row in rows)
|
||||||
|
{
|
||||||
|
Claim claim = new Claim((string)row["ClaimType"], (string)row["ClaimValue"]);
|
||||||
|
claims.AddClaim(claim);
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes all claims from a user given a userId
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user's id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int Delete(string userId)
|
||||||
|
{
|
||||||
|
string commandText = "Delete from UserClaims where UserId = @userId";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("userId", userId);
|
||||||
|
|
||||||
|
return (int)_database.ExecuteNonQuery(commandText, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a new claim in UserClaims table
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userClaim">User's claim to be added</param>
|
||||||
|
/// <param name="userId">User's id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int Insert(Claim userClaim, string userId)
|
||||||
|
{
|
||||||
|
string commandText = "Insert into UserClaims (ClaimValue, ClaimType, UserId) values (@value, @type, @userId)";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("value", userClaim.Value);
|
||||||
|
parameters.Add("type", userClaim.Type);
|
||||||
|
parameters.Add("userId", userId);
|
||||||
|
|
||||||
|
return (int)_database.ExecuteNonQuery(commandText, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a claim from a user
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">The user to have a claim deleted</param>
|
||||||
|
/// <param name="claim">A claim to be deleted from user</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int Delete(IdentityUser user, Claim claim)
|
||||||
|
{
|
||||||
|
string commandText = "Delete from UserClaims where UserId = @userId and @ClaimValue = @value and ClaimType = @type";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("userId", user.Id);
|
||||||
|
parameters.Add("value", claim.Value);
|
||||||
|
parameters.Add("type", claim.Type);
|
||||||
|
|
||||||
|
return (int)_database.ExecuteNonQuery(commandText, parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
117
gaseous-server/Classes/Auth/Classes/UserLoginsTable.cs
Normal file
117
gaseous-server/Classes/Auth/Classes/UserLoginsTable.cs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
using gaseous_server.Classes;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
|
||||||
|
namespace Authentication
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class that represents the UserLogins table in the MySQL Database
|
||||||
|
/// </summary>
|
||||||
|
public class UserLoginsTable
|
||||||
|
{
|
||||||
|
private Database _database;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor that takes a MySQLDatabase instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="database"></param>
|
||||||
|
public UserLoginsTable(Database database)
|
||||||
|
{
|
||||||
|
_database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a login from a user in the UserLogins table
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">User to have login deleted</param>
|
||||||
|
/// <param name="login">Login to be deleted from user</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int Delete(IdentityUser user, UserLoginInfo login)
|
||||||
|
{
|
||||||
|
string commandText = "Delete from UserLogins where UserId = @userId and LoginProvider = @loginProvider and ProviderKey = @providerKey";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("UserId", user.Id);
|
||||||
|
parameters.Add("loginProvider", login.LoginProvider);
|
||||||
|
parameters.Add("providerKey", login.ProviderKey);
|
||||||
|
|
||||||
|
return (int)_database.ExecuteNonQuery(commandText, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes all Logins from a user in the UserLogins table
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user's id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int Delete(string userId)
|
||||||
|
{
|
||||||
|
string commandText = "Delete from UserLogins where UserId = @userId";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("UserId", userId);
|
||||||
|
|
||||||
|
return (int)_database.ExecuteNonQuery(commandText, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a new login in the UserLogins table
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">User to have new login added</param>
|
||||||
|
/// <param name="login">Login to be added</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int Insert(IdentityUser user, UserLoginInfo login)
|
||||||
|
{
|
||||||
|
string commandText = "Insert into UserLogins (LoginProvider, ProviderKey, UserId) values (@loginProvider, @providerKey, @userId)";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("loginProvider", login.LoginProvider);
|
||||||
|
parameters.Add("providerKey", login.ProviderKey);
|
||||||
|
parameters.Add("userId", user.Id);
|
||||||
|
|
||||||
|
return (int)_database.ExecuteNonQuery(commandText, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return a userId given a user's login
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userLogin">The user's login info</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string? FindUserIdByLogin(UserLoginInfo userLogin)
|
||||||
|
{
|
||||||
|
string commandText = "Select UserId from UserLogins where LoginProvider = @loginProvider and ProviderKey = @providerKey";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("loginProvider", userLogin.LoginProvider);
|
||||||
|
parameters.Add("providerKey", userLogin.ProviderKey);
|
||||||
|
|
||||||
|
DataTable table = _database.ExecuteCMD(commandText, parameters);
|
||||||
|
|
||||||
|
if (table.Rows.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (string)table.Rows[0][0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a list of user's logins
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user's id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public List<UserLoginInfo> FindByUserId(string userId)
|
||||||
|
{
|
||||||
|
List<UserLoginInfo> logins = new List<UserLoginInfo>();
|
||||||
|
string commandText = "Select * from UserLogins where UserId = @userId";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@userId", userId } };
|
||||||
|
|
||||||
|
var rows = _database.ExecuteCMD(commandText, parameters).Rows;
|
||||||
|
foreach (DataRow row in rows)
|
||||||
|
{
|
||||||
|
var login = new UserLoginInfo((string)row["LoginProvider"], (string)row["ProviderKey"], (string)row["LoginProvider"]);
|
||||||
|
logins.Add(login);
|
||||||
|
}
|
||||||
|
|
||||||
|
return logins;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
86
gaseous-server/Classes/Auth/Classes/UserRoleTable.cs
Normal file
86
gaseous-server/Classes/Auth/Classes/UserRoleTable.cs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using gaseous_server.Classes;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace Authentication
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class that represents the UserRoles table in the MySQL Database
|
||||||
|
/// </summary>
|
||||||
|
public class UserRolesTable
|
||||||
|
{
|
||||||
|
private Database _database;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor that takes a MySQLDatabase instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="database"></param>
|
||||||
|
public UserRolesTable(Database database)
|
||||||
|
{
|
||||||
|
_database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a list of user's roles
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user's id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public List<string> FindByUserId(string userId)
|
||||||
|
{
|
||||||
|
List<string> roles = new List<string>();
|
||||||
|
string commandText = "Select Roles.Name from UserRoles, Roles where UserRoles.UserId = @userId and UserRoles.RoleId = Roles.Id";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("@userId", userId);
|
||||||
|
|
||||||
|
var rows = _database.ExecuteCMD(commandText, parameters).Rows;
|
||||||
|
foreach(DataRow row in rows)
|
||||||
|
{
|
||||||
|
roles.Add((string)row["Name"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes all roles from a user in the UserRoles table
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user's id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int Delete(string userId)
|
||||||
|
{
|
||||||
|
string commandText = "Delete from UserRoles where UserId = @userId";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("UserId", userId);
|
||||||
|
|
||||||
|
return (int)_database.ExecuteNonQuery(commandText, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int DeleteUserFromRole(string userId, string roleId)
|
||||||
|
{
|
||||||
|
string commandText = "Delete from UserRoles where UserId = @userId and RoleId = @roleId";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("userId", userId);
|
||||||
|
parameters.Add("roleId", roleId);
|
||||||
|
|
||||||
|
return (int)_database.ExecuteNonQuery(commandText, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a new role for a user in the UserRoles table
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">The User</param>
|
||||||
|
/// <param name="roleId">The Role's id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int Insert(IdentityUser user, string roleId)
|
||||||
|
{
|
||||||
|
string commandText = "Insert into UserRoles (UserId, RoleId) values (@userId, @roleId)";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("userId", user.Id);
|
||||||
|
parameters.Add("roleId", roleId);
|
||||||
|
|
||||||
|
return (int)_database.ExecuteNonQuery(commandText, parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
616
gaseous-server/Classes/Auth/Classes/UserStore.cs
Normal file
616
gaseous-server/Classes/Auth/Classes/UserStore.cs
Normal file
@@ -0,0 +1,616 @@
|
|||||||
|
using System;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using gaseous_server.Classes;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using MySqlConnector;
|
||||||
|
|
||||||
|
namespace Authentication
|
||||||
|
{
|
||||||
|
public class UserStore :
|
||||||
|
IUserStore<ApplicationUser>,
|
||||||
|
IUserRoleStore<ApplicationUser>,
|
||||||
|
IUserLoginStore<ApplicationUser>,
|
||||||
|
IUserClaimStore<ApplicationUser>,
|
||||||
|
IUserPasswordStore<ApplicationUser>,
|
||||||
|
IUserSecurityStampStore<ApplicationUser>,
|
||||||
|
IQueryableUserStore<ApplicationUser>,
|
||||||
|
IUserEmailStore<ApplicationUser>,
|
||||||
|
IUserPhoneNumberStore<ApplicationUser>,
|
||||||
|
IUserTwoFactorStore<ApplicationUser>,
|
||||||
|
IUserLockoutStore<ApplicationUser>
|
||||||
|
{
|
||||||
|
private Database database;
|
||||||
|
|
||||||
|
private UserTable<ApplicationUser> userTable;
|
||||||
|
private RoleTable roleTable;
|
||||||
|
private UserRolesTable userRolesTable;
|
||||||
|
private UserLoginsTable userLoginsTable;
|
||||||
|
private UserClaimsTable userClaimsTable;
|
||||||
|
|
||||||
|
public UserStore()
|
||||||
|
{
|
||||||
|
database = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
userTable = new UserTable<ApplicationUser>(database);
|
||||||
|
roleTable = new RoleTable(database);
|
||||||
|
userRolesTable = new UserRolesTable(database);
|
||||||
|
userLoginsTable = new UserLoginsTable(database);
|
||||||
|
userClaimsTable = new UserClaimsTable(database);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserStore(Database database)
|
||||||
|
{
|
||||||
|
this.database = database;
|
||||||
|
userTable = new UserTable<ApplicationUser>(database);
|
||||||
|
roleTable = new RoleTable(database);
|
||||||
|
userRolesTable = new UserRolesTable(database);
|
||||||
|
userLoginsTable = new UserLoginsTable(database);
|
||||||
|
userClaimsTable = new UserClaimsTable(database);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IQueryable<ApplicationUser> Users
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
List<ApplicationUser> users = userTable.GetUsers();
|
||||||
|
return users.AsQueryable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task AddClaimsAsync(ApplicationUser user, IEnumerable<Claim> claims, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (claims == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Claim claim in claims)
|
||||||
|
{
|
||||||
|
userClaimsTable.Insert(claim, user.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<object>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task AddLoginAsync(ApplicationUser user, UserLoginInfo login, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (login == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("login");
|
||||||
|
}
|
||||||
|
|
||||||
|
userLoginsTable.Insert(user, login);
|
||||||
|
|
||||||
|
return Task.FromResult<object>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task AddToRoleAsync(ApplicationUser user, string roleName, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(roleName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Argument cannot be null or empty: roleName.");
|
||||||
|
}
|
||||||
|
|
||||||
|
string roleId = roleTable.GetRoleId(roleName);
|
||||||
|
if (!string.IsNullOrEmpty(roleId))
|
||||||
|
{
|
||||||
|
userRolesTable.Insert(user, roleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<object>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IdentityResult> CreateAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
userTable.Insert(user);
|
||||||
|
|
||||||
|
return Task.FromResult<IdentityResult>(IdentityResult.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IdentityResult> DeleteAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
userTable.Delete(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<IdentityResult>(IdentityResult.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (database != null)
|
||||||
|
{
|
||||||
|
database = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ApplicationUser?> FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (String.IsNullOrEmpty(normalizedEmail))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("email");
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationUser result = userTable.GetUserByEmail(normalizedEmail) as ApplicationUser;
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
return Task.FromResult<ApplicationUser>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<ApplicationUser>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ApplicationUser?> FindByIdAsync(string userId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(userId))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Null or empty argument: userId");
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationUser result = userTable.GetUserById(userId) as ApplicationUser;
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
return Task.FromResult<ApplicationUser>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<ApplicationUser>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ApplicationUser?> FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (loginProvider == null || providerKey == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("login");
|
||||||
|
}
|
||||||
|
|
||||||
|
UserLoginInfo login = new UserLoginInfo(loginProvider, providerKey, loginProvider);
|
||||||
|
|
||||||
|
var userId = userLoginsTable.FindUserIdByLogin(login);
|
||||||
|
if (userId != null)
|
||||||
|
{
|
||||||
|
ApplicationUser user = userTable.GetUserById(userId) as ApplicationUser;
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
return Task.FromResult<ApplicationUser>(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<ApplicationUser>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ApplicationUser?> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(normalizedUserName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Null or empty argument: normalizedUserName");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ApplicationUser> result = userTable.GetUserByName(normalizedUserName) as List<ApplicationUser>;
|
||||||
|
|
||||||
|
// Should I throw if > 1 user?
|
||||||
|
if (result != null && result.Count == 1)
|
||||||
|
{
|
||||||
|
return Task.FromResult<ApplicationUser>(result[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<ApplicationUser>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<int> GetAccessFailedCountAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(user.AccessFailedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IList<Claim>> GetClaimsAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ClaimsIdentity identity = userClaimsTable.FindByUserId(user.Id);
|
||||||
|
|
||||||
|
return Task.FromResult<IList<Claim>>(identity.Claims.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string?> GetEmailAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(user.Email);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> GetEmailConfirmedAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(user.EmailConfirmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> GetLockoutEnabledAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(user.LockoutEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<DateTimeOffset?> GetLockoutEndDateAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user.LockoutEnd.HasValue)
|
||||||
|
{
|
||||||
|
return Task.FromResult((DateTimeOffset?)user.LockoutEnd.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Task.FromResult((DateTimeOffset?)new DateTimeOffset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IList<UserLoginInfo>> GetLoginsAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<UserLoginInfo> logins = userLoginsTable.FindByUserId(user.Id);
|
||||||
|
if (logins != null)
|
||||||
|
{
|
||||||
|
return Task.FromResult<IList<UserLoginInfo>>(logins);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<IList<UserLoginInfo>>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string?> GetNormalizedEmailAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(user.NormalizedEmail);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string?> GetNormalizedUserNameAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
return Task.FromResult<string?>(userTable.GetUserName(user.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<string?>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string?> GetPasswordHashAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
return Task.FromResult<string?>(userTable.GetPasswordHash(user.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<string?>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string?> GetPhoneNumberAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(user.PhoneNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> GetPhoneNumberConfirmedAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(user.PhoneNumberConfirmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IList<string>> GetRolesAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<string> roles = userRolesTable.FindByUserId(user.Id);
|
||||||
|
{
|
||||||
|
if (roles != null)
|
||||||
|
{
|
||||||
|
return Task.FromResult<IList<string>>(roles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<IList<string>>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string?> GetSecurityStampAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(user.SecurityStamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> GetTwoFactorEnabledAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(user.TwoFactorEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string> GetUserIdAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
return Task.FromResult<string>(userTable.GetUserId(user.NormalizedUserName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<string>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string?> GetUserNameAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
//return Task.FromResult<string?>(userTable.GetUserName(user.Id));
|
||||||
|
return Task.FromResult(user.UserName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<string?>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IList<ApplicationUser>> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IList<ApplicationUser>> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> HasPasswordAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var hasPassword = !string.IsNullOrEmpty(userTable.GetPasswordHash(user.Id));
|
||||||
|
|
||||||
|
return Task.FromResult<bool>(Boolean.Parse(hasPassword.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<int> IncrementAccessFailedCountAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
user.AccessFailedCount++;
|
||||||
|
userTable.Update(user);
|
||||||
|
|
||||||
|
return Task.FromResult(user.AccessFailedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> IsInRoleAsync(ApplicationUser user, string roleName, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(roleName))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("role");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<string> roles = userRolesTable.FindByUserId(user.Id);
|
||||||
|
{
|
||||||
|
if (roles != null)
|
||||||
|
{
|
||||||
|
foreach (string role in roles)
|
||||||
|
{
|
||||||
|
if (role.ToUpper() == roleName.ToUpper())
|
||||||
|
{
|
||||||
|
return Task.FromResult<bool>(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<bool>(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task RemoveClaimsAsync(ApplicationUser user, IEnumerable<Claim> claims, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (claims == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("claim");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Claim claim in claims)
|
||||||
|
{
|
||||||
|
userClaimsTable.Delete(user, claim);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<object>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task RemoveFromRoleAsync(ApplicationUser user, string roleName, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (roleName == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("role");
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentityRole? role = roleTable.GetRoleByName(roleName);
|
||||||
|
|
||||||
|
if (role != null)
|
||||||
|
{
|
||||||
|
userRolesTable.DeleteUserFromRole(user.Id, role.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<Object>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task RemoveLoginAsync(ApplicationUser user, string loginProvider, string providerKey, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginProvider == null || providerKey == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("login");
|
||||||
|
}
|
||||||
|
|
||||||
|
UserLoginInfo login = new UserLoginInfo(loginProvider, providerKey, loginProvider);
|
||||||
|
|
||||||
|
userLoginsTable.Delete(user, login);
|
||||||
|
|
||||||
|
return Task.FromResult<Object>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ReplaceClaimAsync(ApplicationUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (claim == null || newClaim == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("claim");
|
||||||
|
}
|
||||||
|
|
||||||
|
userClaimsTable.Delete(user, claim);
|
||||||
|
userClaimsTable.Insert(newClaim, user.Id);
|
||||||
|
|
||||||
|
return Task.FromResult<Object>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ResetAccessFailedCountAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
user.AccessFailedCount = 0;
|
||||||
|
userTable.Update(user);
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetEmailAsync(ApplicationUser user, string? email, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
user.Email = email;
|
||||||
|
userTable.Update(user);
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetEmailConfirmedAsync(ApplicationUser user, bool confirmed, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
user.EmailConfirmed = confirmed;
|
||||||
|
userTable.Update(user);
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetLockoutEnabledAsync(ApplicationUser user, bool enabled, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
user.LockoutEnabled = enabled;
|
||||||
|
userTable.Update(user);
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetLockoutEndDateAsync(ApplicationUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
user.LockoutEnd = lockoutEnd;
|
||||||
|
userTable.Update(user);
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetNormalizedEmailAsync(ApplicationUser user, string? normalizedEmail, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
user.NormalizedEmail = normalizedEmail;
|
||||||
|
userTable.Update(user);
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetNormalizedUserNameAsync(ApplicationUser user, string? normalizedName, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
user.NormalizedUserName = normalizedName;
|
||||||
|
userTable.Update(user);
|
||||||
|
|
||||||
|
return Task.FromResult<IdentityResult>(IdentityResult.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetPasswordHashAsync(ApplicationUser user, string? passwordHash, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
user.PasswordHash = passwordHash;
|
||||||
|
|
||||||
|
return Task.FromResult<Object>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetPhoneNumberAsync(ApplicationUser user, string? phoneNumber, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
user.PhoneNumber = phoneNumber;
|
||||||
|
userTable.Update(user);
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetPhoneNumberConfirmedAsync(ApplicationUser user, bool confirmed, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
user.PhoneNumberConfirmed = confirmed;
|
||||||
|
userTable.Update(user);
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetSecurityStampAsync(ApplicationUser user, string stamp, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
user.SecurityStamp = stamp;
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetTwoFactorEnabledAsync(ApplicationUser user, bool enabled, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
user.TwoFactorEnabled = enabled;
|
||||||
|
userTable.Update(user);
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetUserNameAsync(ApplicationUser user, string? userName, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
user.UserName = userName;
|
||||||
|
userTable.Update(user);
|
||||||
|
|
||||||
|
return Task.FromResult<IdentityResult>(IdentityResult.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IdentityResult> UpdateAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
userTable.Update(user);
|
||||||
|
|
||||||
|
return Task.FromResult<IdentityResult>(IdentityResult.Success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
441
gaseous-server/Classes/Auth/Classes/UserTable.cs
Normal file
441
gaseous-server/Classes/Auth/Classes/UserTable.cs
Normal file
@@ -0,0 +1,441 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using gaseous_server.Classes;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace Authentication
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class that represents the Users table in the MySQL Database
|
||||||
|
/// </summary>
|
||||||
|
public class UserTable<TUser>
|
||||||
|
where TUser :ApplicationUser
|
||||||
|
{
|
||||||
|
private Database _database;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor that takes a MySQLDatabase instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="database"></param>
|
||||||
|
public UserTable(Database database)
|
||||||
|
{
|
||||||
|
_database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the user's name given a user id
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string? GetUserName(string userId)
|
||||||
|
{
|
||||||
|
string commandText = "Select NormalizedUserName from Users where Id = @id";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@id", userId } };
|
||||||
|
|
||||||
|
DataTable table = _database.ExecuteCMD(commandText, parameters);
|
||||||
|
|
||||||
|
if (table.Rows.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (string)table.Rows[0][0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a User ID given a user name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userName">The user's name</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string? GetUserId(string normalizedUserName)
|
||||||
|
{
|
||||||
|
string commandText = "Select Id from Users where NormalizedUserName = @name";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@name", normalizedUserName } };
|
||||||
|
|
||||||
|
DataTable table = _database.ExecuteCMD(commandText, parameters);
|
||||||
|
|
||||||
|
if (table.Rows.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (string)table.Rows[0][0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an TUser given the user's id
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user's id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public TUser GetUserById(string userId)
|
||||||
|
{
|
||||||
|
TUser user = null;
|
||||||
|
string commandText = "Select * from Users where Id = @id";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@id", userId } };
|
||||||
|
|
||||||
|
var rows = _database.ExecuteCMDDict(commandText, parameters);
|
||||||
|
if (rows != null && rows.Count == 1)
|
||||||
|
{
|
||||||
|
Dictionary<string, object> row = rows[0];
|
||||||
|
user = (TUser)Activator.CreateInstance(typeof(TUser));
|
||||||
|
user.Id = (string)row["Id"];
|
||||||
|
user.UserName = (string?)row["UserName"];
|
||||||
|
user.PasswordHash = (string?)(string.IsNullOrEmpty((string?)row["PasswordHash"]) ? null : row["PasswordHash"]);
|
||||||
|
user.SecurityStamp = (string?)(string.IsNullOrEmpty((string?)row["SecurityStamp"]) ? null : row["SecurityStamp"]);
|
||||||
|
user.ConcurrencyStamp = (string?)(string.IsNullOrEmpty((string?)row["ConcurrencyStamp"]) ? null : row["ConcurrencyStamp"]);
|
||||||
|
user.Email = (string?)(string.IsNullOrEmpty((string?)row["Email"]) ? null : row["Email"]);
|
||||||
|
user.EmailConfirmed = row["EmailConfirmed"] == "1" ? true:false;
|
||||||
|
user.PhoneNumber = (string?)(string.IsNullOrEmpty((string?)row["PhoneNumber"]) ? null : row["PhoneNumber"]);
|
||||||
|
user.PhoneNumberConfirmed = row["PhoneNumberConfirmed"] == "1" ? true : false;
|
||||||
|
user.NormalizedEmail = (string?)(string.IsNullOrEmpty((string?)row["NormalizedEmail"]) ? null : row["NormalizedEmail"]);
|
||||||
|
user.NormalizedUserName = (string?)(string.IsNullOrEmpty((string?)row["NormalizedUserName"]) ? null : row["NormalizedUserName"]);
|
||||||
|
user.LockoutEnabled = row["LockoutEnabled"] == "1" ? true : false;
|
||||||
|
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.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true:false;
|
||||||
|
user.SecurityProfile = GetSecurityProfile(user);
|
||||||
|
user.UserPreferences = GetPreferences(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a list of TUser instances given a user name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="normalizedUserName">User's name</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public List<TUser> GetUserByName(string normalizedUserName)
|
||||||
|
{
|
||||||
|
List<TUser> users = new List<TUser>();
|
||||||
|
string commandText = "Select * from Users where NormalizedEmail = @name";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@name", normalizedUserName } };
|
||||||
|
|
||||||
|
var rows = _database.ExecuteCMDDict(commandText, parameters);
|
||||||
|
foreach(Dictionary<string, object> row in rows)
|
||||||
|
{
|
||||||
|
TUser user = (TUser)Activator.CreateInstance(typeof(TUser));
|
||||||
|
user.Id = (string)row["Id"];
|
||||||
|
user.UserName = (string?)row["UserName"];
|
||||||
|
user.PasswordHash = (string?)(string.IsNullOrEmpty((string?)row["PasswordHash"]) ? null : row["PasswordHash"]);
|
||||||
|
user.SecurityStamp = (string?)(string.IsNullOrEmpty((string?)row["SecurityStamp"]) ? null : row["SecurityStamp"]);
|
||||||
|
user.ConcurrencyStamp = (string?)(string.IsNullOrEmpty((string?)row["ConcurrencyStamp"]) ? null : row["ConcurrencyStamp"]);
|
||||||
|
user.Email = (string?)(string.IsNullOrEmpty((string?)row["Email"]) ? null : row["Email"]);
|
||||||
|
user.EmailConfirmed = row["EmailConfirmed"] == "1" ? true:false;
|
||||||
|
user.PhoneNumber = (string?)(string.IsNullOrEmpty((string?)row["PhoneNumber"]) ? null : row["PhoneNumber"]);
|
||||||
|
user.PhoneNumberConfirmed = row["PhoneNumberConfirmed"] == "1" ? true : false;
|
||||||
|
user.NormalizedEmail = (string?)(string.IsNullOrEmpty((string?)row["NormalizedEmail"]) ? null : row["NormalizedEmail"]);
|
||||||
|
user.NormalizedUserName = (string?)(string.IsNullOrEmpty((string?)row["NormalizedUserName"]) ? null : row["NormalizedUserName"]);
|
||||||
|
user.LockoutEnabled = row["LockoutEnabled"] == "1" ? true : false;
|
||||||
|
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.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true:false;
|
||||||
|
user.SecurityProfile = GetSecurityProfile(user);
|
||||||
|
user.UserPreferences = GetPreferences(user);
|
||||||
|
users.Add(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TUser> GetUsers()
|
||||||
|
{
|
||||||
|
List<TUser> users = new List<TUser>();
|
||||||
|
string commandText = "Select * from Users order by NormalizedUserName";
|
||||||
|
|
||||||
|
var rows = _database.ExecuteCMDDict(commandText);
|
||||||
|
foreach(Dictionary<string, object> row in rows)
|
||||||
|
{
|
||||||
|
TUser user = (TUser)Activator.CreateInstance(typeof(TUser));
|
||||||
|
user.Id = (string)row["Id"];
|
||||||
|
user.UserName = (string?)row["UserName"];
|
||||||
|
user.PasswordHash = (string?)(string.IsNullOrEmpty((string?)row["PasswordHash"]) ? null : row["PasswordHash"]);
|
||||||
|
user.SecurityStamp = (string?)(string.IsNullOrEmpty((string?)row["SecurityStamp"]) ? null : row["SecurityStamp"]);
|
||||||
|
user.ConcurrencyStamp = (string?)(string.IsNullOrEmpty((string?)row["ConcurrencyStamp"]) ? null : row["ConcurrencyStamp"]);
|
||||||
|
user.Email = (string?)(string.IsNullOrEmpty((string?)row["Email"]) ? null : row["Email"]);
|
||||||
|
user.EmailConfirmed = row["EmailConfirmed"] == "1" ? true:false;
|
||||||
|
user.PhoneNumber = (string?)(string.IsNullOrEmpty((string?)row["PhoneNumber"]) ? null : row["PhoneNumber"]);
|
||||||
|
user.PhoneNumberConfirmed = row["PhoneNumberConfirmed"] == "1" ? true : false;
|
||||||
|
user.NormalizedEmail = (string?)(string.IsNullOrEmpty((string?)row["NormalizedEmail"]) ? null : row["NormalizedEmail"]);
|
||||||
|
user.NormalizedUserName = (string?)(string.IsNullOrEmpty((string?)row["NormalizedUserName"]) ? null : row["NormalizedUserName"]);
|
||||||
|
user.LockoutEnabled = row["LockoutEnabled"] == "1" ? true : false;
|
||||||
|
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.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true:false;
|
||||||
|
user.SecurityProfile = GetSecurityProfile(user);
|
||||||
|
user.UserPreferences = GetPreferences(user);
|
||||||
|
users.Add(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUser GetUserByEmail(string email)
|
||||||
|
{
|
||||||
|
List<TUser> users = GetUserByName(email);
|
||||||
|
if (users.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return users[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return the user's password hash
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user's id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GetPasswordHash(string userId)
|
||||||
|
{
|
||||||
|
string commandText = "Select PasswordHash from Users where Id = @id";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("@id", userId);
|
||||||
|
|
||||||
|
DataTable table = _database.ExecuteCMD(commandText, parameters);
|
||||||
|
|
||||||
|
if (table.Rows.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (string)table.Rows[0][0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the user's password hash
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
|
/// <param name="passwordHash"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int SetPasswordHash(string userId, string passwordHash)
|
||||||
|
{
|
||||||
|
string commandText = "Update Users set PasswordHash = @pwdHash where Id = @id";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("@pwdHash", passwordHash);
|
||||||
|
parameters.Add("@id", userId);
|
||||||
|
|
||||||
|
return _database.ExecuteCMD(commandText, parameters).Rows.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the user's security stamp
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GetSecurityStamp(string userId)
|
||||||
|
{
|
||||||
|
string commandText = "Select SecurityStamp from Users where Id = @id";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@id", userId } };
|
||||||
|
DataTable table = _database.ExecuteCMD(commandText, parameters);
|
||||||
|
|
||||||
|
if (table.Rows.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (string)table.Rows[0][0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a new user in the Users table
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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);";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("@name", user.UserName);
|
||||||
|
parameters.Add("@id", user.Id);
|
||||||
|
parameters.Add("@pwdHash", user.PasswordHash);
|
||||||
|
parameters.Add("@SecStamp", user.SecurityStamp);
|
||||||
|
parameters.Add("@concurrencystamp", user.ConcurrencyStamp);
|
||||||
|
parameters.Add("@email", user.Email);
|
||||||
|
parameters.Add("@emailconfirmed", user.EmailConfirmed);
|
||||||
|
parameters.Add("@phonenumber", user.PhoneNumber);
|
||||||
|
parameters.Add("@phonenumberconfirmed", user.PhoneNumberConfirmed);
|
||||||
|
parameters.Add("@normalizedemail", user.NormalizedEmail);
|
||||||
|
parameters.Add("@normalizedusername", user.NormalizedUserName);
|
||||||
|
parameters.Add("@accesscount", user.AccessFailedCount);
|
||||||
|
parameters.Add("@lockoutenabled", user.LockoutEnabled);
|
||||||
|
parameters.Add("@lockoutenddate", user.LockoutEnd);
|
||||||
|
parameters.Add("@twofactorenabled", user.TwoFactorEnabled);
|
||||||
|
|
||||||
|
// set default security profile
|
||||||
|
SetSecurityProfile(user, new SecurityProfileViewModel());
|
||||||
|
|
||||||
|
// set default preferences
|
||||||
|
SetPreferences(user, new List<UserPreferenceViewModel>());
|
||||||
|
|
||||||
|
return _database.ExecuteCMD(commandText, parameters).Rows.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a user from the Users table
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user's id</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("@userId", userId);
|
||||||
|
|
||||||
|
return _database.ExecuteCMD(commandText, parameters).Rows.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a user from the Users table
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int Delete(TUser user)
|
||||||
|
{
|
||||||
|
return Delete(user.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates a user in the Users table
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int Update(TUser user)
|
||||||
|
{
|
||||||
|
string commandText = @"Update Users set UserName = @userName, PasswordHash = @pwdHash, SecurityStamp = @secStamp, ConcurrencyStamp = @concurrencystamp, Email = @email, EmailConfirmed = @emailconfirmed, PhoneNumber = @phonenumber, PhoneNumberConfirmed = @phonenumberconfirmed, NormalizedEmail = @normalizedemail, NormalizedUserName = @normalizedusername, AccessFailedCount = @accesscount, LockoutEnabled = @lockoutenabled, LockoutEnd = @lockoutenddate, TwoFactorEnabled=@twofactorenabled WHERE Id = @userId;";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("@userId", user.Id);
|
||||||
|
parameters.Add("@userName", user.UserName);
|
||||||
|
parameters.Add("@pwdHash", user.PasswordHash);
|
||||||
|
parameters.Add("@SecStamp", user.SecurityStamp);
|
||||||
|
parameters.Add("@concurrencystamp", user.ConcurrencyStamp);
|
||||||
|
parameters.Add("@email", user.Email);
|
||||||
|
parameters.Add("@emailconfirmed", user.EmailConfirmed);
|
||||||
|
parameters.Add("@phonenumber", user.PhoneNumber);
|
||||||
|
parameters.Add("@phonenumberconfirmed", user.PhoneNumberConfirmed);
|
||||||
|
parameters.Add("@normalizedemail", user.NormalizedEmail);
|
||||||
|
parameters.Add("@normalizedusername", user.NormalizedUserName);
|
||||||
|
parameters.Add("@accesscount", user.AccessFailedCount);
|
||||||
|
parameters.Add("@lockoutenabled", user.LockoutEnabled);
|
||||||
|
parameters.Add("@lockoutenddate", user.LockoutEnd);
|
||||||
|
parameters.Add("@twofactorenabled", user.TwoFactorEnabled);
|
||||||
|
|
||||||
|
// set the security profile
|
||||||
|
SetSecurityProfile(user, user.SecurityProfile);
|
||||||
|
|
||||||
|
// set preferences
|
||||||
|
SetPreferences(user, user.UserPreferences);
|
||||||
|
|
||||||
|
return _database.ExecuteCMD(commandText, parameters).Rows.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecurityProfileViewModel GetSecurityProfile(TUser user)
|
||||||
|
{
|
||||||
|
string sql = "SELECT SecurityProfile FROM Users WHERE Id=@Id;";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("Id", user.Id);
|
||||||
|
|
||||||
|
List<Dictionary<string, object>> data = _database.ExecuteCMDDict(sql, dbDict);
|
||||||
|
if (data.Count == 0)
|
||||||
|
{
|
||||||
|
// no saved profile - return the default one
|
||||||
|
return new SecurityProfileViewModel();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string? securityProfileString = (string?)data[0]["SecurityProfile"];
|
||||||
|
if (securityProfileString != null && securityProfileString != "null")
|
||||||
|
{
|
||||||
|
SecurityProfileViewModel securityProfile = Newtonsoft.Json.JsonConvert.DeserializeObject<SecurityProfileViewModel>(securityProfileString);
|
||||||
|
return securityProfile;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new SecurityProfileViewModel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int SetSecurityProfile(TUser user, SecurityProfileViewModel securityProfile)
|
||||||
|
{
|
||||||
|
string commandText = "UPDATE Users SET SecurityProfile=@SecurityProfile WHERE Id=@Id;";
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||||
|
parameters.Add("Id", user.Id);
|
||||||
|
parameters.Add("SecurityProfile", Newtonsoft.Json.JsonConvert.SerializeObject(securityProfile));
|
||||||
|
|
||||||
|
return _database.ExecuteCMD(commandText, parameters).Rows.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UserPreferenceViewModel> GetPreferences(TUser user)
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "SELECT `Setting`, `Value` FROM User_Settings WHERE Id=@id;";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("id", user.Id);
|
||||||
|
|
||||||
|
DataTable data = db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
List<UserPreferenceViewModel> userPrefs = new List<UserPreferenceViewModel>();
|
||||||
|
foreach (DataRow row in data.Rows)
|
||||||
|
{
|
||||||
|
UserPreferenceViewModel userPref = new UserPreferenceViewModel();
|
||||||
|
userPref.Setting = (string)row["Setting"];
|
||||||
|
userPref.Value = (string)row["Value"];
|
||||||
|
userPrefs.Add(userPref);
|
||||||
|
}
|
||||||
|
|
||||||
|
return userPrefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SetPreferences(TUser user, List<UserPreferenceViewModel> model)
|
||||||
|
{
|
||||||
|
if (model != null)
|
||||||
|
{
|
||||||
|
List<UserPreferenceViewModel> userPreferences = GetPreferences(user);
|
||||||
|
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
|
||||||
|
foreach (UserPreferenceViewModel modelItem in model)
|
||||||
|
{
|
||||||
|
bool prefItemFound = false;
|
||||||
|
foreach (UserPreferenceViewModel existing in userPreferences)
|
||||||
|
{
|
||||||
|
if (existing.Setting.ToLower() == modelItem.Setting.ToLower())
|
||||||
|
{
|
||||||
|
prefItemFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string sql = "INSERT INTO User_Settings (`Id`, `Setting`, `Value`) VALUES (@id, @setting, @value);";
|
||||||
|
if (prefItemFound == true)
|
||||||
|
{
|
||||||
|
sql = "UPDATE User_Settings SET `Value`=@value WHERE `Id`=@id AND `Setting`=@setting";
|
||||||
|
}
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("id", user.Id);
|
||||||
|
dbDict.Add("setting", modelItem.Setting);
|
||||||
|
dbDict.Add("value", modelItem.Value);
|
||||||
|
db.ExecuteNonQuery(sql, dbDict);
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.Count;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
100
gaseous-server/Classes/Auth/Models/AccountViewModels.cs
Normal file
100
gaseous-server/Classes/Auth/Models/AccountViewModels.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Authentication
|
||||||
|
{
|
||||||
|
public class ExternalLoginConfirmationViewModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[EmailAddress]
|
||||||
|
[Display(Name = "Email")]
|
||||||
|
public string Email { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ManageUserViewModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[DataType(DataType.Password)]
|
||||||
|
[Display(Name = "Current password")]
|
||||||
|
public string OldPassword { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
|
||||||
|
[DataType(DataType.Password)]
|
||||||
|
[Display(Name = "New password")]
|
||||||
|
public string NewPassword { get; set; }
|
||||||
|
|
||||||
|
[DataType(DataType.Password)]
|
||||||
|
[Display(Name = "Confirm new password")]
|
||||||
|
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
|
||||||
|
public string ConfirmPassword { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LoginViewModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[EmailAddress]
|
||||||
|
[Display(Name = "Email")]
|
||||||
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[DataType(DataType.Password)]
|
||||||
|
[Display(Name = "Password")]
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Remember me?")]
|
||||||
|
public bool RememberMe { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RegisterViewModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[DataType(DataType.Text)]
|
||||||
|
[Display(Name = "User name")]
|
||||||
|
public string UserName { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[EmailAddress]
|
||||||
|
[Display(Name = "Email")]
|
||||||
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
|
||||||
|
[DataType(DataType.Password)]
|
||||||
|
[Display(Name = "Password")]
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
[DataType(DataType.Password)]
|
||||||
|
[Display(Name = "Confirm password")]
|
||||||
|
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
||||||
|
public string ConfirmPassword { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ResetPasswordViewModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[EmailAddress]
|
||||||
|
[Display(Name = "Email")]
|
||||||
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
|
||||||
|
[DataType(DataType.Password)]
|
||||||
|
[Display(Name = "Password")]
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
[DataType(DataType.Password)]
|
||||||
|
[Display(Name = "Confirm password")]
|
||||||
|
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
||||||
|
public string ConfirmPassword { get; set; }
|
||||||
|
|
||||||
|
public string Code { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ForgotPasswordViewModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[EmailAddress]
|
||||||
|
[Display(Name = "Email")]
|
||||||
|
public string Email { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Authentication;
|
||||||
|
|
||||||
|
public class AddPhoneNumberViewModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[Phone]
|
||||||
|
[Display(Name = "Phone number")]
|
||||||
|
public string PhoneNumber { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Authentication;
|
||||||
|
|
||||||
|
public class ChangePasswordViewModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[DataType(DataType.Password)]
|
||||||
|
[Display(Name = "Current password")]
|
||||||
|
public string OldPassword { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 10)]
|
||||||
|
[DataType(DataType.Password)]
|
||||||
|
[Display(Name = "New password")]
|
||||||
|
public string NewPassword { get; set; }
|
||||||
|
|
||||||
|
[DataType(DataType.Password)]
|
||||||
|
[Display(Name = "Confirm new password")]
|
||||||
|
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
|
||||||
|
public string ConfirmPassword { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Authentication;
|
||||||
|
|
||||||
|
public class DisplayRecoveryCodesViewModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public IEnumerable<string> Codes { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
21
gaseous-server/Classes/Auth/Models/IndexViewModel.cs
Normal file
21
gaseous-server/Classes/Auth/Models/IndexViewModel.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace Authentication;
|
||||||
|
|
||||||
|
public class IndexViewModel
|
||||||
|
{
|
||||||
|
public bool HasPassword { get; set; }
|
||||||
|
|
||||||
|
public IList<UserLoginInfo> Logins { get; set; }
|
||||||
|
|
||||||
|
public string PhoneNumber { get; set; }
|
||||||
|
|
||||||
|
public bool TwoFactor { get; set; }
|
||||||
|
|
||||||
|
public bool BrowserRemembered { get; set; }
|
||||||
|
|
||||||
|
public string AuthenticatorKey { get; set; }
|
||||||
|
}
|
||||||
14
gaseous-server/Classes/Auth/Models/ManageLoginsViewModel.cs
Normal file
14
gaseous-server/Classes/Auth/Models/ManageLoginsViewModel.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace Authentication;
|
||||||
|
|
||||||
|
public class ManageLoginsViewModel
|
||||||
|
{
|
||||||
|
public IList<UserLoginInfo> CurrentLogins { get; set; }
|
||||||
|
|
||||||
|
public IList<AuthenticationScheme> OtherLogins { get; set; }
|
||||||
|
}
|
||||||
47
gaseous-server/Classes/Auth/Models/ProfileViewModel.cs
Normal file
47
gaseous-server/Classes/Auth/Models/ProfileViewModel.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
namespace Authentication
|
||||||
|
{
|
||||||
|
public class ProfileBasicViewModel
|
||||||
|
{
|
||||||
|
public string UserId { get; set; }
|
||||||
|
public string UserName { get; set; }
|
||||||
|
public string EmailAddress { get; set; }
|
||||||
|
public List<String> Roles { get; set; }
|
||||||
|
public SecurityProfileViewModel SecurityProfile { get; set; }
|
||||||
|
public List<UserPreferenceViewModel> UserPreferences { get; set; }
|
||||||
|
public string HighestRole {
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string _highestRole = "";
|
||||||
|
foreach (string role in Roles)
|
||||||
|
{
|
||||||
|
switch (role)
|
||||||
|
{
|
||||||
|
case "Admin":
|
||||||
|
// there is no higher
|
||||||
|
_highestRole = role;
|
||||||
|
break;
|
||||||
|
case "Gamer":
|
||||||
|
// only one high is Admin, so check for that
|
||||||
|
if (_highestRole != "Admin")
|
||||||
|
{
|
||||||
|
_highestRole = role;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Player":
|
||||||
|
// make sure _highestRole isn't already set to Gamer or Admin
|
||||||
|
if (_highestRole != "Admin" && _highestRole != "Gamer")
|
||||||
|
{
|
||||||
|
_highestRole = role;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_highestRole = "Player";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _highestRole;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
gaseous-server/Classes/Auth/Models/RemoveLoginViewModel.cs
Normal file
10
gaseous-server/Classes/Auth/Models/RemoveLoginViewModel.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
namespace Authentication;
|
||||||
|
|
||||||
|
public class RemoveLoginViewModel
|
||||||
|
{
|
||||||
|
public string LoginProvider { get; set; }
|
||||||
|
public string ProviderKey { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Authentication
|
||||||
|
{
|
||||||
|
public class SecurityProfileViewModel
|
||||||
|
{
|
||||||
|
public AgeRestrictionItem AgeRestrictionPolicy { get; set; } = new AgeRestrictionItem{
|
||||||
|
MaximumAgeRestriction = gaseous_server.Classes.Metadata.AgeGroups.AgeRestrictionGroupings.Adult,
|
||||||
|
IncludeUnrated = true
|
||||||
|
};
|
||||||
|
|
||||||
|
public class AgeRestrictionItem
|
||||||
|
{
|
||||||
|
public gaseous_server.Classes.Metadata.AgeGroups.AgeRestrictionGroupings MaximumAgeRestriction { get; set; }
|
||||||
|
public bool IncludeUnrated { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
gaseous-server/Classes/Auth/Models/SendCodeViewModel.cs
Normal file
17
gaseous-server/Classes/Auth/Models/SendCodeViewModel.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
|
||||||
|
namespace Authentication;
|
||||||
|
|
||||||
|
public class SendCodeViewModel
|
||||||
|
{
|
||||||
|
public string SelectedProvider { get; set; }
|
||||||
|
|
||||||
|
public ICollection<SelectListItem> Providers { get; set; }
|
||||||
|
|
||||||
|
public string ReturnUrl { get; set; }
|
||||||
|
|
||||||
|
public bool RememberMe { get; set; }
|
||||||
|
}
|
||||||
20
gaseous-server/Classes/Auth/Models/SetPasswordViewModel.cs
Normal file
20
gaseous-server/Classes/Auth/Models/SetPasswordViewModel.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Authentication;
|
||||||
|
|
||||||
|
public class SetPasswordViewModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
|
||||||
|
[DataType(DataType.Password)]
|
||||||
|
[Display(Name = "New password")]
|
||||||
|
public string NewPassword { get; set; }
|
||||||
|
|
||||||
|
[DataType(DataType.Password)]
|
||||||
|
[Display(Name = "Confirm new password")]
|
||||||
|
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
|
||||||
|
public string ConfirmPassword { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Authentication;
|
||||||
|
|
||||||
|
public class UseRecoveryCodeViewModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public string Code { get; set; }
|
||||||
|
|
||||||
|
public string ReturnUrl { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Authentication;
|
||||||
|
|
||||||
|
public class UserPreferenceViewModel
|
||||||
|
{
|
||||||
|
public string Setting { get; set; }
|
||||||
|
|
||||||
|
public string Value { get; set; }
|
||||||
|
}
|
||||||
47
gaseous-server/Classes/Auth/Models/UserViewModel.cs
Normal file
47
gaseous-server/Classes/Auth/Models/UserViewModel.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
namespace Authentication
|
||||||
|
{
|
||||||
|
public class UserViewModel
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string EmailAddress { get; set; }
|
||||||
|
public bool LockoutEnabled { get; set; }
|
||||||
|
public DateTimeOffset? LockoutEnd { get; set; }
|
||||||
|
public List<string> Roles { get; set; }
|
||||||
|
public SecurityProfileViewModel SecurityProfile { get; set; }
|
||||||
|
public string HighestRole {
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string _highestRole = "";
|
||||||
|
foreach (string role in Roles)
|
||||||
|
{
|
||||||
|
switch (role)
|
||||||
|
{
|
||||||
|
case "Admin":
|
||||||
|
// there is no higher
|
||||||
|
_highestRole = role;
|
||||||
|
break;
|
||||||
|
case "Gamer":
|
||||||
|
// only one high is Admin, so check for that
|
||||||
|
if (_highestRole != "Admin")
|
||||||
|
{
|
||||||
|
_highestRole = role;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Player":
|
||||||
|
// make sure _highestRole isn't already set to Gamer or Admin
|
||||||
|
if (_highestRole != "Admin" && _highestRole != "Gamer")
|
||||||
|
{
|
||||||
|
_highestRole = role;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_highestRole = "Player";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _highestRole;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Authentication;
|
||||||
|
|
||||||
|
public class VerifyAuthenticatorCodeViewModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public string Code { get; set; }
|
||||||
|
|
||||||
|
public string ReturnUrl { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Remember this browser?")]
|
||||||
|
public bool RememberBrowser { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Remember me?")]
|
||||||
|
public bool RememberMe { get; set; }
|
||||||
|
}
|
||||||
23
gaseous-server/Classes/Auth/Models/VerifyCodeViewModel.cs
Normal file
23
gaseous-server/Classes/Auth/Models/VerifyCodeViewModel.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Authentication;
|
||||||
|
|
||||||
|
public class VerifyCodeViewModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public string Provider { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string Code { get; set; }
|
||||||
|
|
||||||
|
public string ReturnUrl { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Remember this browser?")]
|
||||||
|
public bool RememberBrowser { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Remember me?")]
|
||||||
|
public bool RememberMe { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Authentication;
|
||||||
|
|
||||||
|
public class VerifyPhoneNumberViewModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public string Code { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[Phone]
|
||||||
|
[Display(Name = "Phone number")]
|
||||||
|
public string PhoneNumber { get; set; }
|
||||||
|
}
|
||||||
112
gaseous-server/Classes/Bios.cs
Normal file
112
gaseous-server/Classes/Bios.cs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace gaseous_server.Classes
|
||||||
|
{
|
||||||
|
public class Bios
|
||||||
|
{
|
||||||
|
public Bios()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Models.PlatformMapping.PlatformMapItem? BiosHashSignatureLookup(string MD5)
|
||||||
|
{
|
||||||
|
foreach (Models.PlatformMapping.PlatformMapItem platformMapping in Models.PlatformMapping.PlatformMap)
|
||||||
|
{
|
||||||
|
if (platformMapping.Bios != null)
|
||||||
|
{
|
||||||
|
foreach (Models.PlatformMapping.PlatformMapItem.EmulatorBiosItem emulatorBiosItem in platformMapping.Bios)
|
||||||
|
{
|
||||||
|
if (emulatorBiosItem.hash.ToLower() == MD5.ToLower())
|
||||||
|
{
|
||||||
|
return platformMapping;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<BiosItem> GetBios()
|
||||||
|
{
|
||||||
|
return BuildBiosList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<BiosItem> GetBios(long PlatformId, bool HideUnavailable)
|
||||||
|
{
|
||||||
|
List<BiosItem> biosItems = new List<BiosItem>();
|
||||||
|
foreach (BiosItem biosItem in BuildBiosList())
|
||||||
|
{
|
||||||
|
if (biosItem.platformid == PlatformId)
|
||||||
|
{
|
||||||
|
if (HideUnavailable == true)
|
||||||
|
{
|
||||||
|
if (biosItem.Available == true)
|
||||||
|
{
|
||||||
|
biosItems.Add(biosItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
biosItems.Add(biosItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return biosItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<BiosItem> BuildBiosList()
|
||||||
|
{
|
||||||
|
List<BiosItem> biosItems = new List<BiosItem>();
|
||||||
|
|
||||||
|
foreach (Models.PlatformMapping.PlatformMapItem platformMapping in Models.PlatformMapping.PlatformMap)
|
||||||
|
{
|
||||||
|
if (platformMapping.Bios != null)
|
||||||
|
{
|
||||||
|
IGDB.Models.Platform platform = Metadata.Platforms.GetPlatform(platformMapping.IGDBId);
|
||||||
|
|
||||||
|
foreach (Models.PlatformMapping.PlatformMapItem.EmulatorBiosItem emulatorBios in platformMapping.Bios)
|
||||||
|
{
|
||||||
|
BiosItem biosItem = new BiosItem
|
||||||
|
{
|
||||||
|
platformid = platformMapping.IGDBId,
|
||||||
|
platformslug = platform.Slug,
|
||||||
|
platformname = platform.Name,
|
||||||
|
description = emulatorBios.description,
|
||||||
|
filename = emulatorBios.filename,
|
||||||
|
hash = emulatorBios.hash.ToLower()
|
||||||
|
};
|
||||||
|
biosItems.Add(biosItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return biosItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BiosItem : Models.PlatformMapping.PlatformMapItem.EmulatorBiosItem
|
||||||
|
{
|
||||||
|
public long platformid { get; set; }
|
||||||
|
public string platformslug { get; set; }
|
||||||
|
public string platformname { get; set; }
|
||||||
|
public string biosPath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(Config.LibraryConfiguration.LibraryBIOSDirectory, platformslug, base.filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool Available {
|
||||||
|
get
|
||||||
|
{
|
||||||
|
bool fileExists = File.Exists(biosPath);
|
||||||
|
return fileExists;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
874
gaseous-server/Classes/Collections.cs
Normal file
874
gaseous-server/Classes/Collections.cs
Normal file
@@ -0,0 +1,874 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using Authentication;
|
||||||
|
using gaseous_server.Classes.Metadata;
|
||||||
|
using gaseous_server.Controllers;
|
||||||
|
using gaseous_server.Models;
|
||||||
|
using IGDB.Models;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using SharpCompress.Common;
|
||||||
|
|
||||||
|
namespace gaseous_server.Classes
|
||||||
|
{
|
||||||
|
public class Collections
|
||||||
|
{
|
||||||
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||||
|
|
||||||
|
public Collections(
|
||||||
|
UserManager<ApplicationUser> userManager,
|
||||||
|
SignInManager<ApplicationUser> signInManager)
|
||||||
|
{
|
||||||
|
_userManager = userManager;
|
||||||
|
_signInManager = signInManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<CollectionItem> GetCollections() {
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "SELECT * FROM RomCollections ORDER BY `Name`";
|
||||||
|
|
||||||
|
DataTable data = db.ExecuteCMD(sql);
|
||||||
|
|
||||||
|
List<CollectionItem> collectionItems = new List<CollectionItem>();
|
||||||
|
|
||||||
|
foreach(DataRow row in data.Rows) {
|
||||||
|
collectionItems.Add(BuildCollectionItem(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
return collectionItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CollectionItem GetCollection(long Id) {
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "SELECT * FROM RomCollections WHERE Id = @id ORDER BY `Name`";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("id", Id);
|
||||||
|
DataTable romDT = db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
if (romDT.Rows.Count > 0)
|
||||||
|
{
|
||||||
|
DataRow row = romDT.Rows[0];
|
||||||
|
CollectionItem collectionItem = BuildCollectionItem(row);
|
||||||
|
|
||||||
|
return collectionItem;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Unknown Collection Id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CollectionItem NewCollection(CollectionItem item)
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "INSERT INTO RomCollections (`Name`, Description, Platforms, Genres, Players, PlayerPerspectives, Themes, MinimumRating, MaximumRating, MaximumRomsPerPlatform, MaximumBytesPerPlatform, MaximumCollectionSizeInBytes, FolderStructure, IncludeBIOSFiles, ArchiveType, AlwaysInclude, BuiltStatus) VALUES (@name, @description, @platforms, @genres, @players, @playerperspectives, @themes, @minimumrating, @maximumrating, @maximumromsperplatform, @maximumbytesperplatform, @maximumcollectionsizeinbytes, @folderstructure, @includebiosfiles, @archivetype, @alwaysinclude, @builtstatus); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("name", item.Name);
|
||||||
|
dbDict.Add("description", item.Description);
|
||||||
|
dbDict.Add("platforms", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Platforms, new List<long>())));
|
||||||
|
dbDict.Add("genres", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Genres, new List<long>())));
|
||||||
|
dbDict.Add("players", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Players, new List<long>())));
|
||||||
|
dbDict.Add("playerperspectives", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.PlayerPerspectives, new List<long>())));
|
||||||
|
dbDict.Add("themes", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Themes, new List<long>())));
|
||||||
|
dbDict.Add("minimumrating", Common.ReturnValueIfNull(item.MinimumRating, -1));
|
||||||
|
dbDict.Add("maximumrating", Common.ReturnValueIfNull(item.MaximumRating, -1));
|
||||||
|
dbDict.Add("maximumromsperplatform", Common.ReturnValueIfNull(item.MaximumRomsPerPlatform, -1));
|
||||||
|
dbDict.Add("maximumbytesperplatform", Common.ReturnValueIfNull(item.MaximumBytesPerPlatform, -1));
|
||||||
|
dbDict.Add("maximumcollectionsizeinbytes", Common.ReturnValueIfNull(item.MaximumCollectionSizeInBytes, -1));
|
||||||
|
dbDict.Add("folderstructure", Common.ReturnValueIfNull(item.FolderStructure, CollectionItem.FolderStructures.Gaseous));
|
||||||
|
dbDict.Add("includebiosfiles", Common.ReturnValueIfNull(item.IncludeBIOSFiles, 0));
|
||||||
|
dbDict.Add("archivetype", Common.ReturnValueIfNull(item.ArchiveType, CollectionItem.ArchiveTypes.Zip));
|
||||||
|
dbDict.Add("alwaysinclude", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.AlwaysInclude, new List<CollectionItem.AlwaysIncludeItem>())));
|
||||||
|
dbDict.Add("builtstatus", CollectionItem.CollectionBuildStatus.WaitingForBuild);
|
||||||
|
DataTable romDT = db.ExecuteCMD(sql, dbDict);
|
||||||
|
long CollectionId = (long)romDT.Rows[0][0];
|
||||||
|
|
||||||
|
CollectionItem collectionItem = GetCollection(CollectionId);
|
||||||
|
|
||||||
|
StartCollectionItemBuild(CollectionId);
|
||||||
|
|
||||||
|
return collectionItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CollectionItem EditCollection(long Id, CollectionItem item, bool ForceRebuild = true)
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "UPDATE RomCollections SET `Name`=@name, Description=@description, Platforms=@platforms, Genres=@genres, Players=@players, PlayerPerspectives=@playerperspectives, Themes=@themes, MinimumRating=@minimumrating, MaximumRating=@maximumrating, MaximumRomsPerPlatform=@maximumromsperplatform, MaximumBytesPerPlatform=@maximumbytesperplatform, MaximumCollectionSizeInBytes=@maximumcollectionsizeinbytes, FolderStructure=@folderstructure, IncludeBIOSFiles=@includebiosfiles, ArchiveType=@archivetype, AlwaysInclude=@alwaysinclude, BuiltStatus=@builtstatus WHERE Id=@id";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("id", Id);
|
||||||
|
dbDict.Add("name", item.Name);
|
||||||
|
dbDict.Add("description", item.Description);
|
||||||
|
dbDict.Add("platforms", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Platforms, new List<long>())));
|
||||||
|
dbDict.Add("genres", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Genres, new List<long>())));
|
||||||
|
dbDict.Add("players", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Players, new List<long>())));
|
||||||
|
dbDict.Add("playerperspectives", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.PlayerPerspectives, new List<long>())));
|
||||||
|
dbDict.Add("themes", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Themes, new List<long>())));
|
||||||
|
dbDict.Add("minimumrating", Common.ReturnValueIfNull(item.MinimumRating, -1));
|
||||||
|
dbDict.Add("maximumrating", Common.ReturnValueIfNull(item.MaximumRating, -1));
|
||||||
|
dbDict.Add("maximumromsperplatform", Common.ReturnValueIfNull(item.MaximumRomsPerPlatform, -1));
|
||||||
|
dbDict.Add("maximumbytesperplatform", Common.ReturnValueIfNull(item.MaximumBytesPerPlatform, -1));
|
||||||
|
dbDict.Add("maximumcollectionsizeinbytes", Common.ReturnValueIfNull(item.MaximumCollectionSizeInBytes, -1));
|
||||||
|
dbDict.Add("folderstructure", Common.ReturnValueIfNull(item.FolderStructure, CollectionItem.FolderStructures.Gaseous));
|
||||||
|
dbDict.Add("includebiosfiles", Common.ReturnValueIfNull(item.IncludeBIOSFiles, 0));
|
||||||
|
dbDict.Add("alwaysinclude", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.AlwaysInclude, new List<CollectionItem.AlwaysIncludeItem>())));
|
||||||
|
dbDict.Add("archivetype", Common.ReturnValueIfNull(item.ArchiveType, CollectionItem.ArchiveTypes.Zip));
|
||||||
|
|
||||||
|
string CollectionZipFile = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + item.ArchiveExtension);
|
||||||
|
if (ForceRebuild == true)
|
||||||
|
{
|
||||||
|
dbDict.Add("builtstatus", CollectionItem.CollectionBuildStatus.WaitingForBuild);
|
||||||
|
if (File.Exists(CollectionZipFile))
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Collections", "Deleting existing build of collection: " + item.Name);
|
||||||
|
File.Delete(CollectionZipFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (File.Exists(CollectionZipFile))
|
||||||
|
{
|
||||||
|
dbDict.Add("builtstatus", CollectionItem.CollectionBuildStatus.Completed);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dbDict.Add("builtstatus", CollectionItem.CollectionBuildStatus.NoStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
CollectionItem collectionItem = GetCollection(Id);
|
||||||
|
|
||||||
|
if (collectionItem.BuildStatus == CollectionItem.CollectionBuildStatus.WaitingForBuild)
|
||||||
|
{
|
||||||
|
StartCollectionItemBuild(Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return collectionItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DeleteCollection(long Id)
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "DELETE FROM RomCollections WHERE Id=@id";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("id", Id);
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
string CollectionZipFile = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + ".zip");
|
||||||
|
if (File.Exists(CollectionZipFile))
|
||||||
|
{
|
||||||
|
File.Delete(CollectionZipFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void StartCollectionItemBuild(long Id)
|
||||||
|
{
|
||||||
|
CollectionItem collectionItem = GetCollection(Id);
|
||||||
|
|
||||||
|
if (collectionItem.BuildStatus != CollectionItem.CollectionBuildStatus.Building)
|
||||||
|
{
|
||||||
|
// set collection item to waitingforbuild
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "UPDATE RomCollections SET BuiltStatus=@bs WHERE Id=@id";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("id", Id);
|
||||||
|
dbDict.Add("bs", CollectionItem.CollectionBuildStatus.WaitingForBuild);
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
// start background task
|
||||||
|
ProcessQueue.QueueItem queueItem = new ProcessQueue.QueueItem(ProcessQueue.QueueItemType.CollectionCompiler, 1, false, true);
|
||||||
|
queueItem.Options = Id;
|
||||||
|
queueItem.ForceExecute();
|
||||||
|
ProcessQueue.QueueItems.Add(queueItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CollectionContents GetCollectionContent(CollectionItem collectionItem) {
|
||||||
|
List<CollectionContents.CollectionPlatformItem> collectionPlatformItems = new List<CollectionContents.CollectionPlatformItem>();
|
||||||
|
|
||||||
|
// get platforms
|
||||||
|
List<long> platformids = new List<long>();
|
||||||
|
platformids.AddRange(collectionItem.Platforms);
|
||||||
|
|
||||||
|
List<long>? DynamicPlatforms = new List<long>();
|
||||||
|
DynamicPlatforms.AddRange(collectionItem.Platforms);
|
||||||
|
|
||||||
|
List<Platform> platforms = new List<Platform>();
|
||||||
|
|
||||||
|
// add platforms with an inclusion status
|
||||||
|
foreach (CollectionItem.AlwaysIncludeItem alwaysIncludeItem in collectionItem.AlwaysInclude)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
alwaysIncludeItem.InclusionState == CollectionItem.AlwaysIncludeStatus.AlwaysInclude ||
|
||||||
|
alwaysIncludeItem.InclusionState == CollectionItem.AlwaysIncludeStatus.AlwaysExclude
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (!platformids.Contains(alwaysIncludeItem.PlatformId))
|
||||||
|
{
|
||||||
|
platformids.Add(alwaysIncludeItem.PlatformId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add dynamic platforms
|
||||||
|
if (DynamicPlatforms.Count > 0) {
|
||||||
|
foreach (long PlatformId in platformids) {
|
||||||
|
platforms.Add(Platforms.GetPlatform(PlatformId));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// get all platforms to pull from
|
||||||
|
Dictionary<string, List<Filters.FilterItem>> FilterDict = Filters.Filter(AgeGroups.AgeRestrictionGroupings.Adult, true);
|
||||||
|
List<Classes.Filters.FilterItem> filteredPlatforms = (List<Classes.Filters.FilterItem>)FilterDict["platforms"];
|
||||||
|
foreach (Filters.FilterItem filterItem in filteredPlatforms) {
|
||||||
|
platforms.Add(Platforms.GetPlatform(filterItem.Id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build collection
|
||||||
|
List<CollectionContents.CollectionPlatformItem> platformItems = new List<CollectionContents.CollectionPlatformItem>();
|
||||||
|
|
||||||
|
foreach (Platform platform in platforms) {
|
||||||
|
long TotalRomSize = 0;
|
||||||
|
long TotalGameCount = 0;
|
||||||
|
|
||||||
|
bool isDynamic = false;
|
||||||
|
if (DynamicPlatforms.Contains((long)platform.Id))
|
||||||
|
{
|
||||||
|
isDynamic = true;
|
||||||
|
}
|
||||||
|
else if (DynamicPlatforms.Count == 0)
|
||||||
|
{
|
||||||
|
isDynamic = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Game> games = new List<Game>();
|
||||||
|
if (isDynamic == true)
|
||||||
|
{
|
||||||
|
games = GamesController.GetGames("",
|
||||||
|
platform.Id.ToString(),
|
||||||
|
string.Join(",", collectionItem.Genres),
|
||||||
|
string.Join(",", collectionItem.Players),
|
||||||
|
string.Join(",", collectionItem.PlayerPerspectives),
|
||||||
|
string.Join(",", collectionItem.Themes),
|
||||||
|
collectionItem.MinimumRating,
|
||||||
|
collectionItem.MaximumRating
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionContents.CollectionPlatformItem collectionPlatformItem = new CollectionContents.CollectionPlatformItem(platform);
|
||||||
|
collectionPlatformItem.Games = new List<CollectionContents.CollectionPlatformItem.CollectionGameItem>();
|
||||||
|
|
||||||
|
// add titles with an inclusion status
|
||||||
|
foreach (CollectionItem.AlwaysIncludeItem alwaysIncludeItem in collectionItem.AlwaysInclude)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
alwaysIncludeItem.InclusionState == CollectionItem.AlwaysIncludeStatus.AlwaysInclude ||
|
||||||
|
alwaysIncludeItem.InclusionState == CollectionItem.AlwaysIncludeStatus.AlwaysExclude
|
||||||
|
) && alwaysIncludeItem.PlatformId == platform.Id
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Game AlwaysIncludeGame = Games.GetGame(alwaysIncludeItem.GameId, false, false, false);
|
||||||
|
CollectionContents.CollectionPlatformItem.CollectionGameItem gameItem = new CollectionContents.CollectionPlatformItem.CollectionGameItem(AlwaysIncludeGame);
|
||||||
|
gameItem.InclusionStatus = new CollectionItem.AlwaysIncludeItem();
|
||||||
|
gameItem.InclusionStatus.PlatformId = alwaysIncludeItem.PlatformId;
|
||||||
|
gameItem.InclusionStatus.GameId = alwaysIncludeItem.GameId;
|
||||||
|
gameItem.InclusionStatus.InclusionState = alwaysIncludeItem.InclusionState;
|
||||||
|
gameItem.Roms = Roms.GetRoms((long)gameItem.Id, (long)platform.Id).GameRomItems;
|
||||||
|
|
||||||
|
collectionPlatformItem.Games.Add(gameItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Game game in games) {
|
||||||
|
bool gameAlreadyInList = false;
|
||||||
|
foreach (CollectionContents.CollectionPlatformItem.CollectionGameItem existingGame in collectionPlatformItem.Games)
|
||||||
|
{
|
||||||
|
if (existingGame.Id == game.Id)
|
||||||
|
{
|
||||||
|
gameAlreadyInList = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameAlreadyInList == false)
|
||||||
|
{
|
||||||
|
CollectionContents.CollectionPlatformItem.CollectionGameItem collectionGameItem = new CollectionContents.CollectionPlatformItem.CollectionGameItem(game);
|
||||||
|
|
||||||
|
List<Roms.GameRomItem> gameRoms = Roms.GetRoms((long)game.Id, (long)platform.Id).GameRomItems;
|
||||||
|
|
||||||
|
bool AddGame = false;
|
||||||
|
|
||||||
|
// calculate total rom size for the game
|
||||||
|
long GameRomSize = 0;
|
||||||
|
foreach (Roms.GameRomItem gameRom in gameRoms) {
|
||||||
|
GameRomSize += (long)gameRom.Size;
|
||||||
|
}
|
||||||
|
if (collectionItem.MaximumBytesPerPlatform > 0) {
|
||||||
|
if ((TotalRomSize + GameRomSize) < collectionItem.MaximumBytesPerPlatform) {
|
||||||
|
AddGame = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddGame = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AddGame == true) {
|
||||||
|
TotalRomSize += GameRomSize;
|
||||||
|
|
||||||
|
bool AddRoms = false;
|
||||||
|
|
||||||
|
if (collectionItem.MaximumRomsPerPlatform > 0) {
|
||||||
|
if (TotalGameCount < collectionItem.MaximumRomsPerPlatform) {
|
||||||
|
AddRoms = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddRoms = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AddRoms == true) {
|
||||||
|
TotalGameCount += 1;
|
||||||
|
collectionGameItem.Roms = gameRoms;
|
||||||
|
collectionPlatformItem.Games.Add(collectionGameItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionPlatformItem.Games.Sort((x, y) => x.Name.CompareTo(y.Name));
|
||||||
|
|
||||||
|
if (collectionPlatformItem.Games.Count > 0)
|
||||||
|
{
|
||||||
|
bool AddPlatform = false;
|
||||||
|
if (collectionItem.MaximumCollectionSizeInBytes > 0)
|
||||||
|
{
|
||||||
|
if (TotalRomSize < collectionItem.MaximumCollectionSizeInBytes)
|
||||||
|
{
|
||||||
|
AddPlatform = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddPlatform = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AddPlatform == true)
|
||||||
|
{
|
||||||
|
collectionPlatformItems.Add(collectionPlatformItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionPlatformItems.Sort((x, y) => x.Name.CompareTo(y.Name));
|
||||||
|
|
||||||
|
CollectionContents collectionContents = new CollectionContents();
|
||||||
|
collectionContents.Collection = collectionPlatformItems;
|
||||||
|
|
||||||
|
return collectionContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CompileCollections(long CollectionId)
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
|
||||||
|
CollectionItem collectionItem = GetCollection(CollectionId);
|
||||||
|
if (collectionItem.BuildStatus == CollectionItem.CollectionBuildStatus.WaitingForBuild)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Collections", "Beginning build of collection: " + collectionItem.Name);
|
||||||
|
|
||||||
|
// set starting
|
||||||
|
string sql = "UPDATE RomCollections SET BuiltStatus=@bs WHERE Id=@id";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("id", collectionItem.Id);
|
||||||
|
dbDict.Add("bs", CollectionItem.CollectionBuildStatus.Building);
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
List<CollectionContents.CollectionPlatformItem> collectionPlatformItems = GetCollectionContent(collectionItem).Collection;
|
||||||
|
string ZipFilePath = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, collectionItem.Id + collectionItem.ArchiveExtension);
|
||||||
|
string ZipFileTempPath = Path.Combine(Config.LibraryConfiguration.LibraryTempDirectory, collectionItem.Id.ToString());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
// clean up if needed
|
||||||
|
if (File.Exists(ZipFilePath))
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Collections", "Deleting existing build of collection: " + collectionItem.Name);
|
||||||
|
File.Delete(ZipFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Directory.Exists(ZipFileTempPath))
|
||||||
|
{
|
||||||
|
Directory.Delete(ZipFileTempPath, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// gather collection files
|
||||||
|
Directory.CreateDirectory(ZipFileTempPath);
|
||||||
|
string ZipBiosPath = Path.Combine(ZipFileTempPath, "BIOS");
|
||||||
|
|
||||||
|
// get the games
|
||||||
|
foreach (CollectionContents.CollectionPlatformItem collectionPlatformItem in collectionPlatformItems)
|
||||||
|
{
|
||||||
|
// get platform bios files if present
|
||||||
|
if (collectionItem.IncludeBIOSFiles == true)
|
||||||
|
{
|
||||||
|
List<Bios.BiosItem> bios = Bios.GetBios(collectionPlatformItem.Id, true);
|
||||||
|
if (!Directory.Exists(ZipBiosPath)) {
|
||||||
|
Directory.CreateDirectory(ZipBiosPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Bios.BiosItem biosItem in bios)
|
||||||
|
{
|
||||||
|
if (File.Exists(biosItem.biosPath))
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Collections", "Copying BIOS file: " + biosItem.filename);
|
||||||
|
File.Copy(biosItem.biosPath, Path.Combine(ZipBiosPath, biosItem.filename), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create platform directory
|
||||||
|
string ZipPlatformPath = "";
|
||||||
|
switch (collectionItem.FolderStructure)
|
||||||
|
{
|
||||||
|
case CollectionItem.FolderStructures.Gaseous:
|
||||||
|
ZipPlatformPath = Path.Combine(ZipFileTempPath, collectionPlatformItem.Slug);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CollectionItem.FolderStructures.RetroPie:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PlatformMapping.PlatformMapItem platformMapItem = PlatformMapping.GetPlatformMap(collectionPlatformItem.Id);
|
||||||
|
ZipPlatformPath = Path.Combine(ZipFileTempPath, "roms", platformMapItem.RetroPieDirectoryName);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ZipPlatformPath = Path.Combine(ZipFileTempPath, collectionPlatformItem.Slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
if (!Directory.Exists(ZipPlatformPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(ZipPlatformPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (CollectionContents.CollectionPlatformItem.CollectionGameItem collectionGameItem in collectionPlatformItem.Games)
|
||||||
|
{
|
||||||
|
bool includeGame = false;
|
||||||
|
if (collectionGameItem.InclusionStatus == null)
|
||||||
|
{
|
||||||
|
includeGame = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (collectionGameItem.InclusionStatus.InclusionState == CollectionItem.AlwaysIncludeStatus.AlwaysInclude)
|
||||||
|
{
|
||||||
|
includeGame = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeGame == true)
|
||||||
|
{
|
||||||
|
string ZipGamePath = "";
|
||||||
|
switch (collectionItem.FolderStructure)
|
||||||
|
{
|
||||||
|
case CollectionItem.FolderStructures.Gaseous:
|
||||||
|
// create game directory
|
||||||
|
ZipGamePath = Path.Combine(ZipPlatformPath, collectionGameItem.Slug);
|
||||||
|
if (!Directory.Exists(ZipGamePath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(ZipGamePath);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CollectionItem.FolderStructures.RetroPie:
|
||||||
|
ZipGamePath = ZipPlatformPath;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy in roms
|
||||||
|
foreach (Roms.GameRomItem gameRomItem in collectionGameItem.Roms)
|
||||||
|
{
|
||||||
|
if (File.Exists(gameRomItem.Path))
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Collections", "Copying ROM: " + gameRomItem.Name);
|
||||||
|
File.Copy(gameRomItem.Path, Path.Combine(ZipGamePath, gameRomItem.Name), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compress to zip
|
||||||
|
Logging.Log(Logging.LogType.Information, "Collections", "Compressing collection");
|
||||||
|
switch(collectionItem.ArchiveType)
|
||||||
|
{
|
||||||
|
case CollectionItem.ArchiveTypes.Zip:
|
||||||
|
ZipFile.CreateFromDirectory(ZipFileTempPath, ZipFilePath, CompressionLevel.SmallestSize, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CollectionItem.ArchiveTypes.RAR:
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CollectionItem.ArchiveTypes.SevenZip:
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
if (Directory.Exists(ZipFileTempPath))
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Collections", "Cleaning up");
|
||||||
|
Directory.Delete(ZipFileTempPath, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set completed
|
||||||
|
dbDict["bs"] = CollectionItem.CollectionBuildStatus.Completed;
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// clean up
|
||||||
|
if (Directory.Exists(ZipFileTempPath))
|
||||||
|
{
|
||||||
|
Directory.Delete(ZipFileTempPath, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(ZipFilePath))
|
||||||
|
{
|
||||||
|
File.Delete(ZipFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set failed
|
||||||
|
dbDict["bs"] = CollectionItem.CollectionBuildStatus.Failed;
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Critical, "Collection Builder", "Collection building has failed", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CollectionItem BuildCollectionItem(DataRow row) {
|
||||||
|
string strPlatforms = (string)Common.ReturnValueIfNull(row["Platforms"], "[ ]");
|
||||||
|
string strGenres = (string)Common.ReturnValueIfNull(row["Genres"], "[ ]");
|
||||||
|
string strPlayers = (string)Common.ReturnValueIfNull(row["Players"], "[ ]");
|
||||||
|
string strPlayerPerspectives = (string)Common.ReturnValueIfNull(row["PlayerPerspectives"], "[ ]");
|
||||||
|
string strThemes = (string)Common.ReturnValueIfNull(row["Themes"], "[ ]");
|
||||||
|
string strAlwaysInclude = (string)Common.ReturnValueIfNull(row["AlwaysInclude"], "[ ]");
|
||||||
|
|
||||||
|
CollectionItem item = new CollectionItem();
|
||||||
|
item.Id = (long)row["Id"];
|
||||||
|
item.Name = (string)row["Name"];
|
||||||
|
item.Description = (string)row["Description"];
|
||||||
|
item.Platforms = Newtonsoft.Json.JsonConvert.DeserializeObject<List<long>>(strPlatforms);
|
||||||
|
item.Genres = Newtonsoft.Json.JsonConvert.DeserializeObject<List<long>>(strGenres);
|
||||||
|
item.Players = Newtonsoft.Json.JsonConvert.DeserializeObject<List<long>>(strPlayers);
|
||||||
|
item.PlayerPerspectives = Newtonsoft.Json.JsonConvert.DeserializeObject<List<long>>(strPlayerPerspectives);
|
||||||
|
item.Themes = Newtonsoft.Json.JsonConvert.DeserializeObject<List<long>>(strThemes);
|
||||||
|
item.MinimumRating = (int)Common.ReturnValueIfNull(row["MinimumRating"], -1);
|
||||||
|
item.MaximumRating = (int)Common.ReturnValueIfNull(row["MaximumRating"], -1);
|
||||||
|
item.MaximumRomsPerPlatform = (int)Common.ReturnValueIfNull(row["MaximumRomsPerPlatform"], (int)-1);
|
||||||
|
item.MaximumBytesPerPlatform = (long)Common.ReturnValueIfNull(row["MaximumBytesPerPlatform"], (long)-1);
|
||||||
|
item.MaximumCollectionSizeInBytes = (long)Common.ReturnValueIfNull(row["MaximumCollectionSizeInBytes"], (long)-1);
|
||||||
|
item.FolderStructure = (CollectionItem.FolderStructures)(int)Common.ReturnValueIfNull(row["FolderStructure"], 0);
|
||||||
|
item.IncludeBIOSFiles = (bool)row["IncludeBIOSFiles"];
|
||||||
|
item.ArchiveType = (CollectionItem.ArchiveTypes)(int)Common.ReturnValueIfNull(row["ArchiveType"], 0);
|
||||||
|
item.AlwaysInclude = Newtonsoft.Json.JsonConvert.DeserializeObject<List<CollectionItem.AlwaysIncludeItem>>(strAlwaysInclude);
|
||||||
|
item.BuildStatus = (CollectionItem.CollectionBuildStatus)(int)Common.ReturnValueIfNull(row["BuiltStatus"], 0);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CollectionItem
|
||||||
|
{
|
||||||
|
public CollectionItem()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public long Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public List<long>? Platforms { get; set; }
|
||||||
|
public List<long>? Genres { get; set; }
|
||||||
|
public List<long>? Players { get; set; }
|
||||||
|
public List<long>? PlayerPerspectives { get; set; }
|
||||||
|
public List<long>? Themes { get; set; }
|
||||||
|
public int MinimumRating { get; set; }
|
||||||
|
public int MaximumRating { get; set; }
|
||||||
|
public int? MaximumRomsPerPlatform { get; set; }
|
||||||
|
public long? MaximumBytesPerPlatform { get; set; }
|
||||||
|
public long? MaximumCollectionSizeInBytes { get; set; }
|
||||||
|
public FolderStructures FolderStructure { get; set; } = FolderStructures.Gaseous;
|
||||||
|
public bool IncludeBIOSFiles { get; set; } = true;
|
||||||
|
public ArchiveTypes ArchiveType { get; set; } = CollectionItem.ArchiveTypes.Zip;
|
||||||
|
public string ArchiveExtension
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (ArchiveType != null)
|
||||||
|
{
|
||||||
|
switch (ArchiveType)
|
||||||
|
{
|
||||||
|
case ArchiveTypes.Zip:
|
||||||
|
default:
|
||||||
|
return ".zip";
|
||||||
|
|
||||||
|
case ArchiveTypes.RAR:
|
||||||
|
return ".rar";
|
||||||
|
|
||||||
|
case ArchiveTypes.SevenZip:
|
||||||
|
return ".7z";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ".zip";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public List<AlwaysIncludeItem> AlwaysInclude { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public CollectionBuildStatus BuildStatus
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_BuildStatus == CollectionBuildStatus.Completed)
|
||||||
|
{
|
||||||
|
if (File.Exists(Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + ArchiveExtension)))
|
||||||
|
{
|
||||||
|
return CollectionBuildStatus.Completed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return CollectionBuildStatus.NoStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _BuildStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_BuildStatus = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private CollectionBuildStatus _BuildStatus { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public long CollectionBuiltSizeBytes
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (BuildStatus == CollectionBuildStatus.Completed)
|
||||||
|
{
|
||||||
|
string ZipFilePath = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + ArchiveExtension);
|
||||||
|
if (File.Exists(ZipFilePath))
|
||||||
|
{
|
||||||
|
FileInfo fi = new FileInfo(ZipFilePath);
|
||||||
|
return fi.Length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CollectionBuildStatus
|
||||||
|
{
|
||||||
|
NoStatus = 0,
|
||||||
|
WaitingForBuild = 1,
|
||||||
|
Building = 2,
|
||||||
|
Completed = 3,
|
||||||
|
Failed = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FolderStructures
|
||||||
|
{
|
||||||
|
Gaseous = 0,
|
||||||
|
RetroPie = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ArchiveTypes
|
||||||
|
{
|
||||||
|
Zip = 0,
|
||||||
|
RAR = 1,
|
||||||
|
SevenZip = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AlwaysIncludeItem
|
||||||
|
{
|
||||||
|
public long PlatformId { get; set; }
|
||||||
|
public long GameId { get; set; }
|
||||||
|
public AlwaysIncludeStatus InclusionState { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AlwaysIncludeStatus
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
AlwaysInclude = 1,
|
||||||
|
AlwaysExclude = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CollectionContents {
|
||||||
|
[JsonIgnore]
|
||||||
|
public List<CollectionPlatformItem> Collection { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public long CollectionProjectedSizeBytes
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
long CollectionSize = 0;
|
||||||
|
|
||||||
|
List<CollectionPlatformItem> collectionPlatformItems = new List<CollectionPlatformItem>();
|
||||||
|
|
||||||
|
if (Collection != null)
|
||||||
|
{
|
||||||
|
collectionPlatformItems = Collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (CollectionPlatformItem platformItem in collectionPlatformItems)
|
||||||
|
{
|
||||||
|
CollectionSize += platformItem.RomSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CollectionSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CollectionPlatformItem {
|
||||||
|
public CollectionPlatformItem(IGDB.Models.Platform platform) {
|
||||||
|
string[] PropertyWhitelist = new string[] { "Id", "Name", "Slug" };
|
||||||
|
|
||||||
|
PropertyInfo[] srcProperties = typeof(IGDB.Models.Platform).GetProperties();
|
||||||
|
PropertyInfo[] dstProperties = typeof(CollectionPlatformItem).GetProperties();
|
||||||
|
foreach (PropertyInfo srcProperty in srcProperties) {
|
||||||
|
if (PropertyWhitelist.Contains<string>(srcProperty.Name))
|
||||||
|
{
|
||||||
|
foreach (PropertyInfo dstProperty in dstProperties)
|
||||||
|
{
|
||||||
|
if (srcProperty.Name == dstProperty.Name)
|
||||||
|
{
|
||||||
|
dstProperty.SetValue(this, srcProperty.GetValue(platform));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Slug { get; set; }
|
||||||
|
|
||||||
|
public List<CollectionGameItem> Games { get; set; }
|
||||||
|
|
||||||
|
public int RomCount {
|
||||||
|
get {
|
||||||
|
int Counter = 0;
|
||||||
|
foreach (CollectionGameItem Game in Games) {
|
||||||
|
Counter += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Counter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long RomSize {
|
||||||
|
get {
|
||||||
|
long Size = 0;
|
||||||
|
foreach (CollectionGameItem Game in Games) {
|
||||||
|
foreach (Roms.GameRomItem Rom in Game.Roms) {
|
||||||
|
Size += (long)Rom.Size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CollectionGameItem {
|
||||||
|
public CollectionGameItem(IGDB.Models.Game game) {
|
||||||
|
string[] PropertyWhitelist = new string[] { "Id", "Name", "Slug", "Cover" };
|
||||||
|
PropertyInfo[] srcProperties = typeof(IGDB.Models.Game).GetProperties();
|
||||||
|
PropertyInfo[] dstProperties = typeof(CollectionPlatformItem.CollectionGameItem).GetProperties();
|
||||||
|
foreach (PropertyInfo srcProperty in srcProperties) {
|
||||||
|
if (PropertyWhitelist.Contains<string>(srcProperty.Name))
|
||||||
|
{
|
||||||
|
foreach (PropertyInfo dstProperty in dstProperties)
|
||||||
|
{
|
||||||
|
if (srcProperty.Name == dstProperty.Name)
|
||||||
|
{
|
||||||
|
if (srcProperty.GetValue(game) != null) {
|
||||||
|
string compareName = srcProperty.PropertyType.Name.ToLower().Split("`")[0];
|
||||||
|
switch(compareName) {
|
||||||
|
case "identityorvalue":
|
||||||
|
string newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(srcProperty.GetValue(game));
|
||||||
|
Dictionary<string, object> newDict = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(newObjectValue);
|
||||||
|
dstProperty.SetValue(this, newDict["Id"]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dstProperty.SetValue(this, srcProperty.GetValue(game));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Slug { get; set; }
|
||||||
|
public long Cover { get; set;}
|
||||||
|
public IGDB.Models.Cover CoverItem
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
IGDB.Models.Cover cover = Covers.GetCover(Cover, Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory, "Games", Slug), false);
|
||||||
|
|
||||||
|
return cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CollectionItem.AlwaysIncludeItem InclusionStatus { get; set; }
|
||||||
|
|
||||||
|
public List<Roms.GameRomItem> Roms { get; set; }
|
||||||
|
|
||||||
|
public long RomSize {
|
||||||
|
get {
|
||||||
|
long Size = 0;
|
||||||
|
foreach (Roms.GameRomItem Rom in Roms) {
|
||||||
|
Size += (long)Rom.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
using System;
|
using System.Collections.Concurrent;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Reflection;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace gaseous_tools
|
namespace gaseous_server.Classes
|
||||||
{
|
{
|
||||||
public class Common
|
public static class Common
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns IfNullValue if the ObjectToCheck is null
|
/// Returns IfNullValue if the ObjectToCheck is null
|
||||||
@@ -13,7 +15,7 @@ namespace gaseous_tools
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
static public object ReturnValueIfNull(object? ObjectToCheck, object IfNullValue)
|
static public object ReturnValueIfNull(object? ObjectToCheck, object IfNullValue)
|
||||||
{
|
{
|
||||||
if (ObjectToCheck == null)
|
if (ObjectToCheck == null || ObjectToCheck == System.DBNull.Value)
|
||||||
{
|
{
|
||||||
return IfNullValue;
|
return IfNullValue;
|
||||||
} else
|
} else
|
||||||
@@ -61,7 +63,7 @@ namespace gaseous_tools
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _md5hash;
|
return _md5hash.ToLower();
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
@@ -73,7 +75,7 @@ namespace gaseous_tools
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _sha1hash;
|
return _sha1hash.ToLower();
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
@@ -99,6 +101,49 @@ namespace gaseous_tools
|
|||||||
}
|
}
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string[] SkippableFiles = {
|
||||||
|
".DS_STORE",
|
||||||
|
"desktop.ini"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string NormalizePath(string path)
|
||||||
|
{
|
||||||
|
return Path.GetFullPath(new Uri(path).LocalPath)
|
||||||
|
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetDescription(this Enum value)
|
||||||
|
{
|
||||||
|
return ((DescriptionAttribute)Attribute.GetCustomAttribute(
|
||||||
|
value.GetType().GetFields(BindingFlags.Public | BindingFlags.Static)
|
||||||
|
.Single(x => x.GetValue(null).Equals(value)),
|
||||||
|
typeof(DescriptionAttribute)))?.Description ?? value.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a way to set contextual data that flows with the call and
|
||||||
|
/// async context of a test or invocation.
|
||||||
|
/// </summary>
|
||||||
|
public static class CallContext
|
||||||
|
{
|
||||||
|
static ConcurrentDictionary<string, AsyncLocal<object>> state = new ConcurrentDictionary<string, AsyncLocal<object>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores a given object and associates it with the specified name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name with which to associate the new item in the call context.</param>
|
||||||
|
/// <param name="data">The object to store in the call context.</param>
|
||||||
|
public static void SetData(string name, object data) =>
|
||||||
|
state.GetOrAdd(name, _ => new AsyncLocal<object>()).Value = data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves an object with the specified name from the <see cref="CallContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the item in the call context.</param>
|
||||||
|
/// <returns>The object in the call context associated with the specified name, or <see langword="null"/> if not found.</returns>
|
||||||
|
public static object GetData(string name) =>
|
||||||
|
state.TryGetValue(name, out AsyncLocal<object> data) ? data.Value : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using Google.Protobuf.WellKnownTypes;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
|
using gaseous_server.Classes.Metadata;
|
||||||
|
|
||||||
namespace gaseous_tools
|
namespace gaseous_server.Classes
|
||||||
{
|
{
|
||||||
public static class Config
|
public static class Config
|
||||||
{
|
{
|
||||||
@@ -34,6 +34,14 @@ namespace gaseous_tools
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string PlatformMappingFile
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".gaseous-server", "platformmap.json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static ConfigFile.Database DatabaseConfiguration
|
public static ConfigFile.Database DatabaseConfiguration
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -50,6 +58,14 @@ namespace gaseous_tools
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ConfigFile.MetadataAPI MetadataConfiguration
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _config.MetadataConfiguration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static ConfigFile.IGDB IGDB
|
public static ConfigFile.IGDB IGDB
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -74,7 +90,9 @@ namespace gaseous_tools
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
string logPathName = Path.Combine(LogPath, "Log " + DateTime.Now.ToUniversalTime().ToString("yyyyMMdd") + ".txt");
|
string logFileExtension = "txt";
|
||||||
|
|
||||||
|
string logPathName = Path.Combine(LogPath, "Server Log " + DateTime.Now.ToUniversalTime().ToString("yyyyMMdd") + "." + logFileExtension);
|
||||||
return logPathName;
|
return logPathName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,7 +191,7 @@ namespace gaseous_tools
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
string sql = "SELECT * FROM Settings WHERE Setting = @SettingName";
|
string sql = "SELECT Value FROM Settings WHERE Setting = @SettingName";
|
||||||
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
dbDict.Add("SettingName", SettingName);
|
dbDict.Add("SettingName", SettingName);
|
||||||
dbDict.Add("Value", DefaultValue);
|
dbDict.Add("Value", DefaultValue);
|
||||||
@@ -238,6 +256,8 @@ namespace gaseous_tools
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public Library LibraryConfiguration = new Library();
|
public Library LibraryConfiguration = new Library();
|
||||||
|
|
||||||
|
public MetadataAPI MetadataConfiguration = new MetadataAPI();
|
||||||
|
|
||||||
public IGDB IGDBConfiguration = new IGDB();
|
public IGDB IGDBConfiguration = new IGDB();
|
||||||
|
|
||||||
public Logging LoggingConfiguration = new Logging();
|
public Logging LoggingConfiguration = new Logging();
|
||||||
@@ -303,6 +323,16 @@ namespace gaseous_tools
|
|||||||
return dbConnString;
|
return dbConnString;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string ConnectionStringNoDatabase
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string dbConnString = "server=" + HostName + ";port=" + Port + ";userid=" + UserName + ";password=" + Password + ";";
|
||||||
|
return dbConnString;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Library
|
public class Library
|
||||||
@@ -327,11 +357,43 @@ namespace gaseous_tools
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string LibraryDataDirectory
|
public string LibraryImportErrorDirectory
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return Path.Combine(LibraryRootDirectory, "Library");
|
return Path.Combine(LibraryRootDirectory, "Import Errors");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LibraryImportDuplicatesDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(LibraryImportErrorDirectory, "Duplicates");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LibraryImportGeneralErrorDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(LibraryImportErrorDirectory, "Error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LibraryBIOSDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(LibraryRootDirectory, "BIOS");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LibraryUploadDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(LibraryRootDirectory, "Upload");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,6 +405,30 @@ namespace gaseous_tools
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string LibraryTempDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(LibraryRootDirectory, "Temp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LibraryCollectionsDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(LibraryRootDirectory, "Collections");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LibraryMediaGroupDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(LibraryRootDirectory, "Media Groups");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string LibraryMetadataDirectory_Platform(Platform platform)
|
public string LibraryMetadataDirectory_Platform(Platform platform)
|
||||||
{
|
{
|
||||||
string MetadataPath = Path.Combine(LibraryMetadataDirectory, "Platforms", platform.Slug);
|
string MetadataPath = Path.Combine(LibraryMetadataDirectory, "Platforms", platform.Slug);
|
||||||
@@ -372,25 +458,83 @@ namespace gaseous_tools
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string LibrarySignatureImportDirectory_TOSEC
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Path.Combine(LibrarySignatureImportDirectory, "TOSEC");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(LibraryDataDirectory)) { Directory.CreateDirectory(LibraryDataDirectory); }
|
if (!Directory.Exists(LibraryBIOSDirectory)) { Directory.CreateDirectory(LibraryBIOSDirectory); }
|
||||||
|
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(LibraryCollectionsDirectory)) { Directory.CreateDirectory(LibraryCollectionsDirectory); }
|
||||||
if (!Directory.Exists(LibrarySignatureImportDirectory)) { Directory.CreateDirectory(LibrarySignatureImportDirectory); }
|
if (!Directory.Exists(LibrarySignatureImportDirectory)) { Directory.CreateDirectory(LibrarySignatureImportDirectory); }
|
||||||
if (!Directory.Exists(LibrarySignatureImportDirectory_TOSEC)) { Directory.CreateDirectory(LibrarySignatureImportDirectory_TOSEC); }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class MetadataAPI
|
||||||
|
{
|
||||||
|
private static HasheousClient.Models.MetadataModel.MetadataSources _MetadataSource
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("metadatasource")))
|
||||||
|
{
|
||||||
|
return (HasheousClient.Models.MetadataModel.MetadataSources)Enum.Parse(typeof(HasheousClient.Models.MetadataModel.MetadataSources), Environment.GetEnvironmentVariable("metadatasource"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return HasheousClient.Models.MetadataModel.MetadataSources.IGDB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HasheousClient.Models.MetadataModel.SignatureSources _SignatureSource
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("signaturesource")))
|
||||||
|
{
|
||||||
|
return (HasheousClient.Models.MetadataModel.SignatureSources)Enum.Parse(typeof(HasheousClient.Models.MetadataModel.SignatureSources), Environment.GetEnvironmentVariable("signaturesource"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return HasheousClient.Models.MetadataModel.SignatureSources.LocalOnly;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int _MaxLibraryScanWorkers
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string _HasheousHost
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("hasheoushoust")))
|
||||||
|
{
|
||||||
|
return Environment.GetEnvironmentVariable("hasheoushoust");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "https://hasheous.org/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HasheousClient.Models.MetadataModel.MetadataSources MetadataSource = _MetadataSource;
|
||||||
|
|
||||||
|
public HasheousClient.Models.MetadataModel.SignatureSources SignatureSource = _SignatureSource;
|
||||||
|
|
||||||
|
public int MaxLibraryScanWorkers = _MaxLibraryScanWorkers;
|
||||||
|
|
||||||
|
public string HasheousHost = _HasheousHost;
|
||||||
|
}
|
||||||
|
|
||||||
public class IGDB
|
public class IGDB
|
||||||
{
|
{
|
||||||
private static string _DefaultIGDBClientId
|
private static string _DefaultIGDBClientId
|
||||||
@@ -431,13 +575,10 @@ namespace gaseous_tools
|
|||||||
{
|
{
|
||||||
public bool DebugLogging = false;
|
public bool DebugLogging = false;
|
||||||
|
|
||||||
public LoggingFormat LogFormat = Logging.LoggingFormat.Json;
|
// log retention in days
|
||||||
|
public int LogRetention = 7;
|
||||||
|
|
||||||
public enum LoggingFormat
|
public bool AlwaysLogToDisk = false;
|
||||||
{
|
|
||||||
Json,
|
|
||||||
Text
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
476
gaseous-server/Classes/Database.cs
Normal file
476
gaseous-server/Classes/Database.cs
Normal file
@@ -0,0 +1,476 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using MySqlConnector;
|
||||||
|
|
||||||
|
namespace gaseous_server.Classes
|
||||||
|
{
|
||||||
|
public class Database
|
||||||
|
{
|
||||||
|
public Database()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Database(databaseType Type, string ConnectionString)
|
||||||
|
{
|
||||||
|
_ConnectorType = Type;
|
||||||
|
_ConnectionString = ConnectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum databaseType
|
||||||
|
{
|
||||||
|
MySql
|
||||||
|
}
|
||||||
|
|
||||||
|
string _ConnectionString = "";
|
||||||
|
|
||||||
|
public string ConnectionString
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _ConnectionString;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_ConnectionString = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
databaseType? _ConnectorType = null;
|
||||||
|
|
||||||
|
public databaseType? ConnectorType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _ConnectorType;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_ConnectorType = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitDB()
|
||||||
|
{
|
||||||
|
// load resources
|
||||||
|
var assembly = Assembly.GetExecutingAssembly();
|
||||||
|
|
||||||
|
switch (_ConnectorType)
|
||||||
|
{
|
||||||
|
case databaseType.MySql:
|
||||||
|
// 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 + "`;";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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';";
|
||||||
|
DataTable SchemaVersionPresent = ExecuteCMD(sql, dbDict);
|
||||||
|
if (SchemaVersionPresent.Rows.Count == 0)
|
||||||
|
{
|
||||||
|
// no schema table present - create 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);";
|
||||||
|
ExecuteCMD(sql, dbDict);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1000; i < 10000; i++)
|
||||||
|
{
|
||||||
|
string resourceName = "gaseous_server.Support.Database.MySQL.gaseous-" + i + ".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 script
|
||||||
|
sql = "SELECT schema_version FROM schema_version;";
|
||||||
|
dbDict = new Dictionary<string, object>();
|
||||||
|
DataTable SchemaVersion = ExecuteCMD(sql, dbDict);
|
||||||
|
if (SchemaVersion.Rows.Count == 0)
|
||||||
|
{
|
||||||
|
// something is broken here... where's the table?
|
||||||
|
Logging.Log(Logging.LogType.Critical, "Database", "Schema table missing! This shouldn't happen!");
|
||||||
|
throw new Exception("schema_version table is missing!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int SchemaVer = (int)SchemaVersion.Rows[0][0];
|
||||||
|
Logging.Log(Logging.LogType.Information, "Database", "Schema version is " + SchemaVer);
|
||||||
|
if (SchemaVer < i)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// run pre-upgrade code
|
||||||
|
DatabaseMigration.PreUpgradeScript(i, _ConnectorType);
|
||||||
|
|
||||||
|
// apply schema!
|
||||||
|
Logging.Log(Logging.LogType.Information, "Database", "Updating schema to version " + i);
|
||||||
|
ExecuteCMD(dbScript, dbDict, 180);
|
||||||
|
|
||||||
|
sql = "UPDATE schema_version SET schema_version=@schemaver";
|
||||||
|
dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("schemaver", i);
|
||||||
|
ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
// run post-upgrade code
|
||||||
|
DatabaseMigration.PostUpgradeScript(i, _ConnectorType);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Critical, "Database", "Schema upgrade failed! Unable to continue.", ex);
|
||||||
|
System.Environment.Exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Logging.Log(Logging.LogType.Information, "Database", "Database setup complete");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataTable ExecuteCMD(string Command)
|
||||||
|
{
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
return _ExecuteCMD(Command, dbDict, 30, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataTable ExecuteCMD(string Command, Dictionary<string, object> Parameters)
|
||||||
|
{
|
||||||
|
return _ExecuteCMD(Command, Parameters, 30, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataTable ExecuteCMD(string Command, Dictionary<string, object> Parameters, int Timeout = 30, string ConnectionString = "")
|
||||||
|
{
|
||||||
|
return _ExecuteCMD(Command, Parameters, Timeout, ConnectionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Dictionary<string, object>> ExecuteCMDDict(string Command)
|
||||||
|
{
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
return _ExecuteCMDDict(Command, dbDict, 30, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Dictionary<string, object>> ExecuteCMDDict(string Command, Dictionary<string, object> Parameters)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
// convert datatable to dictionary
|
||||||
|
List<Dictionary<string, object?>> rows = new List<Dictionary<string, object?>>();
|
||||||
|
|
||||||
|
foreach (DataRow dataRow in dataTable.Rows)
|
||||||
|
{
|
||||||
|
Dictionary<string, object?> row = new Dictionary<string, object?>();
|
||||||
|
for (int i = 0; i < dataRow.Table.Columns.Count; i++)
|
||||||
|
{
|
||||||
|
string columnName = dataRow.Table.Columns[i].ColumnName;
|
||||||
|
if (dataRow[i] == System.DBNull.Value)
|
||||||
|
{
|
||||||
|
row.Add(columnName, null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
row.Add(columnName, dataRow[i].ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rows.Add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataTable _ExecuteCMD(string Command, Dictionary<string, object> Parameters, int Timeout = 30, string ConnectionString = "")
|
||||||
|
{
|
||||||
|
if (ConnectionString == "") { ConnectionString = _ConnectionString; }
|
||||||
|
switch (_ConnectorType)
|
||||||
|
{
|
||||||
|
case databaseType.MySql:
|
||||||
|
MySQLServerConnector conn = new MySQLServerConnector(ConnectionString);
|
||||||
|
return (DataTable)conn.ExecCMD(Command, Parameters, Timeout);
|
||||||
|
default:
|
||||||
|
return new DataTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ExecuteNonQuery(string Command)
|
||||||
|
{
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
return _ExecuteNonQuery(Command, dbDict, 30, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ExecuteNonQuery(string Command, Dictionary<string, object> Parameters)
|
||||||
|
{
|
||||||
|
return _ExecuteNonQuery(Command, Parameters, 30, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ExecuteNonQuery(string Command, Dictionary<string, object> Parameters, int Timeout = 30, string ConnectionString = "")
|
||||||
|
{
|
||||||
|
return _ExecuteNonQuery(Command, Parameters, Timeout, ConnectionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _ExecuteNonQuery(string Command, Dictionary<string, object> Parameters, int Timeout = 30, string ConnectionString = "")
|
||||||
|
{
|
||||||
|
if (ConnectionString == "") { ConnectionString = _ConnectionString; }
|
||||||
|
switch (_ConnectorType)
|
||||||
|
{
|
||||||
|
case databaseType.MySql:
|
||||||
|
MySQLServerConnector conn = new MySQLServerConnector(ConnectionString);
|
||||||
|
int retVal = conn.ExecNonQuery(Command, Parameters, Timeout);
|
||||||
|
return retVal;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExecuteTransactionCMD(List<SQLTransactionItem> CommandList, int Timeout = 60)
|
||||||
|
{
|
||||||
|
object conn;
|
||||||
|
switch (_ConnectorType)
|
||||||
|
{
|
||||||
|
case databaseType.MySql:
|
||||||
|
{
|
||||||
|
var commands = new List<Dictionary<string, object>>();
|
||||||
|
foreach (SQLTransactionItem CommandItem in CommandList)
|
||||||
|
{
|
||||||
|
var nCmd = new Dictionary<string, object>();
|
||||||
|
nCmd.Add("sql", CommandItem.SQLCommand);
|
||||||
|
nCmd.Add("values", CommandItem.Parameters);
|
||||||
|
commands.Add(nCmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
conn = new MySQLServerConnector(_ConnectionString);
|
||||||
|
((MySQLServerConnector)conn).TransactionExecCMD(commands, Timeout);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetDatabaseSchemaVersion()
|
||||||
|
{
|
||||||
|
switch (_ConnectorType)
|
||||||
|
{
|
||||||
|
case databaseType.MySql:
|
||||||
|
string sql = "SELECT schema_version FROM schema_version;";
|
||||||
|
DataTable SchemaVersion = ExecuteCMD(sql);
|
||||||
|
if (SchemaVersion.Rows.Count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (int)SchemaVersion.Rows[0][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TestConnection()
|
||||||
|
{
|
||||||
|
switch (_ConnectorType)
|
||||||
|
{
|
||||||
|
case databaseType.MySql:
|
||||||
|
MySQLServerConnector conn = new MySQLServerConnector(_ConnectionString);
|
||||||
|
return conn.TestConnection();
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SQLTransactionItem
|
||||||
|
{
|
||||||
|
public SQLTransactionItem()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public SQLTransactionItem(string SQLCommand, Dictionary<string, object> Parameters)
|
||||||
|
{
|
||||||
|
this.SQLCommand = SQLCommand;
|
||||||
|
this.Parameters = Parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? SQLCommand;
|
||||||
|
public Dictionary<string, object>? Parameters = new Dictionary<string, object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class MySQLServerConnector
|
||||||
|
{
|
||||||
|
private string DBConn = "";
|
||||||
|
|
||||||
|
public MySQLServerConnector(string ConnectionString)
|
||||||
|
{
|
||||||
|
DBConn = ConnectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataTable ExecCMD(string SQL, Dictionary<string, object> Parameters, int Timeout)
|
||||||
|
{
|
||||||
|
DataTable RetTable = new DataTable();
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Debug, "Database", "Connecting to database", null, true);
|
||||||
|
using (MySqlConnection conn = new MySqlConnection(DBConn))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
|
||||||
|
MySqlCommand cmd = new MySqlCommand
|
||||||
|
{
|
||||||
|
Connection = conn,
|
||||||
|
CommandText = SQL,
|
||||||
|
CommandTimeout = Timeout
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (string Parameter in Parameters.Keys)
|
||||||
|
{
|
||||||
|
cmd.Parameters.AddWithValue(Parameter, Parameters[Parameter]);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Debug, "Database", "Executing sql: '" + SQL + "'", null, true);
|
||||||
|
if (Parameters.Count > 0)
|
||||||
|
{
|
||||||
|
string dictValues = string.Join(";", Parameters.Select(x => string.Join("=", x.Key, x.Value)));
|
||||||
|
Logging.Log(Logging.LogType.Debug, "Database", "Parameters: " + dictValues, null, true);
|
||||||
|
}
|
||||||
|
RetTable.Load(cmd.ExecuteReader());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logging.Log(Logging.LogType.Critical, "Database", "Error while executing '" + SQL + "'", ex);
|
||||||
|
Trace.WriteLine("Error executing " + SQL);
|
||||||
|
Trace.WriteLine("Full exception: " + ex.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Debug, "Database", "Closing database connection", null, true);
|
||||||
|
conn.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return RetTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ExecNonQuery(string SQL, Dictionary< string, object> Parameters, int Timeout)
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Debug, "Database", "Connecting to database", null, true);
|
||||||
|
using (MySqlConnection conn = new MySqlConnection(DBConn))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
|
||||||
|
MySqlCommand cmd = new MySqlCommand
|
||||||
|
{
|
||||||
|
Connection = conn,
|
||||||
|
CommandText = SQL,
|
||||||
|
CommandTimeout = Timeout
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (string Parameter in Parameters.Keys)
|
||||||
|
{
|
||||||
|
cmd.Parameters.AddWithValue(Parameter, Parameters[Parameter]);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Debug, "Database", "Executing sql: '" + SQL + "'", null, true);
|
||||||
|
if (Parameters.Count > 0)
|
||||||
|
{
|
||||||
|
string dictValues = string.Join(";", Parameters.Select(x => string.Join("=", x.Key, x.Value)));
|
||||||
|
Logging.Log(Logging.LogType.Debug, "Database", "Parameters: " + dictValues, null, true);
|
||||||
|
}
|
||||||
|
result = cmd.ExecuteNonQuery();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logging.Log(Logging.LogType.Critical, "Database", "Error while executing '" + SQL + "'", ex);
|
||||||
|
Trace.WriteLine("Error executing " + SQL);
|
||||||
|
Trace.WriteLine("Full exception: " + ex.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Debug, "Database", "Closing database connection", null, true);
|
||||||
|
conn.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TransactionExecCMD(List<Dictionary<string, object>> Parameters, int Timeout)
|
||||||
|
{
|
||||||
|
using (MySqlConnection conn = new MySqlConnection(DBConn))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
var command = conn.CreateCommand();
|
||||||
|
MySqlTransaction transaction;
|
||||||
|
transaction = conn.BeginTransaction();
|
||||||
|
command.Connection = conn;
|
||||||
|
command.Transaction = transaction;
|
||||||
|
foreach (Dictionary<string, object> Parameter in Parameters)
|
||||||
|
{
|
||||||
|
var cmd = buildcommand(conn, Parameter["sql"].ToString(), (Dictionary<string, object>)Parameter["values"], Timeout);
|
||||||
|
cmd.Transaction = transaction;
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
conn.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MySqlCommand buildcommand(MySqlConnection Conn, string SQL, Dictionary<string, object> Parameters, int Timeout)
|
||||||
|
{
|
||||||
|
var cmd = new MySqlCommand();
|
||||||
|
cmd.Connection = Conn;
|
||||||
|
cmd.CommandText = SQL;
|
||||||
|
cmd.CommandTimeout = Timeout;
|
||||||
|
{
|
||||||
|
var withBlock = cmd.Parameters;
|
||||||
|
if (Parameters is object)
|
||||||
|
{
|
||||||
|
if (Parameters.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (string param in Parameters.Keys)
|
||||||
|
withBlock.AddWithValue(param, Parameters[param]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TestConnection()
|
||||||
|
{
|
||||||
|
using (MySqlConnection conn = new MySqlConnection(DBConn))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
conn.Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
163
gaseous-server/Classes/DatabaseMigration.cs
Normal file
163
gaseous-server/Classes/DatabaseMigration.cs
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data;
|
||||||
|
|
||||||
|
namespace gaseous_server.Classes
|
||||||
|
{
|
||||||
|
public static class DatabaseMigration
|
||||||
|
{
|
||||||
|
public static List<int> BackgroundUpgradeTargetSchemaVersions = new List<int>();
|
||||||
|
|
||||||
|
public static void PreUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PostUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType)
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
switch(DatabaseType)
|
||||||
|
{
|
||||||
|
case Database.databaseType.MySql:
|
||||||
|
switch (TargetSchemaVersion)
|
||||||
|
{
|
||||||
|
case 1002:
|
||||||
|
// this is a safe background task
|
||||||
|
BackgroundUpgradeTargetSchemaVersions.Add(1002);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1004:
|
||||||
|
// needs to run on start up
|
||||||
|
|
||||||
|
// copy root path to new libraries format
|
||||||
|
string oldRoot = Path.Combine(Config.LibraryConfiguration.LibraryRootDirectory, "Library");
|
||||||
|
string sql = "INSERT INTO GameLibraries (Name, Path, DefaultLibrary, DefaultPlatform) VALUES (@name, @path, @defaultlibrary, @defaultplatform); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
|
||||||
|
dbDict.Add("name", "Default");
|
||||||
|
dbDict.Add("path", oldRoot);
|
||||||
|
dbDict.Add("defaultlibrary", 1);
|
||||||
|
dbDict.Add("defaultplatform", 0);
|
||||||
|
DataTable data = db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
// apply the new library id to the existing roms
|
||||||
|
sql = "UPDATE Games_Roms SET LibraryId=@libraryid;";
|
||||||
|
dbDict.Clear();
|
||||||
|
dbDict.Add("libraryid", data.Rows[0][0]);
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UpgradeScriptBackgroundTasks()
|
||||||
|
{
|
||||||
|
foreach (int TargetSchemaVersion in BackgroundUpgradeTargetSchemaVersions)
|
||||||
|
{
|
||||||
|
switch (TargetSchemaVersion)
|
||||||
|
{
|
||||||
|
case 1002:
|
||||||
|
MySql_1002_MigrateMetadataVersion();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void MySql_1002_MigrateMetadataVersion() {
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
// update signature roms to v2
|
||||||
|
sql = "SELECT Id, Flags, Attributes, IngestorVersion FROM Signatures_Roms WHERE IngestorVersion = 1";
|
||||||
|
DataTable data = db.ExecuteCMD(sql);
|
||||||
|
if (data.Rows.Count > 0)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Signature Ingestor - Database Update", "Updating " + data.Rows.Count + " database entries");
|
||||||
|
int Counter = 0;
|
||||||
|
int LastCounterCheck = 0;
|
||||||
|
foreach (DataRow row in data.Rows)
|
||||||
|
{
|
||||||
|
List<string> Flags = Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>>((string)Common.ReturnValueIfNull(row["flags"], "[]"));
|
||||||
|
List<KeyValuePair<string, object>> Attributes = new List<KeyValuePair<string, object>>();
|
||||||
|
foreach (string Flag in Flags)
|
||||||
|
{
|
||||||
|
if (Flag.StartsWith("a"))
|
||||||
|
{
|
||||||
|
Attributes.Add(
|
||||||
|
new KeyValuePair<string, object>(
|
||||||
|
"a",
|
||||||
|
Flag
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string[] FlagCompare = Flag.Split(' ');
|
||||||
|
switch (FlagCompare[0].Trim().ToLower())
|
||||||
|
{
|
||||||
|
case "cr":
|
||||||
|
// cracked
|
||||||
|
case "f":
|
||||||
|
// fixed
|
||||||
|
case "h":
|
||||||
|
// hacked
|
||||||
|
case "m":
|
||||||
|
// modified
|
||||||
|
case "p":
|
||||||
|
// pirated
|
||||||
|
case "t":
|
||||||
|
// trained
|
||||||
|
case "tr":
|
||||||
|
// translated
|
||||||
|
case "o":
|
||||||
|
// overdump
|
||||||
|
case "u":
|
||||||
|
// underdump
|
||||||
|
case "v":
|
||||||
|
// virus
|
||||||
|
case "b":
|
||||||
|
// bad dump
|
||||||
|
case "a":
|
||||||
|
// alternate
|
||||||
|
case "!":
|
||||||
|
// known verified dump
|
||||||
|
// -------------------
|
||||||
|
string shavedToken = Flag.Substring(FlagCompare[0].Trim().Length).Trim();
|
||||||
|
Attributes.Add(new KeyValuePair<string, object>(
|
||||||
|
FlagCompare[0].Trim().ToLower(),
|
||||||
|
shavedToken
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string AttributesJson;
|
||||||
|
if (Attributes.Count > 0)
|
||||||
|
{
|
||||||
|
AttributesJson = Newtonsoft.Json.JsonConvert.SerializeObject(Attributes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AttributesJson = "[]";
|
||||||
|
}
|
||||||
|
|
||||||
|
string updateSQL = "UPDATE Signatures_Roms SET Attributes=@attributes, IngestorVersion=2 WHERE Id=@id";
|
||||||
|
dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("attributes", AttributesJson);
|
||||||
|
dbDict.Add("id", (int)row["Id"]);
|
||||||
|
db.ExecuteCMD(updateSQL, dbDict);
|
||||||
|
|
||||||
|
if ((Counter - LastCounterCheck) > 10)
|
||||||
|
{
|
||||||
|
LastCounterCheck = Counter;
|
||||||
|
Logging.Log(Logging.LogType.Information, "Signature Ingestor - Database Update", "Updating " + Counter + " / " + data.Rows.Count + " database entries");
|
||||||
|
}
|
||||||
|
Counter += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
373
gaseous-server/Classes/FileSignature.cs
Normal file
373
gaseous-server/Classes/FileSignature.cs
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using HasheousClient.Models;
|
||||||
|
using NuGet.Common;
|
||||||
|
using SevenZip;
|
||||||
|
using SharpCompress.Archives;
|
||||||
|
using SharpCompress.Archives.Rar;
|
||||||
|
using SharpCompress.Archives.Zip;
|
||||||
|
using SharpCompress.Common;
|
||||||
|
|
||||||
|
namespace gaseous_server.Classes
|
||||||
|
{
|
||||||
|
public class FileSignature
|
||||||
|
{
|
||||||
|
public gaseous_server.Models.Signatures_Games GetFileSignature(GameLibrary.LibraryItem library, Common.hashObject hash, FileInfo fi, string GameFileImportPath)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Get Signature", "Getting signature for file: " + GameFileImportPath);
|
||||||
|
gaseous_server.Models.Signatures_Games discoveredSignature = new gaseous_server.Models.Signatures_Games();
|
||||||
|
discoveredSignature = _GetFileSignature(hash, fi.Name, fi.Extension, fi.Length, GameFileImportPath, false);
|
||||||
|
|
||||||
|
string[] CompressionExts = { ".zip", ".rar", ".7z" };
|
||||||
|
string ImportedFileExtension = Path.GetExtension(GameFileImportPath);
|
||||||
|
|
||||||
|
if (CompressionExts.Contains(ImportedFileExtension) && (fi.Length < 1073741824))
|
||||||
|
{
|
||||||
|
// file is a zip and less than 1 GiB
|
||||||
|
// extract the zip file and search the contents
|
||||||
|
|
||||||
|
string ExtractPath = Path.Combine(Config.LibraryConfiguration.LibraryTempDirectory, library.Id.ToString(), Path.GetRandomFileName());
|
||||||
|
Logging.Log(Logging.LogType.Information, "Get Signature", "Decompressing " + GameFileImportPath + " to " + ExtractPath + " examine contents");
|
||||||
|
if (!Directory.Exists(ExtractPath)) { Directory.CreateDirectory(ExtractPath); }
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch(ImportedFileExtension)
|
||||||
|
{
|
||||||
|
case ".zip":
|
||||||
|
Logging.Log(Logging.LogType.Information, "Get Signature", "Decompressing using zip");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var archive = SharpCompress.Archives.Zip.ZipArchive.Open(GameFileImportPath))
|
||||||
|
{
|
||||||
|
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Get Signature", "Extracting file: " + entry.Key);
|
||||||
|
entry.WriteToDirectory(ExtractPath, new ExtractionOptions()
|
||||||
|
{
|
||||||
|
ExtractFullPath = true,
|
||||||
|
Overwrite = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception zipEx)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Get Signature", "Unzip error", zipEx);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ".rar":
|
||||||
|
Logging.Log(Logging.LogType.Information, "Get Signature", "Decompressing using rar");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var archive = RarArchive.Open(GameFileImportPath))
|
||||||
|
{
|
||||||
|
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Get Signature", "Extracting file: " + entry.Key);
|
||||||
|
entry.WriteToDirectory(ExtractPath, new ExtractionOptions()
|
||||||
|
{
|
||||||
|
ExtractFullPath = true,
|
||||||
|
Overwrite = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception zipEx)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Get Signature", "Unrar error", zipEx);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ".7z":
|
||||||
|
Logging.Log(Logging.LogType.Information, "Get Signature", "Decompressing using 7z");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var archive = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(GameFileImportPath))
|
||||||
|
{
|
||||||
|
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Get Signature", "Extracting file: " + entry.Key);
|
||||||
|
entry.WriteToDirectory(ExtractPath, new ExtractionOptions()
|
||||||
|
{
|
||||||
|
ExtractFullPath = true,
|
||||||
|
Overwrite = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception zipEx)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Get Signature", "7z error", zipEx);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Information, "Get Signature", "Processing decompressed files for signature matches");
|
||||||
|
// loop through contents until we find the first signature match
|
||||||
|
List<ArchiveData> archiveFiles = new List<ArchiveData>();
|
||||||
|
bool signatureFound = false;
|
||||||
|
foreach (string file in Directory.GetFiles(ExtractPath, "*.*", SearchOption.AllDirectories))
|
||||||
|
{
|
||||||
|
if (File.Exists(file))
|
||||||
|
{
|
||||||
|
FileInfo zfi = new FileInfo(file);
|
||||||
|
Common.hashObject zhash = new Common.hashObject(file);
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Information, "Get Signature", "Checking signature of decompressed file " + file);
|
||||||
|
|
||||||
|
if (zfi != null)
|
||||||
|
{
|
||||||
|
ArchiveData archiveData = new ArchiveData{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
gaseous_server.Models.Signatures_Games zDiscoveredSignature = _GetFileSignature(zhash, zfi.Name, zfi.Extension, zfi.Length, file, true);
|
||||||
|
zDiscoveredSignature.Rom.Name = Path.ChangeExtension(zDiscoveredSignature.Rom.Name, ImportedFileExtension);
|
||||||
|
|
||||||
|
if (zDiscoveredSignature.Score > discoveredSignature.Score)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
zDiscoveredSignature.Rom.SignatureSource == gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType.MAMEArcade ||
|
||||||
|
zDiscoveredSignature.Rom.SignatureSource == gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType.MAMEMess
|
||||||
|
)
|
||||||
|
{
|
||||||
|
zDiscoveredSignature.Rom.Name = zDiscoveredSignature.Game.Description + ImportedFileExtension;
|
||||||
|
}
|
||||||
|
zDiscoveredSignature.Rom.Crc = discoveredSignature.Rom.Crc;
|
||||||
|
zDiscoveredSignature.Rom.Md5 = discoveredSignature.Rom.Md5;
|
||||||
|
zDiscoveredSignature.Rom.Sha1 = discoveredSignature.Rom.Sha1;
|
||||||
|
zDiscoveredSignature.Rom.Size = discoveredSignature.Rom.Size;
|
||||||
|
discoveredSignature = zDiscoveredSignature;
|
||||||
|
|
||||||
|
signatureFound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
discoveredSignature.Rom.Attributes.Add(new KeyValuePair<string, object>(
|
||||||
|
"ZipContents", Newtonsoft.Json.JsonConvert.SerializeObject(archiveFiles)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Critical, "Get Signature", "Error processing compressed file: " + GameFileImportPath, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return discoveredSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
private gaseous_server.Models.Signatures_Games _GetFileSignature(Common.hashObject hash, string ImageName, string ImageExtension, long ImageSize, string GameFileImportPath, bool IsInZip)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
|
||||||
|
// do database search first
|
||||||
|
gaseous_server.Models.Signatures_Games? dbSignature = _GetFileSignatureFromDatabase(hash, ImageName, ImageExtension, ImageSize, GameFileImportPath);
|
||||||
|
|
||||||
|
if (dbSignature != null)
|
||||||
|
{
|
||||||
|
// local signature found
|
||||||
|
Logging.Log(Logging.LogType.Information, "Import Game", "Signature found in local database for game: " + dbSignature.Game.Name);
|
||||||
|
discoveredSignature = dbSignature;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// no local signature attempt to pull from Hasheous
|
||||||
|
dbSignature = _GetFileSignatureFromHasheous(hash, ImageName, ImageExtension, ImageSize, GameFileImportPath);
|
||||||
|
|
||||||
|
if (dbSignature != null)
|
||||||
|
{
|
||||||
|
// signature retrieved from Hasheous
|
||||||
|
Logging.Log(Logging.LogType.Information, "Import Game", "Signature retrieved from Hasheous for game: " + dbSignature.Game.Name);
|
||||||
|
|
||||||
|
discoveredSignature = dbSignature;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Information, "Import Game", " Determined import file as: " + discoveredSignature.Game.Name + " (" + discoveredSignature.Game.Year + ") " + discoveredSignature.Game.System);
|
||||||
|
Logging.Log(Logging.LogType.Information, "Import Game", " Platform determined to be: " + discoveredSignature.Flags.IGDBPlatformName + " (" + discoveredSignature.Flags.IGDBPlatformId + ")");
|
||||||
|
|
||||||
|
return discoveredSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
private gaseous_server.Models.Signatures_Games? _GetFileSignatureFromDatabase(Common.hashObject hash, string ImageName, string ImageExtension, long ImageSize, string GameFileImportPath)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Get Signature", "Checking local database for MD5: " + hash.md5hash);
|
||||||
|
|
||||||
|
// check 1: do we have a signature for it?
|
||||||
|
gaseous_server.Classes.SignatureManagement sc = new SignatureManagement();
|
||||||
|
List<gaseous_server.Models.Signatures_Games> signatures = sc.GetSignature(hash.md5hash);
|
||||||
|
if (signatures == null || signatures.Count == 0)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Get Signature", "Checking local database for SHA1: " + hash.sha1hash);
|
||||||
|
|
||||||
|
// no md5 signature found - try sha1
|
||||||
|
signatures = sc.GetSignature("", hash.sha1hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
gaseous_server.Models.Signatures_Games? discoveredSignature = null;
|
||||||
|
if (signatures.Count == 1)
|
||||||
|
{
|
||||||
|
// only 1 signature found!
|
||||||
|
discoveredSignature = signatures.ElementAt(0);
|
||||||
|
|
||||||
|
return discoveredSignature;
|
||||||
|
}
|
||||||
|
else if (signatures.Count > 1)
|
||||||
|
{
|
||||||
|
// more than one signature found - find one with highest score
|
||||||
|
// start with first returned element
|
||||||
|
discoveredSignature = signatures.First();
|
||||||
|
foreach (gaseous_server.Models.Signatures_Games Sig in signatures)
|
||||||
|
{
|
||||||
|
if (Sig.Score > discoveredSignature.Score)
|
||||||
|
{
|
||||||
|
discoveredSignature = Sig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return discoveredSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private gaseous_server.Models.Signatures_Games? _GetFileSignatureFromHasheous(Common.hashObject hash, string ImageName, string ImageExtension, long ImageSize, string GameFileImportPath)
|
||||||
|
{
|
||||||
|
// check if hasheous is enabled, and if so use it's signature database
|
||||||
|
if (Config.MetadataConfiguration.SignatureSource == HasheousClient.Models.MetadataModel.SignatureSources.Hasheous)
|
||||||
|
{
|
||||||
|
HasheousClient.Hasheous hasheous = new HasheousClient.Hasheous();
|
||||||
|
SignatureLookupItem? HasheousResult = hasheous.RetrieveFromHasheousAsync(new HashLookupModel{
|
||||||
|
MD5 = hash.md5hash,
|
||||||
|
SHA1 = hash.sha1hash
|
||||||
|
});
|
||||||
|
|
||||||
|
if (HasheousResult != null)
|
||||||
|
{
|
||||||
|
if (HasheousResult.Signature != null)
|
||||||
|
{
|
||||||
|
gaseous_server.Models.Signatures_Games signature = new Models.Signatures_Games();
|
||||||
|
signature.Game = HasheousResult.Signature.Game;
|
||||||
|
signature.Rom = HasheousResult.Signature.Rom;
|
||||||
|
|
||||||
|
if (HasheousResult.MetadataResults != null)
|
||||||
|
{
|
||||||
|
if (HasheousResult.MetadataResults.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (SignatureLookupItem.MetadataResult metadataResult in HasheousResult.MetadataResults)
|
||||||
|
{
|
||||||
|
if (metadataResult.Source == MetadataModel.MetadataSources.IGDB)
|
||||||
|
{
|
||||||
|
signature.Flags.IGDBPlatformId = (long)metadataResult.PlatformId;
|
||||||
|
signature.Flags.IGDBGameId = (long)metadataResult.GameId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private gaseous_server.Models.Signatures_Games _GetFileSignatureFromFileData(Common.hashObject hash, string ImageName, string ImageExtension, long ImageSize, string GameFileImportPath)
|
||||||
|
{
|
||||||
|
SignatureManagement signatureManagement = new SignatureManagement();
|
||||||
|
|
||||||
|
gaseous_server.Models.Signatures_Games discoveredSignature = new gaseous_server.Models.Signatures_Games();
|
||||||
|
|
||||||
|
// no signature match found - try name search
|
||||||
|
List<gaseous_server.Models.Signatures_Games> signatures = signatureManagement.GetByTosecName(ImageName);
|
||||||
|
|
||||||
|
if (signatures.Count == 1)
|
||||||
|
{
|
||||||
|
// only 1 signature found!
|
||||||
|
discoveredSignature = signatures.ElementAt(0);
|
||||||
|
|
||||||
|
return discoveredSignature;
|
||||||
|
}
|
||||||
|
else if (signatures.Count > 1)
|
||||||
|
{
|
||||||
|
// more than one signature found - find one with highest score
|
||||||
|
foreach (gaseous_server.Models.Signatures_Games Sig in signatures)
|
||||||
|
{
|
||||||
|
if (Sig.Score > discoveredSignature.Score)
|
||||||
|
{
|
||||||
|
discoveredSignature = Sig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return discoveredSignature;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// still no search - try alternate method
|
||||||
|
gaseous_server.Models.Signatures_Games.GameItem gi = new gaseous_server.Models.Signatures_Games.GameItem();
|
||||||
|
gaseous_server.Models.Signatures_Games.RomItem ri = new gaseous_server.Models.Signatures_Games.RomItem();
|
||||||
|
|
||||||
|
discoveredSignature.Game = gi;
|
||||||
|
discoveredSignature.Rom = ri;
|
||||||
|
|
||||||
|
// game title is the file name without the extension or path
|
||||||
|
gi.Name = Path.GetFileNameWithoutExtension(GameFileImportPath);
|
||||||
|
|
||||||
|
// remove everything after brackets - leaving (hopefully) only the name
|
||||||
|
if (gi.Name.Contains("("))
|
||||||
|
{
|
||||||
|
gi.Name = gi.Name.Substring(0, gi.Name.IndexOf("(")).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove special characters like dashes
|
||||||
|
gi.Name = gi.Name.Replace("-", "").Trim();
|
||||||
|
|
||||||
|
// get rom data
|
||||||
|
ri.Name = Path.GetFileName(GameFileImportPath);
|
||||||
|
ri.Md5 = hash.md5hash;
|
||||||
|
ri.Sha1 = hash.sha1hash;
|
||||||
|
ri.Size = ImageSize;
|
||||||
|
ri.SignatureSource = gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType.None;
|
||||||
|
|
||||||
|
return discoveredSignature;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ArchiveData
|
||||||
|
{
|
||||||
|
public string FileName { get; set; }
|
||||||
|
public string FilePath { get; set; }
|
||||||
|
public long Size { get; set; }
|
||||||
|
public string MD5 { get; set; }
|
||||||
|
public string SHA1 { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
143
gaseous-server/Classes/Filters.cs
Normal file
143
gaseous-server/Classes/Filters.cs
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
using System.Data;
|
||||||
|
using System.Reflection.Metadata.Ecma335;
|
||||||
|
using gaseous_server.Classes.Metadata;
|
||||||
|
using IGDB.Models;
|
||||||
|
|
||||||
|
namespace gaseous_server.Classes
|
||||||
|
{
|
||||||
|
public class Filters
|
||||||
|
{
|
||||||
|
public static Dictionary<string, List<FilterItem>> Filter(Metadata.AgeGroups.AgeRestrictionGroupings MaximumAgeRestriction, bool IncludeUnrated)
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
|
||||||
|
Dictionary<string, List<FilterItem>> FilterSet = new Dictionary<string, List<FilterItem>>();
|
||||||
|
|
||||||
|
// platforms
|
||||||
|
List<FilterItem> platforms = new List<FilterItem>();
|
||||||
|
|
||||||
|
string ageRestriction_Platform = "AgeGroup.AgeGroupId <= " + (int)MaximumAgeRestriction;
|
||||||
|
string ageRestriction_Generic = "view_Games.AgeGroupId <= " + (int)MaximumAgeRestriction;
|
||||||
|
if (IncludeUnrated == true)
|
||||||
|
{
|
||||||
|
ageRestriction_Platform += " OR AgeGroup.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`;";
|
||||||
|
|
||||||
|
DataTable dbResponse = db.ExecuteCMD(sql);
|
||||||
|
|
||||||
|
foreach (DataRow dr in dbResponse.Rows)
|
||||||
|
{
|
||||||
|
FilterItem platformItem = new FilterItem(dr);
|
||||||
|
platforms.Add(platformItem);
|
||||||
|
|
||||||
|
}
|
||||||
|
FilterSet.Add("platforms", platforms);
|
||||||
|
|
||||||
|
// genres
|
||||||
|
List<FilterItem> genres = new List<FilterItem>();
|
||||||
|
dbResponse = GetGenericFilterItem(db, "Genre", ageRestriction_Platform);
|
||||||
|
|
||||||
|
foreach (DataRow dr in dbResponse.Rows)
|
||||||
|
{
|
||||||
|
FilterItem genreItem = new FilterItem(dr);
|
||||||
|
genres.Add(genreItem);
|
||||||
|
}
|
||||||
|
FilterSet.Add("genres", genres);
|
||||||
|
|
||||||
|
// game modes
|
||||||
|
List<FilterItem> gameModes = new List<FilterItem>();
|
||||||
|
dbResponse = GetGenericFilterItem(db, "GameMode", ageRestriction_Platform);
|
||||||
|
|
||||||
|
foreach (DataRow dr in dbResponse.Rows)
|
||||||
|
{
|
||||||
|
FilterItem gameModeItem = new FilterItem(dr);
|
||||||
|
gameModes.Add(gameModeItem);
|
||||||
|
}
|
||||||
|
FilterSet.Add("gamemodes", gameModes);
|
||||||
|
|
||||||
|
// player perspectives
|
||||||
|
List<FilterItem> playerPerspectives = new List<FilterItem>();
|
||||||
|
dbResponse = GetGenericFilterItem(db, "PlayerPerspective", ageRestriction_Platform);
|
||||||
|
|
||||||
|
foreach (DataRow dr in dbResponse.Rows)
|
||||||
|
{
|
||||||
|
FilterItem playerPerspectiveItem = new FilterItem(dr);
|
||||||
|
playerPerspectives.Add(playerPerspectiveItem);
|
||||||
|
}
|
||||||
|
FilterSet.Add("playerperspectives", playerPerspectives);
|
||||||
|
|
||||||
|
// themes
|
||||||
|
List<FilterItem> themes = new List<FilterItem>();
|
||||||
|
dbResponse = GetGenericFilterItem(db, "Theme", ageRestriction_Platform);
|
||||||
|
|
||||||
|
foreach (DataRow dr in dbResponse.Rows)
|
||||||
|
{
|
||||||
|
FilterItem themeItem = new FilterItem(dr);
|
||||||
|
themes.Add(themeItem);
|
||||||
|
}
|
||||||
|
FilterSet.Add("themes", themes);
|
||||||
|
|
||||||
|
// age groups
|
||||||
|
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";
|
||||||
|
dbResponse = db.ExecuteCMD(sql);
|
||||||
|
|
||||||
|
foreach (DataRow dr in dbResponse.Rows)
|
||||||
|
{
|
||||||
|
FilterItem filterAgeGrouping = new FilterItem();
|
||||||
|
if (dr["AgeGroupId"] == DBNull.Value)
|
||||||
|
{
|
||||||
|
filterAgeGrouping.Id = (int)(long)AgeGroups.AgeRestrictionGroupings.Unclassified;
|
||||||
|
filterAgeGrouping.Name = AgeGroups.AgeRestrictionGroupings.Unclassified.ToString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int ageGroupLong = (int)dr["AgeGroupId"];
|
||||||
|
AgeGroups.AgeRestrictionGroupings ageGroup = (AgeGroups.AgeRestrictionGroupings)ageGroupLong;
|
||||||
|
filterAgeGrouping.Id = ageGroupLong;
|
||||||
|
filterAgeGrouping.Name = ageGroup.ToString();
|
||||||
|
}
|
||||||
|
filterAgeGrouping.GameCount = (int)(long)dr["GameCount"];
|
||||||
|
agegroupings.Add(filterAgeGrouping);
|
||||||
|
}
|
||||||
|
FilterSet.Add("agegroupings", agegroupings);
|
||||||
|
|
||||||
|
return FilterSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DataTable GetGenericFilterItem(Database db, string Name, string AgeRestriction)
|
||||||
|
{
|
||||||
|
//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`;";
|
||||||
|
sql = sql.Replace("<ITEMNAME>", Name);
|
||||||
|
DataTable dbResponse = db.ExecuteCMD(sql);
|
||||||
|
|
||||||
|
return dbResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FilterItem
|
||||||
|
{
|
||||||
|
public FilterItem()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilterItem(DataRow dr)
|
||||||
|
{
|
||||||
|
this.Id = (long)dr["Id"];
|
||||||
|
this.Name = (string)dr["Name"];
|
||||||
|
this.GameCount = (int)(long)dr["GameCount"];
|
||||||
|
}
|
||||||
|
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public int GameCount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
222
gaseous-server/Classes/GameLibrary.cs
Normal file
222
gaseous-server/Classes/GameLibrary.cs
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data;
|
||||||
|
using gaseous_server.Classes;
|
||||||
|
using gaseous_server.Classes.Metadata;
|
||||||
|
using IGDB.Models;
|
||||||
|
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow;
|
||||||
|
|
||||||
|
namespace gaseous_server
|
||||||
|
{
|
||||||
|
public static class GameLibrary
|
||||||
|
{
|
||||||
|
// exceptions
|
||||||
|
public class PathExists : Exception
|
||||||
|
{
|
||||||
|
public PathExists(string path) : base("The library path " + path + " already exists.")
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PathNotFound : Exception
|
||||||
|
{
|
||||||
|
public PathNotFound(string path) : base("The path " + path + " does not exist.")
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LibraryNotFound : Exception
|
||||||
|
{
|
||||||
|
public LibraryNotFound(int LibraryId) : base("Library id " + LibraryId + " does not exist.")
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CannotDeleteDefaultLibrary : Exception
|
||||||
|
{
|
||||||
|
public CannotDeleteDefaultLibrary() : base("Unable to delete the default library.")
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// code
|
||||||
|
public static LibraryItem GetDefaultLibrary
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "SELECT * FROM GameLibraries WHERE DefaultLibrary=1 LIMIT 1";
|
||||||
|
DataTable data = db.ExecuteCMD(sql);
|
||||||
|
DataRow row = data.Rows[0];
|
||||||
|
LibraryItem library = new LibraryItem((int)row["Id"], (string)row["Name"], (string)row["Path"], (long)row["DefaultPlatform"], Convert.ToBoolean((int)row["DefaultLibrary"]));
|
||||||
|
|
||||||
|
if (!Directory.Exists(library.Path))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(library.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return library;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<LibraryItem> GetLibraries
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
List<LibraryItem> libraryItems = new List<LibraryItem>();
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "SELECT * FROM GameLibraries";
|
||||||
|
DataTable data = db.ExecuteCMD(sql);
|
||||||
|
foreach (DataRow row in data.Rows)
|
||||||
|
{
|
||||||
|
LibraryItem library = new LibraryItem((int)row["Id"], (string)row["Name"], (string)row["Path"], (long)row["DefaultPlatform"], Convert.ToBoolean((int)row["DefaultLibrary"]));
|
||||||
|
libraryItems.Add(library);
|
||||||
|
|
||||||
|
if (library.IsDefaultLibrary == true)
|
||||||
|
{
|
||||||
|
// check directory exists
|
||||||
|
if (!Directory.Exists(library.Path))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(library.Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return libraryItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LibraryItem AddLibrary(string Name, string Path, long DefaultPlatformId)
|
||||||
|
{
|
||||||
|
string PathName = Common.NormalizePath(Path);
|
||||||
|
|
||||||
|
// check path isn't already in place
|
||||||
|
foreach (LibraryItem item in GetLibraries)
|
||||||
|
{
|
||||||
|
if (Common.NormalizePath(PathName) == Common.NormalizePath(item.Path))
|
||||||
|
{
|
||||||
|
// already existing path!
|
||||||
|
throw new PathExists(PathName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!System.IO.Path.Exists(PathName))
|
||||||
|
{
|
||||||
|
throw new PathNotFound(PathName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "INSERT INTO GameLibraries (Name, Path, DefaultPlatform, DefaultLibrary) VALUES (@name, @path, @defaultplatform, 0); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("name", Name);
|
||||||
|
dbDict.Add("path", PathName);
|
||||||
|
dbDict.Add("defaultplatform", DefaultPlatformId);
|
||||||
|
DataTable data = db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
int newLibraryId = (int)(long)data.Rows[0][0];
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Information, "Library Management", "Created library " + Name + " at directory " + PathName);
|
||||||
|
|
||||||
|
LibraryItem library = GetLibrary(newLibraryId);
|
||||||
|
|
||||||
|
return library;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DeleteLibrary(int LibraryId)
|
||||||
|
{
|
||||||
|
LibraryItem library = GetLibrary(LibraryId);
|
||||||
|
if (library.IsDefaultLibrary == false)
|
||||||
|
{
|
||||||
|
// check for active library scans
|
||||||
|
foreach(ProcessQueue.QueueItem item in ProcessQueue.QueueItems)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
(item.ItemType == ProcessQueue.QueueItemType.LibraryScan && item.ItemState == ProcessQueue.QueueItemState.Running) ||
|
||||||
|
(item.ItemType == ProcessQueue.QueueItemType.LibraryScanWorker && item.ItemState == ProcessQueue.QueueItemState.Running)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Library Management", "Unable to delete libraries while a library scan is running. Wait until the the library scan is completed and try again.");
|
||||||
|
throw new CannotDeleteLibraryWhileScanIsActive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "DELETE FROM Games_Roms WHERE LibraryId=@id; DELETE FROM GameLibraries WHERE Id=@id;";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("id", LibraryId);
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Information, "Library Management", "Deleted library " + library.Name + " at path " + library.Path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Library Management", "Unable to delete the default library.");
|
||||||
|
throw new CannotDeleteDefaultLibrary();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LibraryItem GetLibrary(int LibraryId)
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "SELECT * FROM GameLibraries WHERE Id=@id";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("id", LibraryId);
|
||||||
|
DataTable data = db.ExecuteCMD(sql, dbDict);
|
||||||
|
if (data.Rows.Count > 0)
|
||||||
|
{
|
||||||
|
DataRow row = data.Rows[0];
|
||||||
|
LibraryItem library = new LibraryItem((int)row["Id"], (string)row["Name"], (string)row["Path"], (long)row["DefaultPlatform"], Convert.ToBoolean((int)row["DefaultLibrary"]));
|
||||||
|
return library;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new LibraryNotFound(LibraryId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LibraryItem
|
||||||
|
{
|
||||||
|
public LibraryItem(int Id, string Name, string Path, long DefaultPlatformId, bool IsDefaultLibrary)
|
||||||
|
{
|
||||||
|
_Id = Id;
|
||||||
|
_Name = Name;
|
||||||
|
_Path = Path;
|
||||||
|
_DefaultPlatformId = DefaultPlatformId;
|
||||||
|
_IsDefaultLibrary = IsDefaultLibrary;
|
||||||
|
|
||||||
|
if (!Directory.Exists(Path))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int _Id = 0;
|
||||||
|
string _Name = "";
|
||||||
|
string _Path = "";
|
||||||
|
long _DefaultPlatformId = 0;
|
||||||
|
bool _IsDefaultLibrary = false;
|
||||||
|
|
||||||
|
public int Id => _Id;
|
||||||
|
public string Name => _Name;
|
||||||
|
public string Path => _Path;
|
||||||
|
public long DefaultPlatformId => _DefaultPlatformId;
|
||||||
|
public string? DefaultPlatformName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_DefaultPlatformId != 0)
|
||||||
|
{
|
||||||
|
Platform platform = Platforms.GetPlatform(_DefaultPlatformId);
|
||||||
|
return platform.Name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool IsDefaultLibrary => _IsDefaultLibrary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,194 +1,177 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Security.Authentication;
|
||||||
using System.Security.Policy;
|
using System.Security.Policy;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using gaseous_tools;
|
using gaseous_server.Classes.Metadata;
|
||||||
using MySqlX.XDevAPI;
|
using gaseous_server.Models;
|
||||||
using Org.BouncyCastle.Utilities.IO.Pem;
|
using IGDB.Models;
|
||||||
|
using NuGet.Common;
|
||||||
|
using NuGet.LibraryModel;
|
||||||
using static gaseous_server.Classes.Metadata.Games;
|
using static gaseous_server.Classes.Metadata.Games;
|
||||||
|
using static gaseous_server.Classes.FileSignature;
|
||||||
|
using HasheousClient.Models;
|
||||||
|
|
||||||
namespace gaseous_server.Classes
|
namespace gaseous_server.Classes
|
||||||
{
|
{
|
||||||
public class ImportGames
|
public class ImportGame : QueueItemStatus
|
||||||
{
|
{
|
||||||
public ImportGames(string ImportPath)
|
public void ProcessDirectory(string ImportPath)
|
||||||
{
|
{
|
||||||
if (Directory.Exists(ImportPath))
|
if (Directory.Exists(ImportPath))
|
||||||
{
|
{
|
||||||
string[] importContents_Files = Directory.GetFiles(ImportPath);
|
string[] importContents = Directory.GetFiles(ImportPath, "*.*", SearchOption.AllDirectories);
|
||||||
string[] importContents_Directories = Directory.GetDirectories(ImportPath);
|
|
||||||
|
Logging.Log(Logging.LogType.Information, "Import Games", "Found " + importContents.Length + " files to process in import directory: " + ImportPath);
|
||||||
|
|
||||||
// import files first
|
// import files first
|
||||||
foreach (string importContent in importContents_Files) {
|
int importCount = 1;
|
||||||
ImportGame.ImportGameFile(importContent);
|
foreach (string importContent in importContents) {
|
||||||
|
SetStatus(importCount, importContents.Length, "Importing file: " + importContent);
|
||||||
|
|
||||||
|
ImportGameFile(importContent, null);
|
||||||
|
|
||||||
|
importCount += 1;
|
||||||
}
|
}
|
||||||
|
ClearStatus();
|
||||||
|
|
||||||
|
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 class ImportGame
|
|
||||||
{
|
|
||||||
public static void ImportGameFile(string GameFileImportPath, bool IsDirectory = false, bool ForceImport = false)
|
|
||||||
{
|
{
|
||||||
Database db = new gaseous_tools.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>();
|
||||||
|
|
||||||
string[] SkippableFiles = {
|
if (Common.SkippableFiles.Contains<string>(Path.GetFileName(GameFileImportPath), StringComparer.OrdinalIgnoreCase))
|
||||||
".DS_STORE",
|
|
||||||
"desktop.ini"
|
|
||||||
};
|
|
||||||
if (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
|
||||||
{
|
{
|
||||||
//Logging.Log(Logging.LogType.Information, "Import Game", "Processing item " + GameFileImportPath);
|
FileInfo fi = new FileInfo(GameFileImportPath);
|
||||||
if (IsDirectory == false)
|
Common.hashObject hash = new Common.hashObject(GameFileImportPath);
|
||||||
{
|
|
||||||
FileInfo fi = new FileInfo(GameFileImportPath);
|
|
||||||
Common.hashObject hash = new Common.hashObject(GameFileImportPath);
|
|
||||||
|
|
||||||
// check to make sure we don't already have this file imported
|
Models.PlatformMapping.PlatformMapItem? IsBios = Classes.Bios.BiosHashSignatureLookup(hash.md5hash);
|
||||||
sql = "SELECT COUNT(Id) AS count FROM Games_Roms WHERE MD5=@md5 AND SHA1=@sha1";
|
|
||||||
dbDict.Add("md5", hash.md5hash);
|
|
||||||
dbDict.Add("sha1", hash.sha1hash);
|
|
||||||
DataTable importDB = db.ExecuteCMD(sql, dbDict);
|
|
||||||
if ((Int64)importDB.Rows[0]["count"] > 0)
|
|
||||||
{
|
|
||||||
if (!GameFileImportPath.StartsWith(Config.LibraryConfiguration.LibraryImportDirectory))
|
|
||||||
{
|
|
||||||
Logging.Log(Logging.LogType.Warning, "Import Game", " " + GameFileImportPath + " already in database - skipping");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logging.Log(Logging.LogType.Information, "Import Game", " " + GameFileImportPath + " not in database - processing");
|
|
||||||
|
|
||||||
// process as a single file
|
if (IsBios == null)
|
||||||
Models.Signatures_Games discoveredSignature = GetFileSignature(hash, fi, GameFileImportPath);
|
{
|
||||||
|
// file is a rom
|
||||||
|
// 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";
|
||||||
|
dbDict.Add("md5", hash.md5hash);
|
||||||
|
dbDict.Add("sha1", hash.sha1hash);
|
||||||
|
DataTable importDB = db.ExecuteCMD(sql, dbDict);
|
||||||
|
if ((Int64)importDB.Rows[0]["count"] > 0)
|
||||||
|
{
|
||||||
|
// import source was the import directory
|
||||||
|
if (GameFileImportPath.StartsWith(Config.LibraryConfiguration.LibraryImportDirectory))
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Import Game", " " + GameFileImportPath + " already in database - moving to " + Config.LibraryConfiguration.LibraryImportDuplicatesDirectory);
|
||||||
|
|
||||||
// get discovered platform
|
string targetPathWithFileName = GameFileImportPath.Replace(Config.LibraryConfiguration.LibraryImportDirectory, Config.LibraryConfiguration.LibraryImportDuplicatesDirectory);
|
||||||
IGDB.Models.Platform determinedPlatform = Metadata.Platforms.GetPlatform(discoveredSignature.Flags.IGDBPlatformId);
|
string targetPath = Path.GetDirectoryName(targetPathWithFileName);
|
||||||
if (determinedPlatform == null)
|
|
||||||
{
|
|
||||||
determinedPlatform = new IGDB.Models.Platform();
|
|
||||||
}
|
|
||||||
|
|
||||||
IGDB.Models.Game determinedGame = SearchForGame(discoveredSignature.Game.Name, discoveredSignature.Flags.IGDBPlatformId);
|
if (!Directory.Exists(targetPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(targetPath);
|
||||||
|
}
|
||||||
|
File.Move(GameFileImportPath, targetPathWithFileName, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// import source was the upload directory
|
||||||
|
if (GameFileImportPath.StartsWith(Config.LibraryConfiguration.LibraryUploadDirectory))
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Import Game", " " + GameFileImportPath + " already in database - skipping import");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Import Game", " " + GameFileImportPath + " not in database - processing");
|
||||||
|
|
||||||
|
FileSignature fileSignature = new FileSignature();
|
||||||
|
gaseous_server.Models.Signatures_Games discoveredSignature = fileSignature.GetFileSignature(GameLibrary.GetDefaultLibrary, hash, fi, GameFileImportPath);
|
||||||
|
|
||||||
|
// get discovered platform
|
||||||
|
IGDB.Models.Platform? determinedPlatform = null;
|
||||||
|
if (OverridePlatform == null)
|
||||||
|
{
|
||||||
|
determinedPlatform = Metadata.Platforms.GetPlatform(discoveredSignature.Flags.IGDBPlatformId);
|
||||||
|
if (determinedPlatform == null)
|
||||||
|
{
|
||||||
|
determinedPlatform = new IGDB.Models.Platform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
determinedPlatform = OverridePlatform;
|
||||||
|
discoveredSignature.Flags.IGDBPlatformId = (long)determinedPlatform.Id;
|
||||||
|
discoveredSignature.Flags.IGDBPlatformName = determinedPlatform.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
IGDB.Models.Game determinedGame = SearchForGame(discoveredSignature, discoveredSignature.Flags.IGDBPlatformId, true);
|
||||||
|
|
||||||
// add to database
|
// add to database
|
||||||
StoreROM(hash, determinedGame, determinedPlatform, discoveredSignature, GameFileImportPath);
|
StoreROM(GameLibrary.GetDefaultLibrary, hash, determinedGame, determinedPlatform, discoveredSignature, GameFileImportPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// file is a bios
|
||||||
|
if (IsBios.WebEmulator != null)
|
||||||
|
{
|
||||||
|
foreach (Classes.Bios.BiosItem biosItem in Classes.Bios.GetBios())
|
||||||
|
{
|
||||||
|
if (biosItem.Available == false && biosItem.hash == hash.md5hash)
|
||||||
|
{
|
||||||
|
string biosPath = biosItem.biosPath.Replace(biosItem.filename, "");
|
||||||
|
if (!Directory.Exists(biosPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(biosPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
File.Move(GameFileImportPath, biosItem.biosPath, true);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Models.Signatures_Games GetFileSignature(Common.hashObject hash, FileInfo fi, string GameFileImportPath)
|
public static IGDB.Models.Game SearchForGame(gaseous_server.Models.Signatures_Games Signature, long PlatformId, bool FullDownload)
|
||||||
{
|
|
||||||
// check 1: do we have a signature for it?
|
|
||||||
gaseous_server.Controllers.SignaturesController sc = new Controllers.SignaturesController();
|
|
||||||
List<Models.Signatures_Games> signatures = sc.GetSignature(hash.md5hash);
|
|
||||||
if (signatures.Count == 0)
|
|
||||||
{
|
|
||||||
// no md5 signature found - try sha1
|
|
||||||
signatures = sc.GetSignature("", hash.sha1hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
Models.Signatures_Games discoveredSignature = new Models.Signatures_Games();
|
|
||||||
if (signatures.Count == 1)
|
|
||||||
{
|
|
||||||
// only 1 signature found!
|
|
||||||
discoveredSignature = signatures.ElementAt(0);
|
|
||||||
gaseous_server.Models.PlatformMapping.GetIGDBPlatformMapping(ref discoveredSignature, fi, false);
|
|
||||||
}
|
|
||||||
else if (signatures.Count > 1)
|
|
||||||
{
|
|
||||||
// more than one signature found - find one with highest score
|
|
||||||
foreach (Models.Signatures_Games Sig in signatures)
|
|
||||||
{
|
|
||||||
if (Sig.Score > discoveredSignature.Score)
|
|
||||||
{
|
|
||||||
discoveredSignature = Sig;
|
|
||||||
gaseous_server.Models.PlatformMapping.GetIGDBPlatformMapping(ref discoveredSignature, fi, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// no signature match found - try name search
|
|
||||||
signatures = sc.GetByTosecName(fi.Name);
|
|
||||||
|
|
||||||
if (signatures.Count == 1)
|
|
||||||
{
|
|
||||||
// only 1 signature found!
|
|
||||||
discoveredSignature = signatures.ElementAt(0);
|
|
||||||
gaseous_server.Models.PlatformMapping.GetIGDBPlatformMapping(ref discoveredSignature, fi, false);
|
|
||||||
}
|
|
||||||
else if (signatures.Count > 1)
|
|
||||||
{
|
|
||||||
// more than one signature found - find one with highest score
|
|
||||||
foreach (Models.Signatures_Games Sig in signatures)
|
|
||||||
{
|
|
||||||
if (Sig.Score > discoveredSignature.Score)
|
|
||||||
{
|
|
||||||
discoveredSignature = Sig;
|
|
||||||
gaseous_server.Models.PlatformMapping.GetIGDBPlatformMapping(ref discoveredSignature, fi, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// still no search - try alternate method
|
|
||||||
Models.Signatures_Games.GameItem gi = new Models.Signatures_Games.GameItem();
|
|
||||||
Models.Signatures_Games.RomItem ri = new Models.Signatures_Games.RomItem();
|
|
||||||
|
|
||||||
discoveredSignature.Game = gi;
|
|
||||||
discoveredSignature.Rom = ri;
|
|
||||||
|
|
||||||
// game title is the file name without the extension or path
|
|
||||||
gi.Name = Path.GetFileNameWithoutExtension(GameFileImportPath);
|
|
||||||
|
|
||||||
// remove everything after brackets - leaving (hopefully) only the name
|
|
||||||
if (gi.Name.Contains("("))
|
|
||||||
{
|
|
||||||
gi.Name = gi.Name.Substring(0, gi.Name.IndexOf("("));
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove special characters like dashes
|
|
||||||
gi.Name = gi.Name.Replace("-", "");
|
|
||||||
|
|
||||||
// guess platform
|
|
||||||
gaseous_server.Models.PlatformMapping.GetIGDBPlatformMapping(ref discoveredSignature, fi, true);
|
|
||||||
|
|
||||||
// get rom data
|
|
||||||
ri.Name = Path.GetFileName(GameFileImportPath);
|
|
||||||
ri.Md5 = hash.md5hash;
|
|
||||||
ri.Sha1 = hash.sha1hash;
|
|
||||||
ri.Size = fi.Length;
|
|
||||||
ri.SignatureSource = Models.Signatures_Games.RomItem.SignatureSourceType.None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Logging.Log(Logging.LogType.Information, "Import Game", " Determined import file as: " + discoveredSignature.Game.Name + " (" + discoveredSignature.Game.Year + ") " + discoveredSignature.Game.System);
|
|
||||||
|
|
||||||
return discoveredSignature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IGDB.Models.Game SearchForGame(string GameName, long PlatformId)
|
|
||||||
{
|
{
|
||||||
|
if (Signature.Flags != null)
|
||||||
|
{
|
||||||
|
if (Signature.Flags.IGDBGameId != null && Signature.Flags.IGDBGameId != 0)
|
||||||
|
{
|
||||||
|
// game was determined elsewhere - probably a Hasheous server
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Games.GetGame(Signature.Flags.IGDBGameId, false, false, FullDownload);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Import Game", "Provided game id resulted in a failed game lookup", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// search discovered game - case insensitive exact match first
|
// search discovered game - case insensitive exact match first
|
||||||
IGDB.Models.Game determinedGame = new IGDB.Models.Game();
|
IGDB.Models.Game determinedGame = new IGDB.Models.Game();
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -204,7 +187,7 @@ namespace gaseous_server.Classes
|
|||||||
if (games.Length == 1)
|
if (games.Length == 1)
|
||||||
{
|
{
|
||||||
// exact match!
|
// exact match!
|
||||||
determinedGame = Metadata.Games.GetGame((long)games[0].Id, false, false);
|
determinedGame = Metadata.Games.GetGame((long)games[0].Id, false, false, false);
|
||||||
Logging.Log(Logging.LogType.Information, "Import Game", " IGDB game: " + determinedGame.Name);
|
Logging.Log(Logging.LogType.Information, "Import Game", " IGDB game: " + determinedGame.Name);
|
||||||
GameFound = true;
|
GameFound = true;
|
||||||
break;
|
break;
|
||||||
@@ -212,6 +195,17 @@ namespace gaseous_server.Classes
|
|||||||
else if (games.Length > 0)
|
else if (games.Length > 0)
|
||||||
{
|
{
|
||||||
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
|
||||||
|
foreach (Game game in games) {
|
||||||
|
if (game.Name == SearchCandidate) {
|
||||||
|
// found game title matches the search candidate
|
||||||
|
determinedGame = Metadata.Games.GetGame((long)games[0].Id, false, false, false);
|
||||||
|
Logging.Log(Logging.LogType.Information, "Import Game", "Found exact match!");
|
||||||
|
GameFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -277,11 +271,17 @@ namespace gaseous_server.Classes
|
|||||||
GameName = Regex.Replace(GameName, @"v(\d+\.)?(\d+\.)?(\*|\d+)$", "").Trim();
|
GameName = Regex.Replace(GameName, @"v(\d+\.)?(\d+\.)?(\*|\d+)$", "").Trim();
|
||||||
GameName = Regex.Replace(GameName, @"Rev (\d+\.)?(\d+\.)?(\*|\d+)$", "").Trim();
|
GameName = Regex.Replace(GameName, @"Rev (\d+\.)?(\d+\.)?(\*|\d+)$", "").Trim();
|
||||||
|
|
||||||
|
// assumption: no games have () in their titles so we'll remove them
|
||||||
|
int idx = GameName.IndexOf('(');
|
||||||
|
if (idx >= 0) {
|
||||||
|
GameName = GameName.Substring(0, idx);
|
||||||
|
}
|
||||||
|
|
||||||
List<string> SearchCandidates = new List<string>();
|
List<string> SearchCandidates = new List<string>();
|
||||||
SearchCandidates.Add(GameName);
|
SearchCandidates.Add(GameName.Trim());
|
||||||
if (GameName.Contains(" - "))
|
if (GameName.Contains(" - "))
|
||||||
{
|
{
|
||||||
SearchCandidates.Add(GameName.Replace(" - ", ": "));
|
SearchCandidates.Add(GameName.Replace(" - ", ": ").Trim());
|
||||||
SearchCandidates.Add(GameName.Substring(0, GameName.IndexOf(" - ")).Trim());
|
SearchCandidates.Add(GameName.Substring(0, GameName.IndexOf(" - ")).Trim());
|
||||||
}
|
}
|
||||||
if (GameName.Contains(": "))
|
if (GameName.Contains(": "))
|
||||||
@@ -294,9 +294,9 @@ namespace gaseous_server.Classes
|
|||||||
return SearchCandidates;
|
return SearchCandidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long StoreROM(Common.hashObject hash, IGDB.Models.Game determinedGame, IGDB.Models.Platform determinedPlatform, 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)
|
||||||
{
|
{
|
||||||
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
|
||||||
string sql = "";
|
string sql = "";
|
||||||
|
|
||||||
@@ -304,36 +304,39 @@ namespace gaseous_server.Classes
|
|||||||
|
|
||||||
if (UpdateId == 0)
|
if (UpdateId == 0)
|
||||||
{
|
{
|
||||||
sql = "INSERT INTO Games_Roms (PlatformId, GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Flags, RomType, RomTypeMedia, MediaLabel, Path, MetadataSource) VALUES (@platformid, @gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @flags, @romtype, @romtypemedia, @medialabel, @path, @metadatasource); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
|
sql = "INSERT INTO Games_Roms (PlatformId, GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Attributes, RomType, RomTypeMedia, MediaLabel, Path, MetadataSource, MetadataGameName, MetadataVersion, LibraryId) VALUES (@platformid, @gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @Attributes, @romtype, @romtypemedia, @medialabel, @path, @metadatasource, @metadatagamename, @metadataversion, @libraryid); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
sql = "UPDATE Games_Roms SET PlatformId=platformid, GameId=@gameid, Name=@name, Size=@size, DevelopmentStatus=@developmentstatus, Flags=@flags, RomType=@romtype, RomTypeMedia=@romtypemedia, MediaLabel=@medialabel, MetadataSource=@metadatasource WHERE Id=@id;";
|
sql = "UPDATE Games_Roms SET PlatformId=@platformid, GameId=@gameid, Name=@name, Size=@size, DevelopmentStatus=@developmentstatus, Attributes=@Attributes, RomType=@romtype, RomTypeMedia=@romtypemedia, MediaLabel=@medialabel, MetadataSource=@metadatasource, MetadataGameName=@metadatagamename, MetadataVersion=@metadataversion WHERE Id=@id;";
|
||||||
dbDict.Add("id", UpdateId);
|
dbDict.Add("id", UpdateId);
|
||||||
}
|
}
|
||||||
dbDict.Add("platformid", Common.ReturnValueIfNull(determinedPlatform.Id, 0));
|
dbDict.Add("platformid", Common.ReturnValueIfNull(determinedPlatform.Id, 0));
|
||||||
dbDict.Add("gameid", Common.ReturnValueIfNull(determinedGame.Id, 0));
|
dbDict.Add("gameid", Common.ReturnValueIfNull(determinedGame.Id, 0));
|
||||||
dbDict.Add("name", Common.ReturnValueIfNull(discoveredSignature.Rom.Name, ""));
|
dbDict.Add("name", Common.ReturnValueIfNull(discoveredSignature.Rom.Name, 0));
|
||||||
dbDict.Add("size", Common.ReturnValueIfNull(discoveredSignature.Rom.Size, 0));
|
dbDict.Add("size", Common.ReturnValueIfNull(discoveredSignature.Rom.Size, 0));
|
||||||
dbDict.Add("md5", hash.md5hash);
|
dbDict.Add("md5", hash.md5hash);
|
||||||
dbDict.Add("sha1", hash.sha1hash);
|
dbDict.Add("sha1", hash.sha1hash);
|
||||||
dbDict.Add("crc", Common.ReturnValueIfNull(discoveredSignature.Rom.Crc, ""));
|
dbDict.Add("crc", Common.ReturnValueIfNull(discoveredSignature.Rom.Crc, ""));
|
||||||
dbDict.Add("developmentstatus", Common.ReturnValueIfNull(discoveredSignature.Rom.DevelopmentStatus, ""));
|
dbDict.Add("developmentstatus", Common.ReturnValueIfNull(discoveredSignature.Rom.DevelopmentStatus, ""));
|
||||||
dbDict.Add("metadatasource", discoveredSignature.Rom.SignatureSource);
|
dbDict.Add("metadatasource", discoveredSignature.Rom.SignatureSource);
|
||||||
|
dbDict.Add("metadatagamename", discoveredSignature.Game.Name);
|
||||||
|
dbDict.Add("metadataversion", 2);
|
||||||
|
dbDict.Add("libraryid", library.Id);
|
||||||
|
|
||||||
if (discoveredSignature.Rom.flags != null)
|
if (discoveredSignature.Rom.Attributes != null)
|
||||||
{
|
{
|
||||||
if (discoveredSignature.Rom.flags.Count > 0)
|
if (discoveredSignature.Rom.Attributes.Count > 0)
|
||||||
{
|
{
|
||||||
dbDict.Add("flags", Newtonsoft.Json.JsonConvert.SerializeObject(discoveredSignature.Rom.flags));
|
dbDict.Add("attributes", Newtonsoft.Json.JsonConvert.SerializeObject(discoveredSignature.Rom.Attributes));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dbDict.Add("flags", "[ ]");
|
dbDict.Add("attributes", "[ ]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dbDict.Add("flags", "[ ]");
|
dbDict.Add("attributes", "[ ]");
|
||||||
}
|
}
|
||||||
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, ""));
|
||||||
@@ -351,7 +354,10 @@ namespace gaseous_server.Classes
|
|||||||
}
|
}
|
||||||
|
|
||||||
// move to destination
|
// move to destination
|
||||||
MoveGameFile(romId);
|
if (library.IsDefaultLibrary == true)
|
||||||
|
{
|
||||||
|
MoveGameFile(romId);
|
||||||
|
}
|
||||||
|
|
||||||
return romId;
|
return romId;
|
||||||
}
|
}
|
||||||
@@ -362,7 +368,7 @@ namespace gaseous_server.Classes
|
|||||||
|
|
||||||
// get metadata
|
// get metadata
|
||||||
IGDB.Models.Platform platform = gaseous_server.Classes.Metadata.Platforms.GetPlatform(rom.PlatformId);
|
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);
|
IGDB.Models.Game game = gaseous_server.Classes.Metadata.Games.GetGame(rom.GameId, false, false, false);
|
||||||
|
|
||||||
// build path
|
// build path
|
||||||
string platformSlug = "Unknown Platform";
|
string platformSlug = "Unknown Platform";
|
||||||
@@ -375,7 +381,7 @@ namespace gaseous_server.Classes
|
|||||||
{
|
{
|
||||||
gameSlug = game.Slug;
|
gameSlug = game.Slug;
|
||||||
}
|
}
|
||||||
string DestinationPath = Path.Combine(Config.LibraryConfiguration.LibraryDataDirectory, gameSlug, platformSlug);
|
string DestinationPath = Path.Combine(GameLibrary.GetDefaultLibrary.Path, gameSlug, platformSlug);
|
||||||
if (!Directory.Exists(DestinationPath))
|
if (!Directory.Exists(DestinationPath))
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(DestinationPath);
|
Directory.CreateDirectory(DestinationPath);
|
||||||
@@ -413,7 +419,7 @@ namespace gaseous_server.Classes
|
|||||||
File.Move(romPath, DestinationPath);
|
File.Move(romPath, DestinationPath);
|
||||||
|
|
||||||
// update the db
|
// update the db
|
||||||
Database db = new gaseous_tools.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 Path=@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);
|
||||||
@@ -431,14 +437,18 @@ namespace gaseous_server.Classes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OrganiseLibrary()
|
public void OrganiseLibrary()
|
||||||
{
|
{
|
||||||
Logging.Log(Logging.LogType.Information, "Organise Library", "Starting library organisation");
|
Logging.Log(Logging.LogType.Information, "Organise Library", "Starting default library organisation");
|
||||||
|
|
||||||
|
GameLibrary.LibraryItem library = GameLibrary.GetDefaultLibrary;
|
||||||
|
|
||||||
// move rom files to their new location
|
// move rom files to their new location
|
||||||
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
string sql = "SELECT * FROM Games_Roms";
|
string sql = "SELECT * FROM Games_Roms WHERE LibraryId = @libraryid";
|
||||||
DataTable romDT = db.ExecuteCMD(sql);
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("libraryid", library.Id);
|
||||||
|
DataTable romDT = db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
if (romDT.Rows.Count > 0)
|
if (romDT.Rows.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -451,36 +461,121 @@ namespace gaseous_server.Classes
|
|||||||
}
|
}
|
||||||
|
|
||||||
// clean up empty directories
|
// clean up empty directories
|
||||||
DeleteOrphanedDirectories(Config.LibraryConfiguration.LibraryDataDirectory);
|
DeleteOrphanedDirectories(GameLibrary.GetDefaultLibrary.Path);
|
||||||
|
|
||||||
Logging.Log(Logging.LogType.Information, "Organise Library", "Finsihed library organisation");
|
Logging.Log(Logging.LogType.Information, "Organise Library", "Finsihed default library organisation");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DeleteOrphanedDirectories(string startLocation)
|
public static void DeleteOrphanedDirectories(string startLocation)
|
||||||
{
|
{
|
||||||
foreach (var directory in Directory.GetDirectories(startLocation))
|
foreach (var directory in Directory.GetDirectories(startLocation))
|
||||||
{
|
{
|
||||||
DeleteOrphanedDirectories(directory);
|
DeleteOrphanedDirectories(directory);
|
||||||
if (Directory.GetFiles(directory).Length == 0 &&
|
|
||||||
Directory.GetDirectories(directory).Length == 0)
|
string[] files = Directory.GetFiles(directory);
|
||||||
|
string[] directories = Directory.GetDirectories(directory);
|
||||||
|
|
||||||
|
if (files.Length == 0 &&
|
||||||
|
directories.Length == 0)
|
||||||
{
|
{
|
||||||
Directory.Delete(directory, false);
|
Directory.Delete(directory, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void LibraryScan()
|
public void LibraryScan(GameLibrary.LibraryItem? singleLibrary = null)
|
||||||
{
|
{
|
||||||
Logging.Log(Logging.LogType.Information, "Library Scan", "Starting library scan");
|
int maxWorkers = Config.MetadataConfiguration.MaxLibraryScanWorkers;
|
||||||
|
|
||||||
|
List<GameLibrary.LibraryItem> libraries = new List<GameLibrary.LibraryItem>();
|
||||||
|
if (singleLibrary == null)
|
||||||
|
{
|
||||||
|
libraries.AddRange(GameLibrary.GetLibraries);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
libraries.Add(singleLibrary);
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup background tasks for each library
|
||||||
|
foreach (GameLibrary.LibraryItem library in libraries)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
queue.Options = library;
|
||||||
|
queue.ForceExecute();
|
||||||
|
|
||||||
|
ProcessQueue.QueueItems.Add(queue);
|
||||||
|
|
||||||
|
// check number of running tasks is less than maxWorkers
|
||||||
|
bool allowContinue;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
allowContinue = true;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
currentWorkerCount += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentWorkerCount >= maxWorkers)
|
||||||
|
{
|
||||||
|
allowContinue = false;
|
||||||
|
Thread.Sleep(60000);
|
||||||
|
}
|
||||||
|
} while (allowContinue == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WorkersStillWorking;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
WorkersStillWorking = false;
|
||||||
|
List<ProcessQueue.QueueItem> queueItems = new List<ProcessQueue.QueueItem>();
|
||||||
|
queueItems.AddRange(ProcessQueue.QueueItems);
|
||||||
|
foreach (ProcessQueue.QueueItem item in queueItems)
|
||||||
|
{
|
||||||
|
if (item.ItemType == ProcessQueue.QueueItemType.LibraryScanWorker)
|
||||||
|
{
|
||||||
|
// workers are still running - sleep and keep looping
|
||||||
|
WorkersStillWorking = true;
|
||||||
|
Thread.Sleep(30000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (WorkersStillWorking == true);
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Information, "Library Scan", "Library scan complete. All workers stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LibrarySpecificScan(GameLibrary.LibraryItem library)
|
||||||
|
{
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Information, "Library Scan", "Starting scan of library: " + library.Name);
|
||||||
|
|
||||||
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
|
||||||
Logging.Log(Logging.LogType.Information, "Library Scan", "Looking for duplicate library files to clean up");
|
Logging.Log(Logging.LogType.Information, "Library Scan", "Looking for duplicate library files to clean up");
|
||||||
string duplicateSql = "DELETE r1 FROM Games_Roms r1 INNER JOIN Games_Roms r2 WHERE r1.Id > r2.Id AND r1.MD5 = r2.MD5;";
|
string duplicateSql = "DELETE r1 FROM Games_Roms r1 INNER JOIN Games_Roms r2 WHERE r1.Id > r2.Id AND r1.MD5 = r2.MD5 AND r1.LibraryId=@libraryid AND r2.LibraryId=@libraryid;";
|
||||||
db.ExecuteCMD(duplicateSql);
|
Dictionary<string, object> dupDict = new Dictionary<string, object>();
|
||||||
|
dupDict.Add("libraryid", library.Id);
|
||||||
|
db.ExecuteCMD(duplicateSql, dupDict);
|
||||||
|
|
||||||
string sql = "SELECT * FROM Games_Roms ORDER BY `name`";
|
string sql = "SELECT * FROM Games_Roms WHERE LibraryId=@libraryid ORDER BY `name`";
|
||||||
DataTable dtRoms = db.ExecuteCMD(sql);
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("libraryid", library.Id);
|
||||||
|
DataTable dtRoms = db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
// clean out database entries in the import folder
|
// clean out database entries in the import folder
|
||||||
if (dtRoms.Rows.Count > 0)
|
if (dtRoms.Rows.Count > 0)
|
||||||
@@ -490,130 +585,220 @@ namespace gaseous_server.Classes
|
|||||||
long romId = (long)dtRoms.Rows[i]["Id"];
|
long romId = (long)dtRoms.Rows[i]["Id"];
|
||||||
string romPath = (string)dtRoms.Rows[i]["Path"];
|
string romPath = (string)dtRoms.Rows[i]["Path"];
|
||||||
|
|
||||||
if (!romPath.StartsWith(Config.LibraryConfiguration.LibraryDataDirectory))
|
if (!romPath.StartsWith(library.Path))
|
||||||
{
|
{
|
||||||
Logging.Log(Logging.LogType.Information, "Library Scan", " Deleting database entry for files with incorrect directory " + romPath);
|
Logging.Log(Logging.LogType.Information, "Library Scan", "Deleting database entry for files with incorrect directory " + romPath);
|
||||||
string deleteSql = "DELETE FROM Games_Roms WHERE Id=@id";
|
string deleteSql = "DELETE FROM Games_Roms WHERE Id=@id AND LibraryId=@libraryid";
|
||||||
Dictionary<string, object> deleteDict = new Dictionary<string, object>();
|
Dictionary<string, object> deleteDict = new Dictionary<string, object>();
|
||||||
deleteDict.Add("Id", romId);
|
deleteDict.Add("Id", romId);
|
||||||
|
deleteDict.Add("libraryid", library.Id);
|
||||||
db.ExecuteCMD(deleteSql, deleteDict);
|
db.ExecuteCMD(deleteSql, deleteDict);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sql = "SELECT * FROM Games_Roms ORDER BY `name`";
|
sql = "SELECT * FROM Games_Roms WHERE LibraryId=@libraryid ORDER BY `name`";
|
||||||
dtRoms = db.ExecuteCMD(sql);
|
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
|
||||||
Logging.Log(Logging.LogType.Information, "Library Scan", "Looking for orphaned library files to add");
|
Logging.Log(Logging.LogType.Information, "Library Scan", "Looking for orphaned library files to add");
|
||||||
string[] LibraryFiles = Directory.GetFiles(Config.LibraryConfiguration.LibraryDataDirectory, "*.*", SearchOption.AllDirectories);
|
string[] LibraryFiles = Directory.GetFiles(library.Path, "*.*", SearchOption.AllDirectories);
|
||||||
|
int StatusCount = 0;
|
||||||
foreach (string LibraryFile in LibraryFiles)
|
foreach (string LibraryFile in LibraryFiles)
|
||||||
{
|
{
|
||||||
Common.hashObject LibraryFileHash = new Common.hashObject(LibraryFile);
|
SetStatus(StatusCount, LibraryFiles.Length, "Processing file " + LibraryFile);
|
||||||
|
if (!Common.SkippableFiles.Contains<string>(Path.GetFileName(LibraryFile), StringComparer.OrdinalIgnoreCase))
|
||||||
// check if file is in database
|
|
||||||
bool romFound = false;
|
|
||||||
for (var i = 0; i < dtRoms.Rows.Count; i++)
|
|
||||||
{
|
{
|
||||||
long romId = (long)dtRoms.Rows[i]["Id"];
|
Common.hashObject LibraryFileHash = new Common.hashObject(LibraryFile);
|
||||||
string romPath = (string)dtRoms.Rows[i]["Path"];
|
|
||||||
string romMd5 = (string)dtRoms.Rows[i]["MD5"];
|
|
||||||
|
|
||||||
if ((LibraryFile == romPath) || (LibraryFileHash.md5hash == romMd5))
|
// check if file is in database
|
||||||
|
bool romFound = false;
|
||||||
|
for (var i = 0; i < dtRoms.Rows.Count; i++)
|
||||||
{
|
{
|
||||||
romFound = true;
|
long romId = (long)dtRoms.Rows[i]["Id"];
|
||||||
break;
|
string romPath = (string)dtRoms.Rows[i]["Path"];
|
||||||
}
|
string romMd5 = (string)dtRoms.Rows[i]["MD5"];
|
||||||
}
|
|
||||||
|
|
||||||
if (romFound == false)
|
if ((LibraryFile == romPath) || (LibraryFileHash.md5hash == romMd5))
|
||||||
{
|
{
|
||||||
// file is not in database - process it
|
romFound = true;
|
||||||
Common.hashObject hash = new Common.hashObject(LibraryFile);
|
break;
|
||||||
FileInfo fi = new FileInfo(LibraryFile);
|
}
|
||||||
|
|
||||||
Models.Signatures_Games sig = GetFileSignature(hash, fi, LibraryFile);
|
|
||||||
|
|
||||||
Logging.Log(Logging.LogType.Information, "Library Scan", " Orphaned file found in library: " + LibraryFile);
|
|
||||||
|
|
||||||
// get discovered platform
|
|
||||||
IGDB.Models.Platform determinedPlatform = Metadata.Platforms.GetPlatform(sig.Flags.IGDBPlatformId);
|
|
||||||
if (determinedPlatform == null)
|
|
||||||
{
|
|
||||||
determinedPlatform = new IGDB.Models.Platform();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IGDB.Models.Game determinedGame = SearchForGame(sig.Game.Name, sig.Flags.IGDBPlatformId);
|
if (romFound == false)
|
||||||
|
{
|
||||||
|
// file is not in database - process it
|
||||||
|
Logging.Log(Logging.LogType.Information, "Library Scan", "Orphaned file found in library: " + LibraryFile);
|
||||||
|
|
||||||
StoreROM(hash, determinedGame, determinedPlatform, sig, LibraryFile);
|
Common.hashObject hash = new Common.hashObject(LibraryFile);
|
||||||
|
FileInfo fi = new FileInfo(LibraryFile);
|
||||||
|
|
||||||
|
FileSignature fileSignature = new FileSignature();
|
||||||
|
gaseous_server.Models.Signatures_Games sig = fileSignature.GetFileSignature(library, hash, fi, LibraryFile);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// get discovered platform
|
||||||
|
long PlatformId;
|
||||||
|
IGDB.Models.Platform determinedPlatform;
|
||||||
|
|
||||||
|
if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0 )
|
||||||
|
{
|
||||||
|
// no platform discovered in the signature
|
||||||
|
PlatformId = library.DefaultPlatformId;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// use the platform discovered in the signature
|
||||||
|
PlatformId = sig.Flags.IGDBPlatformId;
|
||||||
|
}
|
||||||
|
determinedPlatform = Platforms.GetPlatform(PlatformId);
|
||||||
|
|
||||||
|
IGDB.Models.Game determinedGame = SearchForGame(sig, PlatformId, true);
|
||||||
|
|
||||||
|
StoreROM(library, hash, determinedGame, determinedPlatform, sig, LibraryFile);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Library Scan", "An error occurred while matching orphaned file: " + LibraryFile + ". Skipping.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
StatusCount += 1;
|
||||||
}
|
}
|
||||||
|
ClearStatus();
|
||||||
|
|
||||||
sql = "SELECT * FROM Games_Roms ORDER BY `name`";
|
sql = "SELECT * FROM Games_Roms WHERE LibraryId=@libraryid ORDER BY `name`";
|
||||||
dtRoms = db.ExecuteCMD(sql);
|
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
|
||||||
Logging.Log(Logging.LogType.Information, "Library Scan", "Checking library files exist on disk");
|
Logging.Log(Logging.LogType.Information, "Library Scan", "Checking library files exist on disk");
|
||||||
|
StatusCount = 0;
|
||||||
if (dtRoms.Rows.Count > 0)
|
if (dtRoms.Rows.Count > 0)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < dtRoms.Rows.Count; i++)
|
for (var i = 0; i < dtRoms.Rows.Count; i++)
|
||||||
{
|
{
|
||||||
long romId = (long)dtRoms.Rows[i]["Id"];
|
long romId = (long)dtRoms.Rows[i]["Id"];
|
||||||
string romPath = (string)dtRoms.Rows[i]["Path"];
|
string romPath = (string)dtRoms.Rows[i]["Path"];
|
||||||
Classes.Roms.GameRomItem.SourceType romMetadataSource = (Classes.Roms.GameRomItem.SourceType)(int)dtRoms.Rows[i]["MetadataSource"];
|
gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType romMetadataSource = (gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType)(int)dtRoms.Rows[i]["MetadataSource"];
|
||||||
|
|
||||||
Logging.Log(Logging.LogType.Information, "Library Scan", " Processing ROM at path " + romPath);
|
SetStatus(StatusCount, dtRoms.Rows.Count, "Processing file " + romPath);
|
||||||
|
Logging.Log(Logging.LogType.Information, "Library Scan", "Processing ROM at path " + romPath);
|
||||||
|
|
||||||
if (File.Exists(romPath))
|
if (File.Exists(romPath))
|
||||||
{
|
{
|
||||||
// file exists, so lets check to make sure the signature was matched, and update if a signature can be found
|
if (library.IsDefaultLibrary == true)
|
||||||
if (romMetadataSource == Roms.GameRomItem.SourceType.None)
|
|
||||||
{
|
{
|
||||||
Common.hashObject hash = new Common.hashObject
|
if (romPath != ComputeROMPath(romId))
|
||||||
{
|
{
|
||||||
md5hash = "",
|
Logging.Log(Logging.LogType.Information, "Library Scan", "ROM at path " + romPath + " found, but needs to be moved");
|
||||||
sha1hash = ""
|
MoveGameFile(romId);
|
||||||
};
|
}
|
||||||
FileInfo fi = new FileInfo(romPath);
|
else
|
||||||
|
{
|
||||||
Models.Signatures_Games sig = GetFileSignature(hash, fi, romPath);
|
Logging.Log(Logging.LogType.Information, "Library Scan", "ROM at path " + romPath + " found");
|
||||||
if (sig.Rom.SignatureSource != Models.Signatures_Games.RomItem.SignatureSourceType.None)
|
|
||||||
{
|
|
||||||
Logging.Log(Logging.LogType.Information, "Library Scan", " Update signature found for " + romPath);
|
|
||||||
|
|
||||||
// get discovered platform
|
|
||||||
IGDB.Models.Platform determinedPlatform = Metadata.Platforms.GetPlatform(sig.Flags.IGDBPlatformId);
|
|
||||||
if (determinedPlatform == null)
|
|
||||||
{
|
|
||||||
determinedPlatform = new IGDB.Models.Platform();
|
|
||||||
}
|
|
||||||
|
|
||||||
IGDB.Models.Game determinedGame = SearchForGame(sig.Game.Name, sig.Flags.IGDBPlatformId);
|
|
||||||
|
|
||||||
StoreROM(hash, determinedGame, determinedPlatform, sig, romPath, romId);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (romPath != ComputeROMPath(romId))
|
|
||||||
{
|
|
||||||
MoveGameFile(romId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// file doesn't exist where it's supposed to be! delete it from the db
|
// file doesn't exist where it's supposed to be! delete it from the db
|
||||||
Logging.Log(Logging.LogType.Warning, "Library Scan", " Deleting orphaned database entry for " + romPath);
|
Logging.Log(Logging.LogType.Warning, "Library Scan", "Deleting orphaned database entry for " + romPath);
|
||||||
|
|
||||||
string deleteSql = "DELETE FROM Games_Roms WHERE Id = @id";
|
string deleteSql = "DELETE FROM Games_Roms WHERE Id = @id AND LibraryId = @libraryid";
|
||||||
Dictionary<string, object> deleteDict = new Dictionary<string, object>();
|
Dictionary<string, object> deleteDict = new Dictionary<string, object>();
|
||||||
deleteDict.Add("id", romId);
|
deleteDict.Add("id", romId);
|
||||||
|
deleteDict.Add("libraryid", library.Id);
|
||||||
db.ExecuteCMD(deleteSql, deleteDict);
|
db.ExecuteCMD(deleteSql, deleteDict);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StatusCount += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logging.Log(Logging.LogType.Information, "Library Scan", "Library scan completed");
|
Logging.Log(Logging.LogType.Information, "Library Scan", "Library scan completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Rematcher(bool ForceExecute = false)
|
||||||
|
{
|
||||||
|
// rescan all titles with an unknown platform or title and see if we can get a match
|
||||||
|
Logging.Log(Logging.LogType.Information, "Rematch Scan", "Rematch scan starting");
|
||||||
|
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
|
||||||
|
foreach (GameLibrary.LibraryItem library in GameLibrary.GetLibraries)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Rematch Scan", "Rematch on library " + library.Name);
|
||||||
|
|
||||||
|
string sql = "";
|
||||||
|
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;";
|
||||||
|
}
|
||||||
|
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;";
|
||||||
|
}
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("lastmatchattemptdate", DateTime.UtcNow.AddDays(-7));
|
||||||
|
dbDict.Add("libraryid", library.Id);
|
||||||
|
DataTable data = db.ExecuteCMD(sql, dbDict);
|
||||||
|
int StatusCount = -0;
|
||||||
|
foreach (DataRow row in data.Rows)
|
||||||
|
{
|
||||||
|
SetStatus(StatusCount, data.Rows.Count, "Running rematcher");
|
||||||
|
|
||||||
|
// get rom info
|
||||||
|
long romId = (long)row["Id"];
|
||||||
|
string romPath = (string)row["Path"];
|
||||||
|
Common.hashObject hash = new Common.hashObject
|
||||||
|
{
|
||||||
|
md5hash = (string)row["MD5"],
|
||||||
|
sha1hash = (string)row["SHA1"]
|
||||||
|
};
|
||||||
|
FileInfo fi = new FileInfo(romPath);
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Information, "Rematch Scan", "Running rematch against " + romPath);
|
||||||
|
|
||||||
|
// determine rom signature
|
||||||
|
FileSignature fileSignature = new FileSignature();
|
||||||
|
gaseous_server.Models.Signatures_Games sig = fileSignature.GetFileSignature(library, hash, fi, romPath);
|
||||||
|
|
||||||
|
// get discovered platform
|
||||||
|
long PlatformId;
|
||||||
|
IGDB.Models.Platform determinedPlatform;
|
||||||
|
|
||||||
|
if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0 )
|
||||||
|
{
|
||||||
|
// no platform discovered in the signature
|
||||||
|
PlatformId = library.DefaultPlatformId;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// use the platform discovered in the signature
|
||||||
|
PlatformId = sig.Flags.IGDBPlatformId;
|
||||||
|
}
|
||||||
|
determinedPlatform = Platforms.GetPlatform(PlatformId);
|
||||||
|
|
||||||
|
IGDB.Models.Game determinedGame = SearchForGame(sig, PlatformId, true);
|
||||||
|
|
||||||
|
StoreROM(library, hash, determinedGame, determinedPlatform, sig, romPath, romId);
|
||||||
|
|
||||||
|
string attemptSql = "UPDATE Games_Roms SET LastMatchAttemptDate=@lastmatchattemptdate WHERE Id=@id;";
|
||||||
|
Dictionary<string, object> dbLastAttemptDict = new Dictionary<string, object>();
|
||||||
|
dbLastAttemptDict.Add("id", romId);
|
||||||
|
dbLastAttemptDict.Add("lastmatchattemptdate", DateTime.UtcNow);
|
||||||
|
db.ExecuteCMD(attemptSql, dbLastAttemptDict);
|
||||||
|
|
||||||
|
StatusCount += 1;
|
||||||
|
}
|
||||||
|
ClearStatus();
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Information, "Rematch Scan", "Rematch scan completed");
|
||||||
|
ClearStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
364
gaseous-server/Classes/Logging.cs
Normal file
364
gaseous-server/Classes/Logging.cs
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Reflection.Metadata.Ecma335;
|
||||||
|
namespace gaseous_server.Classes
|
||||||
|
{
|
||||||
|
public class Logging
|
||||||
|
{
|
||||||
|
private static DateTime lastDiskRetentionSweep = DateTime.UtcNow;
|
||||||
|
public static bool WriteToDiskOnly { get; set; } = false;
|
||||||
|
|
||||||
|
static public void Log(LogType EventType, string ServerProcess, string Message, Exception? ExceptionValue = null, bool LogToDiskOnly = false)
|
||||||
|
{
|
||||||
|
LogItem logItem = new LogItem
|
||||||
|
{
|
||||||
|
EventTime = DateTime.UtcNow,
|
||||||
|
EventType = EventType,
|
||||||
|
Process = ServerProcess,
|
||||||
|
Message = Message,
|
||||||
|
ExceptionValue = Common.ReturnValueIfNull(ExceptionValue, "").ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
bool AllowWrite = false;
|
||||||
|
if (EventType == LogType.Debug)
|
||||||
|
{
|
||||||
|
if (Config.LoggingConfiguration.DebugLogging == true)
|
||||||
|
{
|
||||||
|
AllowWrite = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AllowWrite = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AllowWrite == true)
|
||||||
|
{
|
||||||
|
// console output
|
||||||
|
string TraceOutput = logItem.EventTime.ToString("yyyyMMdd HHmmss") + ": " + logItem.EventType.ToString() + ": " + logItem.Process + ": " + logItem.Message;
|
||||||
|
if (logItem.ExceptionValue != null)
|
||||||
|
{
|
||||||
|
TraceOutput += Environment.NewLine + logItem.ExceptionValue.ToString();
|
||||||
|
}
|
||||||
|
switch(logItem.EventType) {
|
||||||
|
case LogType.Information:
|
||||||
|
Console.ForegroundColor = ConsoleColor.Blue;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LogType.Warning:
|
||||||
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LogType.Critical:
|
||||||
|
Console.ForegroundColor = ConsoleColor.Red;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LogType.Debug:
|
||||||
|
Console.ForegroundColor = ConsoleColor.Magenta;
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
Console.WriteLine(TraceOutput);
|
||||||
|
Console.ResetColor();
|
||||||
|
|
||||||
|
if (WriteToDiskOnly == true)
|
||||||
|
{
|
||||||
|
LogToDiskOnly = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LogToDiskOnly == false)
|
||||||
|
{
|
||||||
|
if (Config.LoggingConfiguration.AlwaysLogToDisk == true)
|
||||||
|
{
|
||||||
|
LogToDisk(logItem, TraceOutput, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
string correlationId;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (CallContext.GetData("CorrelationId").ToString() == null)
|
||||||
|
{
|
||||||
|
correlationId = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
correlationId = CallContext.GetData("CorrelationId").ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
correlationId = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
string callingProcess;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (CallContext.GetData("CallingProcess").ToString() == null)
|
||||||
|
{
|
||||||
|
callingProcess = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
callingProcess = CallContext.GetData("CallingProcess").ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
callingProcess = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
string callingUser;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (CallContext.GetData("CallingUser").ToString() == null)
|
||||||
|
{
|
||||||
|
callingUser = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
callingUser = CallContext.GetData("CallingUser").ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
callingUser = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "INSERT INTO ServerLogs (EventTime, EventType, Process, Message, Exception, CorrelationId, CallingProcess, CallingUser) VALUES (@EventTime, @EventType, @Process, @Message, @Exception, @correlationid, @callingprocess, @callinguser);";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("EventTime", logItem.EventTime);
|
||||||
|
dbDict.Add("EventType", logItem.EventType);
|
||||||
|
dbDict.Add("Process", logItem.Process);
|
||||||
|
dbDict.Add("Message", logItem.Message);
|
||||||
|
dbDict.Add("Exception", Common.ReturnValueIfNull(logItem.ExceptionValue, "").ToString());
|
||||||
|
dbDict.Add("correlationid", correlationId);
|
||||||
|
dbDict.Add("callingprocess", callingProcess);
|
||||||
|
dbDict.Add("callinguser", callingUser);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogToDisk(logItem, TraceOutput, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogToDisk(logItem, TraceOutput, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastDiskRetentionSweep.AddMinutes(60) < DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
// time to delete any old logs
|
||||||
|
lastDiskRetentionSweep = DateTime.UtcNow;
|
||||||
|
string[] files = Directory.GetFiles(Config.LogPath);
|
||||||
|
|
||||||
|
foreach (string file in files)
|
||||||
|
{
|
||||||
|
FileInfo fi = new FileInfo(file);
|
||||||
|
if (fi.LastAccessTime < DateTime.Now.AddDays(Config.LoggingConfiguration.LogRetention * -1))
|
||||||
|
{
|
||||||
|
fi.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void LogToDisk(LogItem logItem, string TraceOutput, Exception? exception)
|
||||||
|
{
|
||||||
|
if (exception != null)
|
||||||
|
{
|
||||||
|
// dump the error
|
||||||
|
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
|
||||||
|
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, TraceOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public List<LogItem> GetLogs(LogsViewModel model)
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("StartIndex", model.StartIndex);
|
||||||
|
dbDict.Add("PageNumber", (model.PageNumber - 1) * model.PageSize);
|
||||||
|
dbDict.Add("PageSize", model.PageSize);
|
||||||
|
string sql = "";
|
||||||
|
|
||||||
|
List<string> whereClauses = new List<string>();
|
||||||
|
|
||||||
|
// handle status criteria
|
||||||
|
if (model.Status != null)
|
||||||
|
{
|
||||||
|
if (model.Status.Count > 0)
|
||||||
|
{
|
||||||
|
List<string> statusWhere = new List<string>();
|
||||||
|
for (int i = 0; i < model.Status.Count; i++)
|
||||||
|
{
|
||||||
|
string valueName = "@eventtype" + i;
|
||||||
|
statusWhere.Add(valueName);
|
||||||
|
dbDict.Add(valueName, (int)model.Status[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
whereClauses.Add("EventType IN (" + string.Join(",", statusWhere) + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle start date criteria
|
||||||
|
if (model.StartDateTime != null)
|
||||||
|
{
|
||||||
|
dbDict.Add("startdate", model.StartDateTime);
|
||||||
|
whereClauses.Add("EventTime >= @startdate");
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle end date criteria
|
||||||
|
if (model.EndDateTime != null)
|
||||||
|
{
|
||||||
|
dbDict.Add("enddate", model.EndDateTime);
|
||||||
|
whereClauses.Add("EventTime <= @enddate");
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle search text criteria
|
||||||
|
if (model.SearchText != null)
|
||||||
|
{
|
||||||
|
if (model.SearchText.Length > 0)
|
||||||
|
{
|
||||||
|
dbDict.Add("messageSearch", model.SearchText);
|
||||||
|
whereClauses.Add("MATCH(Message) AGAINST (@messageSearch)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.CorrelationId != null)
|
||||||
|
{
|
||||||
|
if (model.CorrelationId.Length > 0)
|
||||||
|
{
|
||||||
|
dbDict.Add("correlationId", model.CorrelationId);
|
||||||
|
whereClauses.Add("CorrelationId = @correlationId");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.CallingProcess != null)
|
||||||
|
{
|
||||||
|
if (model.CallingProcess.Length > 0)
|
||||||
|
{
|
||||||
|
dbDict.Add("callingProcess", model.CallingProcess);
|
||||||
|
whereClauses.Add("CallingProcess = @callingProcess");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.CallingUser != null)
|
||||||
|
{
|
||||||
|
if (model.CallingUser.Length > 0)
|
||||||
|
{
|
||||||
|
dbDict.Add("callingUser", model.CallingUser);
|
||||||
|
whereClauses.Add("CallingUser = @callingUser");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compile WHERE clause
|
||||||
|
string whereClause = "";
|
||||||
|
if (whereClauses.Count > 0)
|
||||||
|
{
|
||||||
|
whereClause = "(" + String.Join(" AND ", whereClauses) + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute query
|
||||||
|
if (model.StartIndex == null)
|
||||||
|
{
|
||||||
|
if (whereClause.Length > 0)
|
||||||
|
{
|
||||||
|
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;";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (whereClause.Length > 0)
|
||||||
|
{
|
||||||
|
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;";
|
||||||
|
}
|
||||||
|
DataTable dataTable = db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
List<LogItem> logs = new List<LogItem>();
|
||||||
|
foreach (DataRow row in dataTable.Rows)
|
||||||
|
{
|
||||||
|
LogItem log = new LogItem
|
||||||
|
{
|
||||||
|
Id = (long)row["Id"],
|
||||||
|
EventTime = DateTime.Parse(((DateTime)row["EventTime"]).ToString("yyyy-MM-ddThh:mm:ss") + 'Z'),
|
||||||
|
EventType = (LogType)row["EventType"],
|
||||||
|
Process = (string)row["Process"],
|
||||||
|
Message = (string)row["Message"],
|
||||||
|
ExceptionValue = (string)row["Exception"],
|
||||||
|
CorrelationId = (string)Common.ReturnValueIfNull(row["CorrelationId"], ""),
|
||||||
|
CallingProcess = (string)Common.ReturnValueIfNull(row["CallingProcess"], ""),
|
||||||
|
CallingUser = (string)Common.ReturnValueIfNull(row["Email"], "")
|
||||||
|
};
|
||||||
|
|
||||||
|
logs.Add(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
return logs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum LogType
|
||||||
|
{
|
||||||
|
Information = 0,
|
||||||
|
Debug = 1,
|
||||||
|
Warning = 2,
|
||||||
|
Critical = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LogItem
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public DateTime EventTime { get; set; }
|
||||||
|
public LogType? EventType { get; set; }
|
||||||
|
public string Process { get; set; } = "";
|
||||||
|
public string CorrelationId { get; set; } = "";
|
||||||
|
public string? CallingProcess { get; set; } = "";
|
||||||
|
public string? CallingUser { get; set; } = "";
|
||||||
|
private string _Message = "";
|
||||||
|
public string Message
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _Message;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_Message = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public string? ExceptionValue { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LogsViewModel
|
||||||
|
{
|
||||||
|
public long? StartIndex { get; set; }
|
||||||
|
public int PageNumber { get; set; } = 1;
|
||||||
|
public int PageSize { get; set; } = 100;
|
||||||
|
public List<LogType> Status { get; set; } = new List<LogType>();
|
||||||
|
public DateTime? StartDateTime { get; set; }
|
||||||
|
public DateTime? EndDateTime { get; set; }
|
||||||
|
public string? SearchText { get; set; }
|
||||||
|
public string? CorrelationId { get; set; }
|
||||||
|
public string? CallingProcess { get; set; }
|
||||||
|
public string? CallingUser { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
99
gaseous-server/Classes/Maintenance.cs
Normal file
99
gaseous-server/Classes/Maintenance.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data;
|
||||||
|
using gaseous_server.Models;
|
||||||
|
using Microsoft.VisualStudio.Web.CodeGeneration;
|
||||||
|
|
||||||
|
namespace gaseous_server.Classes
|
||||||
|
{
|
||||||
|
public class Maintenance : QueueItemStatus
|
||||||
|
{
|
||||||
|
const int MaxFileAge = 30;
|
||||||
|
|
||||||
|
public void RunMaintenance()
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
// remove any entries from the library that have an invalid id
|
||||||
|
string LibraryWhereClause = "";
|
||||||
|
foreach (GameLibrary.LibraryItem library in GameLibrary.GetLibraries)
|
||||||
|
{
|
||||||
|
if (LibraryWhereClause.Length > 0)
|
||||||
|
{
|
||||||
|
LibraryWhereClause += ", ";
|
||||||
|
}
|
||||||
|
LibraryWhereClause += library.Id;
|
||||||
|
}
|
||||||
|
string sqlLibraryWhereClause = "";
|
||||||
|
if (LibraryWhereClause.Length > 0)
|
||||||
|
{
|
||||||
|
sqlLibraryWhereClause = "DELETE FROM Games_Roms WHERE LibraryId NOT IN ( " + LibraryWhereClause + " );";
|
||||||
|
db.ExecuteCMD(sqlLibraryWhereClause);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete old logs
|
||||||
|
sql = "DELETE FROM ServerLogs WHERE EventTime < @EventRententionDate;";
|
||||||
|
dbDict.Add("EventRententionDate", DateTime.UtcNow.AddDays(Config.LoggingConfiguration.LogRetention * -1));
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
// delete files and directories older than 7 days in PathsToClean
|
||||||
|
List<string> PathsToClean = new List<string>();
|
||||||
|
PathsToClean.Add(Config.LibraryConfiguration.LibraryUploadDirectory);
|
||||||
|
PathsToClean.Add(Config.LibraryConfiguration.LibraryTempDirectory);
|
||||||
|
|
||||||
|
foreach (string PathToClean in PathsToClean)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Maintenance", "Removing files older than " + MaxFileAge + " days from " + PathToClean);
|
||||||
|
|
||||||
|
// get content
|
||||||
|
// files first
|
||||||
|
foreach (string filePath in Directory.GetFiles(PathToClean))
|
||||||
|
{
|
||||||
|
FileInfo fileInfo = new FileInfo(filePath);
|
||||||
|
if (fileInfo.LastWriteTimeUtc.AddDays(MaxFileAge) < DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Maintenance", "Deleting file " + filePath);
|
||||||
|
File.Delete(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now directories
|
||||||
|
foreach (string dirPath in Directory.GetDirectories(PathToClean))
|
||||||
|
{
|
||||||
|
DirectoryInfo directoryInfo = new DirectoryInfo(dirPath);
|
||||||
|
if (directoryInfo.LastWriteTimeUtc.AddDays(MaxFileAge) < DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Maintenance", "Deleting directory " + directoryInfo);
|
||||||
|
Directory.Delete(dirPath, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Information, "Maintenance", "Optimising database tables");
|
||||||
|
sql = "SHOW FULL TABLES WHERE Table_Type = 'BASE TABLE';";
|
||||||
|
DataTable tables = db.ExecuteCMD(sql);
|
||||||
|
|
||||||
|
int StatusCounter = 1;
|
||||||
|
foreach (DataRow row in tables.Rows)
|
||||||
|
{
|
||||||
|
SetStatus(StatusCounter, tables.Rows.Count, "Optimising table " + row[0].ToString());
|
||||||
|
|
||||||
|
sql = "OPTIMIZE TABLE " + row[0].ToString();
|
||||||
|
DataTable response = db.ExecuteCMD(sql);
|
||||||
|
foreach (DataRow responseRow in response.Rows)
|
||||||
|
{
|
||||||
|
string retVal = "";
|
||||||
|
for (int i = 0; i < responseRow.ItemArray.Length; i++)
|
||||||
|
{
|
||||||
|
retVal += responseRow.ItemArray[i] + "; ";
|
||||||
|
}
|
||||||
|
Logging.Log(Logging.LogType.Information, "Maintenance", "(" + StatusCounter + "/" + tables.Rows.Count + "): Optimise table " + row[0].ToString() + ": " + retVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusCounter += 1;
|
||||||
|
}
|
||||||
|
ClearStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
304
gaseous-server/Classes/Metadata/AgeGroups.cs
Normal file
304
gaseous-server/Classes/Metadata/AgeGroups.cs
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using IGDB;
|
||||||
|
using IGDB.Models;
|
||||||
|
using Microsoft.CodeAnalysis.Classification;
|
||||||
|
|
||||||
|
namespace gaseous_server.Classes.Metadata
|
||||||
|
{
|
||||||
|
public class AgeGroups
|
||||||
|
{
|
||||||
|
public AgeGroups()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AgeGroup? GetAgeGroup(Game? game)
|
||||||
|
{
|
||||||
|
if (game == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
||||||
|
cacheStatus = Storage.GetCacheStatus("AgeGroup", (long)game.Id);
|
||||||
|
|
||||||
|
AgeGroup? RetVal = new AgeGroup();
|
||||||
|
|
||||||
|
switch (cacheStatus)
|
||||||
|
{
|
||||||
|
case Storage.CacheStatus.NotPresent:
|
||||||
|
RetVal = _GetAgeGroup(game);
|
||||||
|
Storage.NewCacheValue(RetVal, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Storage.CacheStatus.Expired:
|
||||||
|
RetVal = _GetAgeGroup(game);
|
||||||
|
Storage.NewCacheValue(RetVal, true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Storage.CacheStatus.Current:
|
||||||
|
RetVal = Storage.GetCacheValue<AgeGroup>(RetVal, "Id", game.Id);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception("How did you get here?");
|
||||||
|
}
|
||||||
|
|
||||||
|
return RetVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AgeGroup? _GetAgeGroup(Game game)
|
||||||
|
{
|
||||||
|
// compile the maximum age group for the given game
|
||||||
|
if (game != null)
|
||||||
|
{
|
||||||
|
if (game.AgeRatings != null)
|
||||||
|
{
|
||||||
|
if (game.AgeRatings.Ids != null)
|
||||||
|
{
|
||||||
|
// collect ratings values from metadata
|
||||||
|
List<AgeRating> ageRatings = new List<AgeRating>();
|
||||||
|
foreach (long ratingId in game.AgeRatings.Ids)
|
||||||
|
{
|
||||||
|
AgeRating? rating = AgeRatings.GetAgeRatings(ratingId);
|
||||||
|
if (rating != null)
|
||||||
|
{
|
||||||
|
ageRatings.Add(rating);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 the compiled ratings group
|
||||||
|
AgeGroup ageGroup = new AgeGroup();
|
||||||
|
ageGroup.Id = game.Id;
|
||||||
|
ageGroup.GameId = game.Id;
|
||||||
|
if (highestAgeGroup == 0)
|
||||||
|
{
|
||||||
|
ageGroup.AgeGroupId = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ageGroup.AgeGroupId = highestAgeGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ageGroup;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AgeGroup ageGroup = new AgeGroup();
|
||||||
|
ageGroup.Id = game.Id;
|
||||||
|
ageGroup.GameId = game.Id;
|
||||||
|
ageGroup.AgeGroupId = null;
|
||||||
|
|
||||||
|
return ageGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AgeGroup ageGroup = new AgeGroup();
|
||||||
|
ageGroup.Id = game.Id;
|
||||||
|
ageGroup.GameId = game.Id;
|
||||||
|
ageGroup.AgeGroupId = null;
|
||||||
|
|
||||||
|
return ageGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AgeGroup
|
||||||
|
{
|
||||||
|
public long? Id { get; set; }
|
||||||
|
public long? GameId { get; set; }
|
||||||
|
public AgeRestrictionGroupings? AgeGroupId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Dictionary<AgeRestrictionGroupings, List<AgeGroupItem>> AgeGroupings
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new Dictionary<AgeRestrictionGroupings, List<AgeGroupItem>>{
|
||||||
|
{
|
||||||
|
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.Teen, new List<AgeGroupItem>{ Teen_Item, Child_Item }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AgeRestrictionGroupings.Child, new List<AgeGroupItem>{ Child_Item }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Dictionary<AgeRestrictionGroupings, AgeGroupItem> AgeGroupingsFlat
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new Dictionary<AgeRestrictionGroupings, AgeGroupItem>{
|
||||||
|
{
|
||||||
|
AgeRestrictionGroupings.Adult, Adult_Item
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AgeRestrictionGroupings.Mature, Mature_Item
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AgeRestrictionGroupings.Teen, Teen_Item
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AgeRestrictionGroupings.Child, Child_Item
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AgeRestrictionGroupings
|
||||||
|
{
|
||||||
|
Adult = 4,
|
||||||
|
Mature = 3,
|
||||||
|
Teen = 2,
|
||||||
|
Child = 1,
|
||||||
|
Unclassified = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<string> RatingsBoards
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
List<string> boards = new List<string>{
|
||||||
|
"ACB", "CERO", "CLASS_IND", "ESRB", "GRAC", "PEGI", "USK"
|
||||||
|
};
|
||||||
|
|
||||||
|
return boards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly static AgeGroupItem Adult_Item = new AgeGroupItem{
|
||||||
|
ACB = new List<AgeRatingTitle>{ AgeRatingTitle.ACB_R18, AgeRatingTitle.ACB_RC },
|
||||||
|
CERO = new List<AgeRatingTitle>{ AgeRatingTitle.CERO_Z },
|
||||||
|
CLASS_IND = new List<AgeRatingTitle>{ AgeRatingTitle.CLASS_IND_Eighteen },
|
||||||
|
ESRB = new List<AgeRatingTitle>{ AgeRatingTitle.RP, AgeRatingTitle.AO },
|
||||||
|
GRAC = new List<AgeRatingTitle>{ AgeRatingTitle.GRAC_Eighteen },
|
||||||
|
PEGI = new List<AgeRatingTitle>{ AgeRatingTitle.Eighteen},
|
||||||
|
USK = new List<AgeRatingTitle>{ AgeRatingTitle.USK_18}
|
||||||
|
};
|
||||||
|
|
||||||
|
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 },
|
||||||
|
CLASS_IND = new List<AgeRatingTitle>{ AgeRatingTitle.CLASS_IND_Sixteen },
|
||||||
|
ESRB = new List<AgeRatingTitle>{ AgeRatingTitle.M },
|
||||||
|
GRAC = new List<AgeRatingTitle>{ AgeRatingTitle.GRAC_Fifteen },
|
||||||
|
PEGI = new List<AgeRatingTitle>{ AgeRatingTitle.Sixteen},
|
||||||
|
USK = new List<AgeRatingTitle>{ AgeRatingTitle.USK_16}
|
||||||
|
};
|
||||||
|
|
||||||
|
readonly static AgeGroupItem Teen_Item = new AgeGroupItem{
|
||||||
|
ACB = new List<AgeRatingTitle>{ AgeRatingTitle.ACB_PG },
|
||||||
|
CERO = new List<AgeRatingTitle>{ AgeRatingTitle.CERO_B },
|
||||||
|
CLASS_IND = new List<AgeRatingTitle>{ AgeRatingTitle.CLASS_IND_Twelve, AgeRatingTitle.CLASS_IND_Fourteen },
|
||||||
|
ESRB = new List<AgeRatingTitle>{ AgeRatingTitle.T },
|
||||||
|
GRAC = new List<AgeRatingTitle>{ AgeRatingTitle.GRAC_Twelve },
|
||||||
|
PEGI = new List<AgeRatingTitle>{ AgeRatingTitle.Twelve},
|
||||||
|
USK = new List<AgeRatingTitle>{ AgeRatingTitle.USK_12}
|
||||||
|
};
|
||||||
|
|
||||||
|
readonly static AgeGroupItem Child_Item = new AgeGroupItem{
|
||||||
|
ACB = new List<AgeRatingTitle>{ AgeRatingTitle.ACB_G },
|
||||||
|
CERO = new List<AgeRatingTitle>{ AgeRatingTitle.CERO_A },
|
||||||
|
CLASS_IND = new List<AgeRatingTitle>{ AgeRatingTitle.CLASS_IND_L, AgeRatingTitle.CLASS_IND_Ten },
|
||||||
|
ESRB = new List<AgeRatingTitle>{ AgeRatingTitle.E, AgeRatingTitle.E10 },
|
||||||
|
GRAC = new List<AgeRatingTitle>{ AgeRatingTitle.GRAC_All },
|
||||||
|
PEGI = new List<AgeRatingTitle>{ AgeRatingTitle.Three, AgeRatingTitle.Seven},
|
||||||
|
USK = new List<AgeRatingTitle>{ AgeRatingTitle.USK_0, AgeRatingTitle.USK_6}
|
||||||
|
};
|
||||||
|
|
||||||
|
public class AgeGroupItem
|
||||||
|
{
|
||||||
|
public List<IGDB.Models.AgeRatingTitle> ACB { get; set; }
|
||||||
|
public List<IGDB.Models.AgeRatingTitle> CERO { get; set; }
|
||||||
|
public List<IGDB.Models.AgeRatingTitle> CLASS_IND { get; set; }
|
||||||
|
public List<IGDB.Models.AgeRatingTitle> ESRB { get; set; }
|
||||||
|
public List<IGDB.Models.AgeRatingTitle> GRAC { get; set; }
|
||||||
|
public List<IGDB.Models.AgeRatingTitle> PEGI { get; set; }
|
||||||
|
public List<IGDB.Models.AgeRatingTitle> USK { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
[Newtonsoft.Json.JsonIgnore]
|
||||||
|
public List<long> AgeGroupItemValues
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
List<long> values = new List<long>();
|
||||||
|
{
|
||||||
|
foreach (AgeRatingTitle ageRatingTitle in ACB)
|
||||||
|
{
|
||||||
|
values.Add((long)ageRatingTitle);
|
||||||
|
}
|
||||||
|
foreach (AgeRatingTitle ageRatingTitle in CERO)
|
||||||
|
{
|
||||||
|
values.Add((long)ageRatingTitle);
|
||||||
|
}
|
||||||
|
foreach (AgeRatingTitle ageRatingTitle in CLASS_IND)
|
||||||
|
{
|
||||||
|
values.Add((long)ageRatingTitle);
|
||||||
|
}
|
||||||
|
foreach (AgeRatingTitle ageRatingTitle in ESRB)
|
||||||
|
{
|
||||||
|
values.Add((long)ageRatingTitle);
|
||||||
|
}
|
||||||
|
foreach (AgeRatingTitle ageRatingTitle in GRAC)
|
||||||
|
{
|
||||||
|
values.Add((long)ageRatingTitle);
|
||||||
|
}
|
||||||
|
foreach (AgeRatingTitle ageRatingTitle in PEGI)
|
||||||
|
{
|
||||||
|
values.Add((long)ageRatingTitle);
|
||||||
|
}
|
||||||
|
foreach (AgeRatingTitle ageRatingTitle in USK)
|
||||||
|
{
|
||||||
|
values.Add((long)ageRatingTitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using gaseous_tools;
|
using System.Text.Json.Serialization;
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
using MySqlX.XDevAPI.Common;
|
using Microsoft.CodeAnalysis.Classification;
|
||||||
using static gaseous_tools.Config.ConfigFile;
|
|
||||||
|
|
||||||
namespace gaseous_server.Classes.Metadata
|
namespace gaseous_server.Classes.Metadata
|
||||||
{
|
{
|
||||||
@@ -16,12 +15,6 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
public static AgeRating? GetAgeRatings(long? Id)
|
public static AgeRating? GetAgeRatings(long? Id)
|
||||||
{
|
{
|
||||||
if ((Id == 0) || (Id == null))
|
if ((Id == 0) || (Id == null))
|
||||||
@@ -77,9 +70,16 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
UpdateSubClasses(returnValue);
|
UpdateSubClasses(returnValue);
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Expired:
|
case Storage.CacheStatus.Expired:
|
||||||
returnValue = await GetObjectFromServer(WhereClause);
|
try
|
||||||
Storage.NewCacheValue(returnValue, true);
|
{
|
||||||
UpdateSubClasses(returnValue);
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<AgeRating>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
returnValue = Storage.GetCacheValue<AgeRating>(returnValue, "id", (long)searchValue);
|
returnValue = Storage.GetCacheValue<AgeRating>(returnValue, "id", (long)searchValue);
|
||||||
@@ -111,7 +111,8 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
private static async Task<AgeRating> GetObjectFromServer(string WhereClause)
|
private static async Task<AgeRating> GetObjectFromServer(string WhereClause)
|
||||||
{
|
{
|
||||||
// get AgeRatings metadata
|
// get AgeRatings metadata
|
||||||
var results = await igdb.QueryAsync<AgeRating>(IGDBClient.Endpoints.AgeRating, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<AgeRating>(IGDBClient.Endpoints.AgeRating, fieldList, WhereClause);
|
||||||
var result = results.First();
|
var result = results.First();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -147,6 +148,44 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
public AgeRatingTitle RatingTitle { get; set; }
|
public AgeRatingTitle RatingTitle { get; set; }
|
||||||
public string[] Descriptions { get; set; }
|
public string[] Descriptions { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void PopulateAgeMap()
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "DELETE FROM ClassificationMap;";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
db.ExecuteNonQuery(sql);
|
||||||
|
|
||||||
|
// loop all age groups
|
||||||
|
foreach(KeyValuePair<AgeGroups.AgeRestrictionGroupings, AgeGroups.AgeGroupItem> ageGrouping in AgeGroups.AgeGroupingsFlat)
|
||||||
|
{
|
||||||
|
AgeGroups.AgeGroupItem ageGroupItem = ageGrouping.Value;
|
||||||
|
var properties = ageGroupItem.GetType().GetProperties();
|
||||||
|
foreach (var prop in properties)
|
||||||
|
{
|
||||||
|
if (prop.GetGetMethod() != null)
|
||||||
|
{
|
||||||
|
List<string> AgeRatingCategories = new List<string>(Enum.GetNames(typeof(AgeRatingCategory)));
|
||||||
|
if (AgeRatingCategories.Contains(prop.Name))
|
||||||
|
{
|
||||||
|
AgeRatingCategory ageRatingCategory = (AgeRatingCategory)Enum.Parse(typeof(AgeRatingCategory), prop.Name);
|
||||||
|
List<AgeRatingTitle> ageRatingTitles = (List<AgeRatingTitle>)prop.GetValue(ageGroupItem);
|
||||||
|
|
||||||
|
foreach (AgeRatingTitle ageRatingTitle in ageRatingTitles)
|
||||||
|
{
|
||||||
|
dbDict.Clear();
|
||||||
|
dbDict.Add("AgeGroupId", ageGrouping.Key);
|
||||||
|
dbDict.Add("ClassificationBoardId", ageRatingCategory);
|
||||||
|
dbDict.Add("RatingId", ageRatingTitle);
|
||||||
|
|
||||||
|
sql = "INSERT INTO ClassificationMap (AgeGroupId, ClassificationBoardId, RatingId) VALUES (@AgeGroupId, @ClassificationBoardId, @RatingId);";
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
using MySqlX.XDevAPI.Common;
|
|
||||||
using static gaseous_tools.Config.ConfigFile;
|
|
||||||
|
|
||||||
namespace gaseous_server.Classes.Metadata
|
namespace gaseous_server.Classes.Metadata
|
||||||
{
|
{
|
||||||
@@ -15,12 +13,6 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
public static AgeRatingContentDescription? GetAgeRatingContentDescriptions(long? Id)
|
public static AgeRatingContentDescription? GetAgeRatingContentDescriptions(long? Id)
|
||||||
{
|
{
|
||||||
if ((Id == 0) || (Id == null))
|
if ((Id == 0) || (Id == null))
|
||||||
@@ -75,8 +67,16 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
Storage.NewCacheValue(returnValue);
|
Storage.NewCacheValue(returnValue);
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Expired:
|
case Storage.CacheStatus.Expired:
|
||||||
returnValue = await GetObjectFromServer(WhereClause);
|
try
|
||||||
Storage.NewCacheValue(returnValue, true);
|
{
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<AgeRatingContentDescription>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
returnValue = Storage.GetCacheValue<AgeRatingContentDescription>(returnValue, "id", (long)searchValue);
|
returnValue = Storage.GetCacheValue<AgeRatingContentDescription>(returnValue, "id", (long)searchValue);
|
||||||
@@ -97,7 +97,8 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
private static async Task<AgeRatingContentDescription> GetObjectFromServer(string WhereClause)
|
private static async Task<AgeRatingContentDescription> GetObjectFromServer(string WhereClause)
|
||||||
{
|
{
|
||||||
// get AgeRatingContentDescriptionContentDescriptions metadata
|
// get AgeRatingContentDescriptionContentDescriptions metadata
|
||||||
var results = await igdb.QueryAsync<AgeRatingContentDescription>(IGDBClient.Endpoints.AgeRatingContentDescriptions, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<AgeRatingContentDescription>(IGDBClient.Endpoints.AgeRatingContentDescriptions, fieldList, WhereClause);
|
||||||
var result = results.First();
|
var result = results.First();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
using MySqlX.XDevAPI.Common;
|
|
||||||
using static gaseous_tools.Config.ConfigFile;
|
|
||||||
|
|
||||||
namespace gaseous_server.Classes.Metadata
|
namespace gaseous_server.Classes.Metadata
|
||||||
{
|
{
|
||||||
@@ -15,12 +13,6 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
public static AlternativeName? GetAlternativeNames(long? Id)
|
public static AlternativeName? GetAlternativeNames(long? Id)
|
||||||
{
|
{
|
||||||
if ((Id == 0) || (Id == null))
|
if ((Id == 0) || (Id == null))
|
||||||
@@ -75,8 +67,16 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
Storage.NewCacheValue(returnValue);
|
Storage.NewCacheValue(returnValue);
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Expired:
|
case Storage.CacheStatus.Expired:
|
||||||
returnValue = await GetObjectFromServer(WhereClause);
|
try
|
||||||
Storage.NewCacheValue(returnValue, true);
|
{
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<AlternativeName>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
returnValue = Storage.GetCacheValue<AlternativeName>(returnValue, "id", (long)searchValue);
|
returnValue = Storage.GetCacheValue<AlternativeName>(returnValue, "id", (long)searchValue);
|
||||||
@@ -97,7 +97,8 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
private static async Task<AlternativeName> GetObjectFromServer(string WhereClause)
|
private static async Task<AlternativeName> GetObjectFromServer(string WhereClause)
|
||||||
{
|
{
|
||||||
// get AlternativeNames metadata
|
// get AlternativeNames metadata
|
||||||
var results = await igdb.QueryAsync<AlternativeName>(IGDBClient.Endpoints.AlternativeNames, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<AlternativeName>(IGDBClient.Endpoints.AlternativeNames, fieldList, WhereClause);
|
||||||
var result = results.First();
|
var result = results.First();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
using MySqlX.XDevAPI.Common;
|
|
||||||
using static gaseous_tools.Config.ConfigFile;
|
|
||||||
|
|
||||||
namespace gaseous_server.Classes.Metadata
|
namespace gaseous_server.Classes.Metadata
|
||||||
{
|
{
|
||||||
@@ -15,13 +13,7 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
public static Artwork? GetArtwork(long? Id, string ImagePath, bool GetImages)
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
public static Artwork? GetArtwork(long? Id, string LogoPath)
|
|
||||||
{
|
{
|
||||||
if ((Id == 0) || (Id == null))
|
if ((Id == 0) || (Id == null))
|
||||||
{
|
{
|
||||||
@@ -29,18 +21,18 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Task<Artwork> RetVal = _GetArtwork(SearchUsing.id, Id, LogoPath);
|
Task<Artwork> RetVal = _GetArtwork(SearchUsing.id, Id, ImagePath, GetImages);
|
||||||
return RetVal.Result;
|
return RetVal.Result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Artwork GetArtwork(string Slug, string LogoPath)
|
public static Artwork GetArtwork(string Slug, string ImagePath, bool GetImages)
|
||||||
{
|
{
|
||||||
Task<Artwork> RetVal = _GetArtwork(SearchUsing.slug, Slug, LogoPath);
|
Task<Artwork> RetVal = _GetArtwork(SearchUsing.slug, Slug, ImagePath, GetImages);
|
||||||
return RetVal.Result;
|
return RetVal.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<Artwork> _GetArtwork(SearchUsing searchUsing, object searchValue, string LogoPath)
|
private static async Task<Artwork> _GetArtwork(SearchUsing searchUsing, object searchValue, string ImagePath, bool GetImages = true)
|
||||||
{
|
{
|
||||||
// check database first
|
// check database first
|
||||||
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
||||||
@@ -69,18 +61,26 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
|
|
||||||
Artwork returnValue = new Artwork();
|
Artwork returnValue = new Artwork();
|
||||||
bool forceImageDownload = false;
|
bool forceImageDownload = false;
|
||||||
LogoPath = Path.Combine(LogoPath, "Artwork");
|
ImagePath = Path.Combine(ImagePath, "Artwork");
|
||||||
switch (cacheStatus)
|
switch (cacheStatus)
|
||||||
{
|
{
|
||||||
case Storage.CacheStatus.NotPresent:
|
case Storage.CacheStatus.NotPresent:
|
||||||
returnValue = await GetObjectFromServer(WhereClause, LogoPath);
|
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:
|
||||||
returnValue = await GetObjectFromServer(WhereClause, LogoPath);
|
try
|
||||||
Storage.NewCacheValue(returnValue, true);
|
{
|
||||||
forceImageDownload = true;
|
returnValue = await GetObjectFromServer(WhereClause, ImagePath);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
forceImageDownload = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<Artwork>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
returnValue = Storage.GetCacheValue<Artwork>(returnValue, "id", (long)searchValue);
|
returnValue = Storage.GetCacheValue<Artwork>(returnValue, "id", (long)searchValue);
|
||||||
@@ -89,11 +89,17 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
throw new Exception("How did you get here?");
|
throw new Exception("How did you get here?");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!File.Exists(Path.Combine(LogoPath, returnValue.ImageId + ".jpg"))) || forceImageDownload == true)
|
// check for presence of "original" quality file - download if absent or force download is true
|
||||||
|
string localFile = Path.Combine(ImagePath, Communications.IGDBAPI_ImageSize.original.ToString(), returnValue.ImageId + ".jpg");
|
||||||
|
if (GetImages == true)
|
||||||
{
|
{
|
||||||
//GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_thumb, returnValue.ImageId);
|
if ((!File.Exists(localFile)) || forceImageDownload == true)
|
||||||
//GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_logo_med, returnValue.ImageId);
|
{
|
||||||
GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_original, returnValue.ImageId);
|
Logging.Log(Logging.LogType.Information, "Metadata: " + returnValue.GetType().Name, "Artwork download forced.");
|
||||||
|
|
||||||
|
Communications comms = new Communications();
|
||||||
|
comms.GetSpecificImageFromServer(ImagePath, returnValue.ImageId, Communications.IGDBAPI_ImageSize.original, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnValue;
|
return returnValue;
|
||||||
@@ -105,64 +111,15 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
slug
|
slug
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<Artwork> GetObjectFromServer(string WhereClause, string LogoPath)
|
private static async Task<Artwork> GetObjectFromServer(string WhereClause, string ImagePath)
|
||||||
{
|
{
|
||||||
// get Artwork metadata
|
// get Artwork metadata
|
||||||
var results = await igdb.QueryAsync<Artwork>(IGDBClient.Endpoints.Artworks, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<Artwork>(IGDBClient.Endpoints.Artworks, fieldList, WhereClause);
|
||||||
var result = results.First();
|
var result = results.First();
|
||||||
|
|
||||||
//GetImageFromServer(result.Url, LogoPath, LogoSize.t_thumb, result.ImageId);
|
|
||||||
//GetImageFromServer(result.Url, LogoPath, LogoSize.t_logo_med, result.ImageId);
|
|
||||||
GetImageFromServer(result.Url, LogoPath, LogoSize.t_original, result.ImageId);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GetImageFromServer(string Url, string LogoPath, LogoSize logoSize, string ImageId)
|
|
||||||
{
|
|
||||||
using (var client = new HttpClient())
|
|
||||||
{
|
|
||||||
string fileName = "Artwork.jpg";
|
|
||||||
string extension = "jpg";
|
|
||||||
switch (logoSize)
|
|
||||||
{
|
|
||||||
case LogoSize.t_thumb:
|
|
||||||
fileName = "_Thumb";
|
|
||||||
extension = "jpg";
|
|
||||||
break;
|
|
||||||
case LogoSize.t_logo_med:
|
|
||||||
fileName = "_Medium";
|
|
||||||
extension = "png";
|
|
||||||
break;
|
|
||||||
case LogoSize.t_original:
|
|
||||||
fileName = "";
|
|
||||||
extension = "png";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fileName = "Artwork";
|
|
||||||
extension = "jpg";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fileName = ImageId + fileName;
|
|
||||||
string imageUrl = Url.Replace(LogoSize.t_thumb.ToString(), logoSize.ToString()).Replace("jpg", extension);
|
|
||||||
|
|
||||||
using (var s = client.GetStreamAsync("https:" + imageUrl))
|
|
||||||
{
|
|
||||||
if (!Directory.Exists(LogoPath)) { Directory.CreateDirectory(LogoPath); }
|
|
||||||
using (var fs = new FileStream(Path.Combine(LogoPath, fileName + "." + extension), FileMode.OpenOrCreate))
|
|
||||||
{
|
|
||||||
s.Result.CopyTo(fs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum LogoSize
|
|
||||||
{
|
|
||||||
t_thumb,
|
|
||||||
t_logo_med,
|
|
||||||
t_original
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
using MySqlX.XDevAPI.Common;
|
|
||||||
using static gaseous_tools.Config.ConfigFile;
|
|
||||||
|
|
||||||
namespace gaseous_server.Classes.Metadata
|
namespace gaseous_server.Classes.Metadata
|
||||||
{
|
{
|
||||||
@@ -15,12 +13,6 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
public static Collection? GetCollections(long? Id)
|
public static Collection? GetCollections(long? Id)
|
||||||
{
|
{
|
||||||
if ((Id == 0) || (Id == null))
|
if ((Id == 0) || (Id == null))
|
||||||
@@ -75,8 +67,16 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
Storage.NewCacheValue(returnValue);
|
Storage.NewCacheValue(returnValue);
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Expired:
|
case Storage.CacheStatus.Expired:
|
||||||
returnValue = await GetObjectFromServer(WhereClause);
|
try
|
||||||
Storage.NewCacheValue(returnValue, true);
|
{
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<Collection>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
returnValue = Storage.GetCacheValue<Collection>(returnValue, "id", (long)searchValue);
|
returnValue = Storage.GetCacheValue<Collection>(returnValue, "id", (long)searchValue);
|
||||||
@@ -97,7 +97,8 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
private static async Task<Collection> GetObjectFromServer(string WhereClause)
|
private static async Task<Collection> GetObjectFromServer(string WhereClause)
|
||||||
{
|
{
|
||||||
// get Collections metadata
|
// get Collections metadata
|
||||||
var results = await igdb.QueryAsync<Collection>(IGDBClient.Endpoints.Collections, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<Collection>(IGDBClient.Endpoints.Collections, fieldList, WhereClause);
|
||||||
var result = results.First();
|
var result = results.First();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
577
gaseous-server/Classes/Metadata/Communications.cs
Normal file
577
gaseous-server/Classes/Metadata/Communications.cs
Normal file
@@ -0,0 +1,577 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Data;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Net;
|
||||||
|
using Humanizer;
|
||||||
|
using IGDB;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using RestEase;
|
||||||
|
|
||||||
|
namespace gaseous_server.Classes.Metadata
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles all metadata API communications
|
||||||
|
/// </summary>
|
||||||
|
public class Communications
|
||||||
|
{
|
||||||
|
static Communications()
|
||||||
|
{
|
||||||
|
var handler = new HttpClientHandler();
|
||||||
|
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
|
||||||
|
client = new HttpClient(handler);
|
||||||
|
|
||||||
|
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
|
||||||
|
client.DefaultRequestHeaders.Add("Accept-Encoding", "deflate");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IGDBClient igdb = new IGDBClient(
|
||||||
|
// Found in Twitch Developer portal for your app
|
||||||
|
Config.IGDB.ClientId,
|
||||||
|
Config.IGDB.Secret
|
||||||
|
);
|
||||||
|
|
||||||
|
private static HttpClient client = new HttpClient();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configure metadata API communications
|
||||||
|
/// </summary>
|
||||||
|
public static HasheousClient.Models.MetadataModel.MetadataSources MetadataSource
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _MetadataSource;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_MetadataSource = value;
|
||||||
|
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case HasheousClient.Models.MetadataModel.MetadataSources.IGDB:
|
||||||
|
// set rate limiter avoidance values
|
||||||
|
RateLimitAvoidanceWait = 1500;
|
||||||
|
RateLimitAvoidanceThreshold = 3;
|
||||||
|
RateLimitAvoidancePeriod = 1;
|
||||||
|
|
||||||
|
// set rate limiter recovery values
|
||||||
|
RateLimitRecoveryWaitTime = 10000;
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// leave all values at default
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static HasheousClient.Models.MetadataModel.MetadataSources _MetadataSource = HasheousClient.Models.MetadataModel.MetadataSources.None;
|
||||||
|
|
||||||
|
// rate limit avoidance - what can we do to ensure that rate limiting is avoided?
|
||||||
|
// these values affect all communications
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long to wait to avoid hitting an API rate limiter
|
||||||
|
/// </summary>
|
||||||
|
private static int RateLimitAvoidanceWait = 2000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How many API calls in the period are allowed before we start introducing a wait
|
||||||
|
/// </summary>
|
||||||
|
private static int RateLimitAvoidanceThreshold = 80;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A counter of API calls since the beginning of the period
|
||||||
|
/// </summary>
|
||||||
|
private static int RateLimitAvoidanceCallCount = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How large the period (in seconds) to measure API call counts against
|
||||||
|
/// </summary>
|
||||||
|
private static int RateLimitAvoidancePeriod = 60;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The start of the rate limit avoidance period
|
||||||
|
/// </summary>
|
||||||
|
private static DateTime RateLimitAvoidanceStartTime = DateTime.UtcNow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to determine if we're already in rate limit avoidance mode - always query "InRateLimitAvoidanceMode"
|
||||||
|
/// for up to date mode status.
|
||||||
|
/// This bool is used to track status changes and should not be relied upon for current status.
|
||||||
|
/// </summary>
|
||||||
|
private static bool InRateLimitAvoidanceModeStatus = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determine if we're in rate limit avoidance mode.
|
||||||
|
/// </summary>
|
||||||
|
private static bool InRateLimitAvoidanceMode
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (RateLimitAvoidanceStartTime.AddSeconds(RateLimitAvoidancePeriod) <= DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
// avoidance period has expired - reset
|
||||||
|
RateLimitAvoidanceCallCount = 0;
|
||||||
|
RateLimitAvoidanceStartTime = DateTime.UtcNow;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// we're in the avoidance period
|
||||||
|
if (RateLimitAvoidanceCallCount > RateLimitAvoidanceThreshold)
|
||||||
|
{
|
||||||
|
// the number of call counts indicates we should throttle things a bit
|
||||||
|
if (InRateLimitAvoidanceModeStatus == false)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "API Connection", "Entered rate limit avoidance period, API calls will be throttled by " + RateLimitAvoidanceWait + " milliseconds.");
|
||||||
|
InRateLimitAvoidanceModeStatus = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// still in full speed mode - no throttle required
|
||||||
|
if (InRateLimitAvoidanceModeStatus == true)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "API Connection", "Exited rate limit avoidance period, API call rate is returned to full speed.");
|
||||||
|
InRateLimitAvoidanceModeStatus = false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rate limit handling - how long to wait to allow the server to recover and try again
|
||||||
|
// these values affect ALL communications if a 429 response code is received
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long to wait (in milliseconds) if a 429 status code is received before trying again
|
||||||
|
/// </summary>
|
||||||
|
private static int RateLimitRecoveryWaitTime = 10000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time when normal communications can attempt to be resumed
|
||||||
|
/// </summary>
|
||||||
|
private static DateTime RateLimitResumeTime = DateTime.UtcNow.AddMinutes(5 * -1);
|
||||||
|
|
||||||
|
// rate limit retry - how many times to retry before aborting
|
||||||
|
private int RetryAttempts = 0;
|
||||||
|
private int RetryAttemptsMax = 3;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Request data from the metadata API
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of object to return</typeparam>
|
||||||
|
/// <param name="Endpoint">API endpoint segment to use</param>
|
||||||
|
/// <param name="Fields">Fields to request from the API</param>
|
||||||
|
/// <param name="Query">Selection criteria for data to request</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<T[]?> APIComm<T>(string Endpoint, string Fields, string Query)
|
||||||
|
{
|
||||||
|
switch (_MetadataSource)
|
||||||
|
{
|
||||||
|
case HasheousClient.Models.MetadataModel.MetadataSources.None:
|
||||||
|
return null;
|
||||||
|
case HasheousClient.Models.MetadataModel.MetadataSources.IGDB:
|
||||||
|
return await IGDBAPI<T>(Endpoint, Fields, Query);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<T[]> IGDBAPI<T>(string Endpoint, string Fields, string Query)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Debug, "API Connection", "Accessing API for endpoint: " + Endpoint);
|
||||||
|
|
||||||
|
if (RateLimitResumeTime > DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "API Connection", "IGDB rate limit hit. Pausing API communications until " + RateLimitResumeTime.ToString() + ". Attempt " + RetryAttempts + " of " + RetryAttemptsMax + " retries.");
|
||||||
|
Thread.Sleep(RateLimitRecoveryWaitTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (InRateLimitAvoidanceMode == true)
|
||||||
|
{
|
||||||
|
// sleep for a moment to help avoid hitting the rate limiter
|
||||||
|
Thread.Sleep(RateLimitAvoidanceWait);
|
||||||
|
}
|
||||||
|
|
||||||
|
// perform the actual API call
|
||||||
|
var results = await igdb.QueryAsync<T>(Endpoint, query: Fields + " " + Query + ";");
|
||||||
|
|
||||||
|
// increment rate limiter avoidance call count
|
||||||
|
RateLimitAvoidanceCallCount += 1;
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
catch (ApiException apiEx)
|
||||||
|
{
|
||||||
|
switch (apiEx.StatusCode)
|
||||||
|
{
|
||||||
|
case HttpStatusCode.TooManyRequests:
|
||||||
|
if (RetryAttempts >= RetryAttemptsMax)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "API Connection", "IGDB rate limiter attempts expired. Aborting.", apiEx);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "API Connection", "IGDB API rate limit hit while accessing endpoint " + Endpoint, apiEx);
|
||||||
|
|
||||||
|
RetryAttempts += 1;
|
||||||
|
|
||||||
|
return await IGDBAPI<T>(Endpoint, Fields, Query);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
Logging.Log(Logging.LogType.Warning, "API Connection", "Exception when accessing endpoint " + Endpoint, apiEx);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "API Connection", "Exception when accessing endpoint " + Endpoint, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Download from the specified uri
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uri">The uri to download from</param>
|
||||||
|
/// <param name="DestinationFile">The file name and path the download should be stored as</param>
|
||||||
|
public Task<bool?> DownloadFile(Uri uri, string DestinationFile)
|
||||||
|
{
|
||||||
|
var result = _DownloadFile(uri, DestinationFile);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool?> _DownloadFile(Uri uri, string DestinationFile)
|
||||||
|
{
|
||||||
|
string DestinationDirectory = new FileInfo(DestinationFile).Directory.FullName;
|
||||||
|
if (!Directory.Exists(DestinationDirectory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(DestinationDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Information, "Communications", "Downloading from " + uri.ToString() + " to " + DestinationFile);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (HttpResponseMessage response = client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).Result)
|
||||||
|
{
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
using (Stream contentStream = await response.Content.ReadAsStreamAsync(), fileStream = new FileStream(DestinationFile, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
|
||||||
|
{
|
||||||
|
var totalRead = 0L;
|
||||||
|
var totalReads = 0L;
|
||||||
|
var buffer = new byte[8192];
|
||||||
|
var isMoreToRead = true;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
var read = await contentStream.ReadAsync(buffer, 0, buffer.Length);
|
||||||
|
if (read == 0)
|
||||||
|
{
|
||||||
|
isMoreToRead = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await fileStream.WriteAsync(buffer, 0, read);
|
||||||
|
|
||||||
|
totalRead += read;
|
||||||
|
totalReads += 1;
|
||||||
|
|
||||||
|
if (totalReads % 2000 == 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine(string.Format("total bytes downloaded so far: {0:n0}", totalRead));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (isMoreToRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
if (ex.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
if (File.Exists(DestinationFile))
|
||||||
|
{
|
||||||
|
FileInfo fi = new FileInfo(DestinationFile);
|
||||||
|
if (fi.Length == 0)
|
||||||
|
{
|
||||||
|
File.Delete(DestinationFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Download Images", "Error downloading file: ", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetSpecificImageFromServer(string ImagePath, string ImageId, IGDBAPI_ImageSize size, List<IGDBAPI_ImageSize>? FallbackSizes = null)
|
||||||
|
{
|
||||||
|
string returnPath = "";
|
||||||
|
|
||||||
|
// check for artificial sizes first
|
||||||
|
switch (size)
|
||||||
|
{
|
||||||
|
case IGDBAPI_ImageSize.screenshot_small:
|
||||||
|
case IGDBAPI_ImageSize.screenshot_thumb:
|
||||||
|
string BasePath = Path.Combine(ImagePath, size.ToString());
|
||||||
|
if (!Directory.Exists(BasePath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(BasePath);
|
||||||
|
}
|
||||||
|
returnPath = Path.Combine(BasePath, ImageId + ".jpg");
|
||||||
|
if (!File.Exists(returnPath))
|
||||||
|
{
|
||||||
|
// get original size image and resize
|
||||||
|
string originalSizePath = await GetSpecificImageFromServer(ImagePath, ImageId, IGDBAPI_ImageSize.original, null);
|
||||||
|
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
|
||||||
|
switch (size)
|
||||||
|
{
|
||||||
|
case IGDBAPI_ImageSize.screenshot_small:
|
||||||
|
// 235x128
|
||||||
|
width = 235;
|
||||||
|
height = 128;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IGDBAPI_ImageSize.screenshot_thumb:
|
||||||
|
// 165x90
|
||||||
|
width = 165;
|
||||||
|
height = 90;
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var image = new ImageMagick.MagickImage(originalSizePath))
|
||||||
|
{
|
||||||
|
image.Resize(width, height);
|
||||||
|
image.Strip();
|
||||||
|
image.Write(returnPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// these sizes are IGDB native
|
||||||
|
if (RateLimitResumeTime > DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "API Connection", "IGDB rate limit hit. Pausing API communications until " + RateLimitResumeTime.ToString() + ". Attempt " + RetryAttempts + " of " + RetryAttemptsMax + " retries.");
|
||||||
|
Thread.Sleep(RateLimitRecoveryWaitTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (InRateLimitAvoidanceMode == true)
|
||||||
|
{
|
||||||
|
// sleep for a moment to help avoid hitting the rate limiter
|
||||||
|
Thread.Sleep(RateLimitAvoidanceWait);
|
||||||
|
}
|
||||||
|
|
||||||
|
Communications comms = new Communications();
|
||||||
|
List<IGDBAPI_ImageSize> imageSizes = new List<IGDBAPI_ImageSize>
|
||||||
|
{
|
||||||
|
size
|
||||||
|
};
|
||||||
|
|
||||||
|
// get the image
|
||||||
|
try
|
||||||
|
{
|
||||||
|
returnPath = Path.Combine(ImagePath, size.ToString(), ImageId + ".jpg");
|
||||||
|
|
||||||
|
// fail early if the file is already downloaded
|
||||||
|
if (!File.Exists(returnPath))
|
||||||
|
{
|
||||||
|
await comms.IGDBAPI_GetImage(imageSizes, ImageId, ImagePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
if (ex.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Image Download", "Image not found, trying a different size.");
|
||||||
|
|
||||||
|
if (FallbackSizes != null)
|
||||||
|
{
|
||||||
|
foreach (Communications.IGDBAPI_ImageSize imageSize in FallbackSizes)
|
||||||
|
{
|
||||||
|
returnPath = await GetSpecificImageFromServer(ImagePath, ImageId, imageSize, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// increment rate limiter avoidance call count
|
||||||
|
RateLimitAvoidanceCallCount += 1;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T? GetSearchCache<T>(string SearchFields, string SearchString)
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "SELECT * FROM SearchCache WHERE SearchFields = @searchfields AND SearchString = @searchstring;";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "searchfields", SearchFields },
|
||||||
|
{ "searchstring", SearchString }
|
||||||
|
};
|
||||||
|
DataTable data = db.ExecuteCMD(sql, dbDict);
|
||||||
|
if (data.Rows.Count > 0)
|
||||||
|
{
|
||||||
|
// cache hit
|
||||||
|
string rawString = data.Rows[0]["Content"].ToString();
|
||||||
|
T ReturnValue = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(rawString);
|
||||||
|
if (ReturnValue != null)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Search Cache", "Found search result in cache. Search string: " + SearchString);
|
||||||
|
return ReturnValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Search Cache", "Search result not found in cache.");
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// cache miss
|
||||||
|
Logging.Log(Logging.LogType.Information, "Search Cache", "Search result not found in cache.");
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetSearchCache<T>(string SearchFields, string SearchString, T SearchResult)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Search Cache", "Storing search results in cache. Search string: " + SearchString);
|
||||||
|
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "INSERT INTO SearchCache (SearchFields, SearchString, Content, LastSearch) VALUES (@searchfields, @searchstring, @content, @lastsearch);";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "searchfields", SearchFields },
|
||||||
|
{ "searchstring", SearchString },
|
||||||
|
{ "content", Newtonsoft.Json.JsonConvert.SerializeObject(SearchResult) },
|
||||||
|
{ "lastsearch", DateTime.UtcNow }
|
||||||
|
};
|
||||||
|
db.ExecuteNonQuery(sql, dbDict);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// See https://api-docs.igdb.com/?javascript#images for more information about the image url structure
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ImageId"></param>
|
||||||
|
/// <param name="outputPath">The path to save the downloaded files to
|
||||||
|
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";
|
||||||
|
|
||||||
|
foreach (IGDBAPI_ImageSize ImageSize in ImageSizes)
|
||||||
|
{
|
||||||
|
string url = urlTemplate.Replace("{size}", Common.GetDescription(ImageSize)).Replace("{hash}", ImageId);
|
||||||
|
string newOutputPath = Path.Combine(OutputPath, Common.GetDescription(ImageSize));
|
||||||
|
string OutputFile = ImageId + ".jpg";
|
||||||
|
string fullPath = Path.Combine(newOutputPath, OutputFile);
|
||||||
|
|
||||||
|
await _DownloadFile(new Uri(url), fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum IGDBAPI_ImageSize
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 90x128 Fit
|
||||||
|
/// </summary>
|
||||||
|
[Description("cover_small")]
|
||||||
|
cover_small,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 264x374 Fit
|
||||||
|
/// </summary>
|
||||||
|
[Description("cover_big")]
|
||||||
|
cover_big,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 165x90 Lfill, Centre gravity - resized by Gaseous and is not a real IGDB size
|
||||||
|
/// </summary>
|
||||||
|
[Description("screenshot_thumb")]
|
||||||
|
screenshot_thumb,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 235x128 Lfill, Centre gravity - resized by Gaseous and is not a real IGDB size
|
||||||
|
/// </summary>
|
||||||
|
[Description("screenshot_small")]
|
||||||
|
screenshot_small,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 589x320 Lfill, Centre gravity
|
||||||
|
/// </summary>
|
||||||
|
[Description("screenshot_med")]
|
||||||
|
screenshot_med,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 889x500 Lfill, Centre gravity
|
||||||
|
/// </summary>
|
||||||
|
[Description("screenshot_big")]
|
||||||
|
screenshot_big,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 1280x720 Lfill, Centre gravity
|
||||||
|
/// </summary>
|
||||||
|
[Description("screenshot_huge")]
|
||||||
|
screenshot_huge,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 284x160 Fit
|
||||||
|
/// </summary>
|
||||||
|
[Description("logo_med")]
|
||||||
|
logo_med,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 90x90 Thumb, Centre gravity
|
||||||
|
/// </summary>
|
||||||
|
[Description("thumb")]
|
||||||
|
thumb,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 35x35 Thumb, Centre gravity
|
||||||
|
/// </summary>
|
||||||
|
[Description("micro")]
|
||||||
|
micro,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 1280x720 Fit, Centre gravity
|
||||||
|
/// </summary>
|
||||||
|
[Description("720p")]
|
||||||
|
r720p,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 1920x1080 Fit, Centre gravity
|
||||||
|
/// </summary>
|
||||||
|
[Description("1080p")]
|
||||||
|
r1080p,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The originally uploaded image
|
||||||
|
/// </summary>
|
||||||
|
[Description("original")]
|
||||||
|
original
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
|
|
||||||
@@ -13,12 +12,6 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
public static Company? GetCompanies(long? Id)
|
public static Company? GetCompanies(long? Id)
|
||||||
{
|
{
|
||||||
if ((Id == 0) || (Id == null))
|
if ((Id == 0) || (Id == null))
|
||||||
@@ -74,9 +67,16 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
UpdateSubClasses(returnValue);
|
UpdateSubClasses(returnValue);
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Expired:
|
case Storage.CacheStatus.Expired:
|
||||||
returnValue = await GetObjectFromServer(WhereClause);
|
try
|
||||||
if (returnValue != null) { Storage.NewCacheValue(returnValue, true); }
|
{
|
||||||
UpdateSubClasses(returnValue);
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<Company>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
returnValue = Storage.GetCacheValue<Company>(returnValue, "id", (long)searchValue);
|
returnValue = Storage.GetCacheValue<Company>(returnValue, "id", (long)searchValue);
|
||||||
@@ -105,7 +105,8 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
private static async Task<Company> GetObjectFromServer(string WhereClause)
|
private static async Task<Company> GetObjectFromServer(string WhereClause)
|
||||||
{
|
{
|
||||||
// get Companies metadata
|
// get Companies metadata
|
||||||
var results = await igdb.QueryAsync<Company>(IGDBClient.Endpoints.Companies, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<Company>(IGDBClient.Endpoints.Companies, fieldList, WhereClause);
|
||||||
if (results.Length > 0)
|
if (results.Length > 0)
|
||||||
{
|
{
|
||||||
var result = results.First();
|
var result = results.First();
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
using MySqlX.XDevAPI.Common;
|
|
||||||
using static gaseous_tools.Config.ConfigFile;
|
|
||||||
|
|
||||||
namespace gaseous_server.Classes.Metadata
|
namespace gaseous_server.Classes.Metadata
|
||||||
{
|
{
|
||||||
@@ -15,13 +13,7 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
public static CompanyLogo? GetCompanyLogo(long? Id, string ImagePath)
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
public static CompanyLogo? GetCompanyLogo(long? Id, string LogoPath)
|
|
||||||
{
|
{
|
||||||
if ((Id == 0) || (Id == null))
|
if ((Id == 0) || (Id == null))
|
||||||
{
|
{
|
||||||
@@ -29,18 +21,18 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Task<CompanyLogo> RetVal = _GetCompanyLogo(SearchUsing.id, Id, LogoPath);
|
Task<CompanyLogo> RetVal = _GetCompanyLogo(SearchUsing.id, Id, ImagePath);
|
||||||
return RetVal.Result;
|
return RetVal.Result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CompanyLogo GetCompanyLogo(string Slug, string LogoPath)
|
public static CompanyLogo GetCompanyLogo(string Slug, string ImagePath)
|
||||||
{
|
{
|
||||||
Task<CompanyLogo> RetVal = _GetCompanyLogo(SearchUsing.slug, Slug, LogoPath);
|
Task<CompanyLogo> RetVal = _GetCompanyLogo(SearchUsing.slug, Slug, ImagePath);
|
||||||
return RetVal.Result;
|
return RetVal.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<CompanyLogo> _GetCompanyLogo(SearchUsing searchUsing, object searchValue, string LogoPath)
|
private static async Task<CompanyLogo> _GetCompanyLogo(SearchUsing searchUsing, object searchValue, string ImagePath)
|
||||||
{
|
{
|
||||||
// check database first
|
// check database first
|
||||||
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
||||||
@@ -72,7 +64,7 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
switch (cacheStatus)
|
switch (cacheStatus)
|
||||||
{
|
{
|
||||||
case Storage.CacheStatus.NotPresent:
|
case Storage.CacheStatus.NotPresent:
|
||||||
returnValue = await GetObjectFromServer(WhereClause, LogoPath);
|
returnValue = await GetObjectFromServer(WhereClause, ImagePath);
|
||||||
if (returnValue != null)
|
if (returnValue != null)
|
||||||
{
|
{
|
||||||
Storage.NewCacheValue(returnValue);
|
Storage.NewCacheValue(returnValue);
|
||||||
@@ -80,12 +72,17 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Expired:
|
case Storage.CacheStatus.Expired:
|
||||||
returnValue = await GetObjectFromServer(WhereClause, LogoPath);
|
try
|
||||||
if (returnValue != null)
|
|
||||||
{
|
{
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause, ImagePath);
|
||||||
Storage.NewCacheValue(returnValue, true);
|
Storage.NewCacheValue(returnValue, true);
|
||||||
forceImageDownload = true;
|
forceImageDownload = true;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<CompanyLogo>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
returnValue = Storage.GetCacheValue<CompanyLogo>(returnValue, "id", (long)searchValue);
|
returnValue = Storage.GetCacheValue<CompanyLogo>(returnValue, "id", (long)searchValue);
|
||||||
@@ -94,13 +91,14 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
throw new Exception("How did you get here?");
|
throw new Exception("How did you get here?");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (returnValue != null)
|
// check for presence of "original" quality file - download if absent or force download is true
|
||||||
|
string localFile = Path.Combine(ImagePath, Communications.IGDBAPI_ImageSize.original.ToString(), returnValue.ImageId + ".jpg");
|
||||||
|
if ((!File.Exists(localFile)) || forceImageDownload == true)
|
||||||
{
|
{
|
||||||
if ((!File.Exists(Path.Combine(LogoPath, "Logo.jpg"))) || forceImageDownload == true)
|
Logging.Log(Logging.LogType.Information, "Metadata: " + returnValue.GetType().Name, "Company logo download forced.");
|
||||||
{
|
|
||||||
GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_thumb);
|
Communications comms = new Communications();
|
||||||
GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_logo_med);
|
comms.GetSpecificImageFromServer(ImagePath, returnValue.ImageId, Communications.IGDBAPI_ImageSize.original, null);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnValue;
|
return returnValue;
|
||||||
@@ -112,63 +110,14 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
slug
|
slug
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<CompanyLogo?> GetObjectFromServer(string WhereClause, string LogoPath)
|
private static async Task<CompanyLogo> GetObjectFromServer(string WhereClause, string ImagePath)
|
||||||
{
|
{
|
||||||
// get CompanyLogo metadata
|
// get Artwork metadata
|
||||||
var results = await igdb.QueryAsync<CompanyLogo>(IGDBClient.Endpoints.CompanyLogos, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
if (results.Length > 0)
|
var results = await comms.APIComm<CompanyLogo>(IGDBClient.Endpoints.CompanyLogos, fieldList, WhereClause);
|
||||||
{
|
var result = results.First();
|
||||||
var result = results.First();
|
|
||||||
|
|
||||||
GetImageFromServer(result.Url, LogoPath, LogoSize.t_thumb);
|
return result;
|
||||||
GetImageFromServer(result.Url, LogoPath, LogoSize.t_logo_med);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void GetImageFromServer(string Url, string LogoPath, LogoSize logoSize)
|
|
||||||
{
|
|
||||||
using (var client = new HttpClient())
|
|
||||||
{
|
|
||||||
string fileName = "Logo.jpg";
|
|
||||||
string extension = "jpg";
|
|
||||||
switch (logoSize)
|
|
||||||
{
|
|
||||||
case LogoSize.t_thumb:
|
|
||||||
fileName = "Logo_Thumb";
|
|
||||||
extension = "jpg";
|
|
||||||
break;
|
|
||||||
case LogoSize.t_logo_med:
|
|
||||||
fileName = "Logo_Medium";
|
|
||||||
extension = "png";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fileName = "Logo";
|
|
||||||
extension = "jpg";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
string imageUrl = Url.Replace(LogoSize.t_thumb.ToString(), logoSize.ToString()).Replace("jpg", extension);
|
|
||||||
|
|
||||||
using (var s = client.GetStreamAsync("https:" + imageUrl))
|
|
||||||
{
|
|
||||||
if (!Directory.Exists(LogoPath)) { Directory.CreateDirectory(LogoPath); }
|
|
||||||
using (var fs = new FileStream(Path.Combine(LogoPath, fileName + "." + extension), FileMode.OpenOrCreate))
|
|
||||||
{
|
|
||||||
s.Result.CopyTo(fs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum LogoSize
|
|
||||||
{
|
|
||||||
t_thumb,
|
|
||||||
t_logo_med
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using gaseous_tools;
|
using System.Net;
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
using MySqlX.XDevAPI.Common;
|
using Microsoft.CodeAnalysis.Elfie.Model.Strings;
|
||||||
using static gaseous_tools.Config.ConfigFile;
|
|
||||||
|
|
||||||
namespace gaseous_server.Classes.Metadata
|
namespace gaseous_server.Classes.Metadata
|
||||||
{
|
{
|
||||||
@@ -15,13 +15,7 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
public static Cover? GetCover(long? Id, string ImagePath, bool GetImages)
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
public static Cover? GetCover(long? Id, string LogoPath)
|
|
||||||
{
|
{
|
||||||
if ((Id == 0) || (Id == null))
|
if ((Id == 0) || (Id == null))
|
||||||
{
|
{
|
||||||
@@ -29,18 +23,18 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Task<Cover> RetVal = _GetCover(SearchUsing.id, Id, LogoPath);
|
Task<Cover> RetVal = _GetCover(SearchUsing.id, Id, ImagePath, GetImages);
|
||||||
return RetVal.Result;
|
return RetVal.Result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Cover GetCover(string Slug, string LogoPath)
|
public static Cover GetCover(string Slug, string ImagePath, bool GetImages)
|
||||||
{
|
{
|
||||||
Task<Cover> RetVal = _GetCover(SearchUsing.slug, Slug, LogoPath);
|
Task<Cover> RetVal = _GetCover(SearchUsing.slug, Slug, ImagePath, GetImages);
|
||||||
return RetVal.Result;
|
return RetVal.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<Cover> _GetCover(SearchUsing searchUsing, object searchValue, string LogoPath)
|
private static async Task<Cover> _GetCover(SearchUsing searchUsing, object searchValue, string ImagePath, bool GetImages = true)
|
||||||
{
|
{
|
||||||
// check database first
|
// check database first
|
||||||
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
||||||
@@ -69,17 +63,26 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
|
|
||||||
Cover returnValue = new Cover();
|
Cover returnValue = new Cover();
|
||||||
bool forceImageDownload = false;
|
bool forceImageDownload = false;
|
||||||
|
ImagePath = Path.Combine(ImagePath, "Covers");
|
||||||
switch (cacheStatus)
|
switch (cacheStatus)
|
||||||
{
|
{
|
||||||
case Storage.CacheStatus.NotPresent:
|
case Storage.CacheStatus.NotPresent:
|
||||||
returnValue = await GetObjectFromServer(WhereClause, LogoPath);
|
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:
|
||||||
returnValue = await GetObjectFromServer(WhereClause, LogoPath);
|
try
|
||||||
Storage.NewCacheValue(returnValue, true);
|
{
|
||||||
forceImageDownload = true;
|
returnValue = await GetObjectFromServer(WhereClause, ImagePath);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
forceImageDownload = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<Cover>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
returnValue = Storage.GetCacheValue<Cover>(returnValue, "id", (long)searchValue);
|
returnValue = Storage.GetCacheValue<Cover>(returnValue, "id", (long)searchValue);
|
||||||
@@ -88,11 +91,30 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
throw new Exception("How did you get here?");
|
throw new Exception("How did you get here?");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!File.Exists(Path.Combine(LogoPath, "Cover.jpg"))) || forceImageDownload == true)
|
string localFile = Path.Combine(ImagePath, Communications.IGDBAPI_ImageSize.original.ToString(), returnValue.ImageId + ".jpg");
|
||||||
|
if (GetImages == true)
|
||||||
{
|
{
|
||||||
//GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_thumb, returnValue.ImageId);
|
if ((!File.Exists(localFile)) || forceImageDownload == true)
|
||||||
//GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_logo_med, returnValue.ImageId);
|
{
|
||||||
GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_original, returnValue.ImageId);
|
Logging.Log(Logging.LogType.Information, "Metadata: " + returnValue.GetType().Name, "Cover download forced.");
|
||||||
|
|
||||||
|
// check for presence of image file - download if absent or force download is true
|
||||||
|
List<Communications.IGDBAPI_ImageSize> imageSizes = new List<Communications.IGDBAPI_ImageSize>{
|
||||||
|
Communications.IGDBAPI_ImageSize.cover_big,
|
||||||
|
Communications.IGDBAPI_ImageSize.cover_small,
|
||||||
|
Communications.IGDBAPI_ImageSize.original
|
||||||
|
};
|
||||||
|
|
||||||
|
Communications comms = new Communications();
|
||||||
|
foreach (Communications.IGDBAPI_ImageSize size in imageSizes)
|
||||||
|
{
|
||||||
|
localFile = Path.Combine(ImagePath, size.ToString(), returnValue.ImageId + ".jpg");
|
||||||
|
if ((!File.Exists(localFile)) || forceImageDownload == true)
|
||||||
|
{
|
||||||
|
comms.GetSpecificImageFromServer(ImagePath, returnValue.ImageId, size, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnValue;
|
return returnValue;
|
||||||
@@ -104,63 +126,15 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
slug
|
slug
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<Cover> GetObjectFromServer(string WhereClause, string LogoPath)
|
private static async Task<Cover> GetObjectFromServer(string WhereClause, string ImagePath)
|
||||||
{
|
{
|
||||||
// get Cover metadata
|
// get Cover metadata
|
||||||
var results = await igdb.QueryAsync<Cover>(IGDBClient.Endpoints.Covers, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<Cover>(IGDBClient.Endpoints.Covers, fieldList, WhereClause);
|
||||||
var result = results.First();
|
var result = results.First();
|
||||||
|
|
||||||
//GetImageFromServer(result.Url, LogoPath, LogoSize.t_thumb, result.ImageId);
|
|
||||||
//GetImageFromServer(result.Url, LogoPath, LogoSize.t_logo_med, result.ImageId);
|
|
||||||
GetImageFromServer(result.Url, LogoPath, LogoSize.t_original, result.ImageId);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GetImageFromServer(string Url, string LogoPath, LogoSize logoSize, string ImageId)
|
|
||||||
{
|
|
||||||
using (var client = new HttpClient())
|
|
||||||
{
|
|
||||||
string fileName = "Cover.jpg";
|
|
||||||
string extension = "jpg";
|
|
||||||
switch (logoSize)
|
|
||||||
{
|
|
||||||
case LogoSize.t_thumb:
|
|
||||||
fileName = "Cover_Thumb";
|
|
||||||
extension = "jpg";
|
|
||||||
break;
|
|
||||||
case LogoSize.t_logo_med:
|
|
||||||
fileName = "Cover_Medium";
|
|
||||||
extension = "png";
|
|
||||||
break;
|
|
||||||
case LogoSize.t_original:
|
|
||||||
fileName = "Cover";
|
|
||||||
extension = "png";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fileName = "Cover";
|
|
||||||
extension = "jpg";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
string imageUrl = Url.Replace(LogoSize.t_thumb.ToString(), logoSize.ToString()).Replace("jpg", extension);
|
|
||||||
|
|
||||||
using (var s = client.GetStreamAsync("https:" + imageUrl))
|
|
||||||
{
|
|
||||||
if (!Directory.Exists(LogoPath)) { Directory.CreateDirectory(LogoPath); }
|
|
||||||
using (var fs = new FileStream(Path.Combine(LogoPath, fileName + "." + extension), FileMode.OpenOrCreate))
|
|
||||||
{
|
|
||||||
s.Result.CopyTo(fs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum LogoSize
|
|
||||||
{
|
|
||||||
t_thumb,
|
|
||||||
t_logo_med,
|
|
||||||
t_original
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
using MySqlX.XDevAPI.Common;
|
|
||||||
using static gaseous_tools.Config.ConfigFile;
|
|
||||||
|
|
||||||
namespace gaseous_server.Classes.Metadata
|
namespace gaseous_server.Classes.Metadata
|
||||||
{
|
{
|
||||||
@@ -15,12 +13,6 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
public static ExternalGame? GetExternalGames(long? Id)
|
public static ExternalGame? GetExternalGames(long? Id)
|
||||||
{
|
{
|
||||||
if ((Id == 0) || (Id == null))
|
if ((Id == 0) || (Id == null))
|
||||||
@@ -78,11 +70,16 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Expired:
|
case Storage.CacheStatus.Expired:
|
||||||
returnValue = await GetObjectFromServer(WhereClause);
|
try
|
||||||
if (returnValue != null)
|
|
||||||
{
|
{
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
Storage.NewCacheValue(returnValue, true);
|
Storage.NewCacheValue(returnValue, true);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<ExternalGame>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
returnValue = Storage.GetCacheValue<ExternalGame>(returnValue, "id", (long)searchValue);
|
returnValue = Storage.GetCacheValue<ExternalGame>(returnValue, "id", (long)searchValue);
|
||||||
@@ -103,7 +100,8 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
private static async Task<ExternalGame?> GetObjectFromServer(string WhereClause)
|
private static async Task<ExternalGame?> GetObjectFromServer(string WhereClause)
|
||||||
{
|
{
|
||||||
// get ExternalGames metadata
|
// get ExternalGames metadata
|
||||||
var results = await igdb.QueryAsync<ExternalGame>(IGDBClient.Endpoints.ExternalGames, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<ExternalGame>(IGDBClient.Endpoints.ExternalGames, fieldList, WhereClause);
|
||||||
if (results.Length > 0)
|
if (results.Length > 0)
|
||||||
{
|
{
|
||||||
var result = results.First();
|
var result = results.First();
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
using MySqlX.XDevAPI.Common;
|
|
||||||
using static gaseous_tools.Config.ConfigFile;
|
|
||||||
|
|
||||||
namespace gaseous_server.Classes.Metadata
|
namespace gaseous_server.Classes.Metadata
|
||||||
{
|
{
|
||||||
@@ -15,12 +13,6 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
public static Franchise? GetFranchises(long? Id)
|
public static Franchise? GetFranchises(long? Id)
|
||||||
{
|
{
|
||||||
if ((Id == 0) || (Id == null))
|
if ((Id == 0) || (Id == null))
|
||||||
@@ -75,8 +67,16 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
Storage.NewCacheValue(returnValue);
|
Storage.NewCacheValue(returnValue);
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Expired:
|
case Storage.CacheStatus.Expired:
|
||||||
returnValue = await GetObjectFromServer(WhereClause);
|
try
|
||||||
Storage.NewCacheValue(returnValue, true);
|
{
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<Franchise>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
returnValue = Storage.GetCacheValue<Franchise>(returnValue, "id", (long)searchValue);
|
returnValue = Storage.GetCacheValue<Franchise>(returnValue, "id", (long)searchValue);
|
||||||
@@ -97,7 +97,8 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
private static async Task<Franchise> GetObjectFromServer(string WhereClause)
|
private static async Task<Franchise> GetObjectFromServer(string WhereClause)
|
||||||
{
|
{
|
||||||
// get FranchiseContentDescriptions metadata
|
// get FranchiseContentDescriptions metadata
|
||||||
var results = await igdb.QueryAsync<Franchise>(IGDBClient.Endpoints.Franchies, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<Franchise>(IGDBClient.Endpoints.Franchies, fieldList, WhereClause);
|
||||||
var result = results.First();
|
var result = results.First();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
108
gaseous-server/Classes/Metadata/GameModes.cs
Normal file
108
gaseous-server/Classes/Metadata/GameModes.cs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
using System;
|
||||||
|
using IGDB;
|
||||||
|
using IGDB.Models;
|
||||||
|
|
||||||
|
|
||||||
|
namespace gaseous_server.Classes.Metadata
|
||||||
|
{
|
||||||
|
public class GameModes
|
||||||
|
{
|
||||||
|
const string fieldList = "fields checksum,created_at,name,slug,updated_at,url;";
|
||||||
|
|
||||||
|
public GameModes()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameMode? GetGame_Modes(long? Id)
|
||||||
|
{
|
||||||
|
if ((Id == 0) || (Id == null))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Task<GameMode> RetVal = _GetGame_Modes(SearchUsing.id, Id);
|
||||||
|
return RetVal.Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameMode GetGame_Modes(string Slug)
|
||||||
|
{
|
||||||
|
Task<GameMode> RetVal = _GetGame_Modes(SearchUsing.slug, Slug);
|
||||||
|
return RetVal.Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<GameMode> _GetGame_Modes(SearchUsing searchUsing, object searchValue)
|
||||||
|
{
|
||||||
|
// check database first
|
||||||
|
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
||||||
|
if (searchUsing == SearchUsing.id)
|
||||||
|
{
|
||||||
|
cacheStatus = Storage.GetCacheStatus("GameMode", (long)searchValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cacheStatus = Storage.GetCacheStatus("GameMode", (string)searchValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up where clause
|
||||||
|
string WhereClause = "";
|
||||||
|
switch (searchUsing)
|
||||||
|
{
|
||||||
|
case SearchUsing.id:
|
||||||
|
WhereClause = "where id = " + searchValue;
|
||||||
|
break;
|
||||||
|
case SearchUsing.slug:
|
||||||
|
WhereClause = "where slug = " + searchValue;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("Invalid search type");
|
||||||
|
}
|
||||||
|
|
||||||
|
GameMode returnValue = new GameMode();
|
||||||
|
switch (cacheStatus)
|
||||||
|
{
|
||||||
|
case Storage.CacheStatus.NotPresent:
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue);
|
||||||
|
break;
|
||||||
|
case Storage.CacheStatus.Expired:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<GameMode>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Storage.CacheStatus.Current:
|
||||||
|
returnValue = Storage.GetCacheValue<GameMode>(returnValue, "id", (long)searchValue);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("How did you get here?");
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum SearchUsing
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<GameMode> GetObjectFromServer(string WhereClause)
|
||||||
|
{
|
||||||
|
// get Game_Modes metadata
|
||||||
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<GameMode>(IGDBClient.Endpoints.GameModes, fieldList, WhereClause);
|
||||||
|
var result = results.First();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
using MySqlX.XDevAPI.Common;
|
|
||||||
using static gaseous_tools.Config.ConfigFile;
|
|
||||||
|
|
||||||
namespace gaseous_server.Classes.Metadata
|
namespace gaseous_server.Classes.Metadata
|
||||||
{
|
{
|
||||||
@@ -15,12 +13,6 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
public static GameVideo? GetGame_Videos(long? Id)
|
public static GameVideo? GetGame_Videos(long? Id)
|
||||||
{
|
{
|
||||||
if ((Id == 0) || (Id == null))
|
if ((Id == 0) || (Id == null))
|
||||||
@@ -68,18 +60,23 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
}
|
}
|
||||||
|
|
||||||
GameVideo returnValue = new GameVideo();
|
GameVideo returnValue = new GameVideo();
|
||||||
bool forceImageDownload = false;
|
|
||||||
switch (cacheStatus)
|
switch (cacheStatus)
|
||||||
{
|
{
|
||||||
case Storage.CacheStatus.NotPresent:
|
case Storage.CacheStatus.NotPresent:
|
||||||
returnValue = await GetObjectFromServer(WhereClause);
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
Storage.NewCacheValue(returnValue);
|
Storage.NewCacheValue(returnValue);
|
||||||
forceImageDownload = true;
|
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Expired:
|
case Storage.CacheStatus.Expired:
|
||||||
returnValue = await GetObjectFromServer(WhereClause);
|
try
|
||||||
Storage.NewCacheValue(returnValue, true);
|
{
|
||||||
forceImageDownload = true;
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<GameVideo>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
returnValue = Storage.GetCacheValue<GameVideo>(returnValue, "id", (long)searchValue);
|
returnValue = Storage.GetCacheValue<GameVideo>(returnValue, "id", (long)searchValue);
|
||||||
@@ -100,7 +97,8 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
private static async Task<GameVideo> GetObjectFromServer(string WhereClause)
|
private static async Task<GameVideo> GetObjectFromServer(string WhereClause)
|
||||||
{
|
{
|
||||||
// get Game_Videos metadata
|
// get Game_Videos metadata
|
||||||
var results = await igdb.QueryAsync<GameVideo>(IGDBClient.Endpoints.GameVideos, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<GameVideo>(IGDBClient.Endpoints.GameVideos, fieldList, WhereClause);
|
||||||
var result = results.First();
|
var result = results.First();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
|
|
||||||
@@ -15,13 +14,13 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
public class InvalidGameId : Exception
|
||||||
// Found in Twitch Developer portal for your app
|
{
|
||||||
Config.IGDB.ClientId,
|
public InvalidGameId(long Id) : base("Unable to find Game by id " + Id)
|
||||||
Config.IGDB.Secret
|
{}
|
||||||
);
|
}
|
||||||
|
|
||||||
public static Game? GetGame(long Id, bool followSubGames, bool forceRefresh)
|
public static Game? GetGame(long Id, bool getAllMetadata, bool followSubGames, bool forceRefresh)
|
||||||
{
|
{
|
||||||
if (Id == 0)
|
if (Id == 0)
|
||||||
{
|
{
|
||||||
@@ -45,14 +44,14 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Task<Game> RetVal = _GetGame(SearchUsing.id, Id, followSubGames, forceRefresh);
|
Task<Game> RetVal = _GetGame(SearchUsing.id, Id, getAllMetadata, followSubGames, forceRefresh);
|
||||||
return RetVal.Result;
|
return RetVal.Result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Game GetGame(string Slug, bool followSubGames, bool forceRefresh)
|
public static Game GetGame(string Slug, bool getAllMetadata, bool followSubGames, bool forceRefresh)
|
||||||
{
|
{
|
||||||
Task<Game> RetVal = _GetGame(SearchUsing.slug, Slug, followSubGames, forceRefresh);
|
Task<Game> RetVal = _GetGame(SearchUsing.slug, Slug, getAllMetadata, followSubGames, forceRefresh);
|
||||||
return RetVal.Result;
|
return RetVal.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +60,7 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
return Storage.BuildCacheObject<Game>(new Game(), dataRow);
|
return Storage.BuildCacheObject<Game>(new Game(), dataRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<Game> _GetGame(SearchUsing searchUsing, object searchValue, bool followSubGames = false, bool forceRefresh = false)
|
private static async Task<Game> _GetGame(SearchUsing searchUsing, object searchValue, bool getAllMetadata = true, bool followSubGames = false, bool forceRefresh = false)
|
||||||
{
|
{
|
||||||
// check database first
|
// check database first
|
||||||
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
||||||
@@ -99,91 +98,42 @@ 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, followSubGames);
|
UpdateSubClasses(returnValue, getAllMetadata, followSubGames, forceRefresh);
|
||||||
return returnValue;
|
return returnValue;
|
||||||
case Storage.CacheStatus.Expired:
|
case Storage.CacheStatus.Expired:
|
||||||
returnValue = await GetObjectFromServer(WhereClause);
|
try
|
||||||
Storage.NewCacheValue(returnValue, true);
|
{
|
||||||
UpdateSubClasses(returnValue, followSubGames);
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
UpdateSubClasses(returnValue, getAllMetadata, followSubGames, forceRefresh);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<Game>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
return returnValue;
|
return returnValue;
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
return Storage.GetCacheValue<Game>(returnValue, "id", (long)searchValue);
|
returnValue = Storage.GetCacheValue<Game>(returnValue, "id", (long)searchValue);
|
||||||
|
UpdateSubClasses(returnValue, false, false, false);
|
||||||
|
return returnValue;
|
||||||
default:
|
default:
|
||||||
throw new Exception("How did you get here?");
|
throw new Exception("How did you get here?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UpdateSubClasses(Game Game, bool followSubGames)
|
private static void UpdateSubClasses(Game Game, bool getAllMetadata, bool followSubGames, bool forceRefresh)
|
||||||
{
|
{
|
||||||
if (Game.AgeRatings != null)
|
// required metadata
|
||||||
{
|
|
||||||
foreach (long AgeRatingId in Game.AgeRatings.Ids)
|
|
||||||
{
|
|
||||||
AgeRating GameAgeRating = AgeRatings.GetAgeRatings(AgeRatingId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Game.AlternativeNames != null)
|
|
||||||
{
|
|
||||||
foreach (long AlternativeNameId in Game.AlternativeNames.Ids)
|
|
||||||
{
|
|
||||||
AlternativeName GameAlternativeName = AlternativeNames.GetAlternativeNames(AlternativeNameId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Game.Artworks != null)
|
|
||||||
{
|
|
||||||
foreach (long ArtworkId in Game.Artworks.Ids)
|
|
||||||
{
|
|
||||||
Artwork GameArtwork = Artworks.GetArtwork(ArtworkId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (followSubGames)
|
|
||||||
{
|
|
||||||
List<long> gamesToFetch = new List<long>();
|
|
||||||
if (Game.Bundles != null) { gamesToFetch.AddRange(Game.Bundles.Ids); }
|
|
||||||
if (Game.Dlcs != null) { gamesToFetch.AddRange(Game.Dlcs.Ids); }
|
|
||||||
if (Game.Expansions != null) { gamesToFetch.AddRange(Game.Expansions.Ids); }
|
|
||||||
if (Game.ParentGame != null) { gamesToFetch.Add((long)Game.ParentGame.Id); }
|
|
||||||
//if (Game.SimilarGames != null) { gamesToFetch.AddRange(Game.SimilarGames.Ids); }
|
|
||||||
if (Game.StandaloneExpansions != null) { gamesToFetch.AddRange(Game.StandaloneExpansions.Ids); }
|
|
||||||
if (Game.VersionParent != null) { gamesToFetch.Add((long)Game.VersionParent.Id); }
|
|
||||||
|
|
||||||
foreach (long gameId in gamesToFetch)
|
|
||||||
{
|
|
||||||
Game relatedGame = GetGame(gameId, false, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Game.Collection != null)
|
|
||||||
{
|
|
||||||
Collection GameCollection = Collections.GetCollections(Game.Collection.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Game.Cover != null)
|
if (Game.Cover != null)
|
||||||
{
|
{
|
||||||
Cover GameCover = Covers.GetCover(Game.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game));
|
try
|
||||||
}
|
|
||||||
|
|
||||||
if (Game.ExternalGames != null)
|
|
||||||
{
|
|
||||||
foreach (long ExternalGameId in Game.ExternalGames.Ids)
|
|
||||||
{
|
{
|
||||||
ExternalGame GameExternalGame = ExternalGames.GetExternalGames(ExternalGameId);
|
Cover GameCover = Covers.GetCover(Game.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh);
|
||||||
}
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
|
|
||||||
if (Game.Franchise != null)
|
|
||||||
{
|
|
||||||
Franchise GameFranchise = Franchises.GetFranchises(Game.Franchise.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Game.Franchises != null)
|
|
||||||
{
|
|
||||||
foreach (long FranchiseId in Game.Franchises.Ids)
|
|
||||||
{
|
{
|
||||||
Franchise GameFranchise = Franchises.GetFranchises(FranchiseId);
|
Logging.Log(Logging.LogType.Critical, "Game Metadata", "Unable to fetch cover artwork.", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,35 +145,161 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Game.InvolvedCompanies != null)
|
if (Game.GameModes != null)
|
||||||
{
|
{
|
||||||
foreach (long involvedCompanyId in Game.InvolvedCompanies.Ids)
|
foreach (long gameModeId in Game.GameModes.Ids)
|
||||||
{
|
{
|
||||||
InvolvedCompany involvedCompany = InvolvedCompanies.GetInvolvedCompanies(involvedCompanyId);
|
GameMode gameMode = GameModes.GetGame_Modes(gameModeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Game.Platforms != null)
|
if (Game.MultiplayerModes != null)
|
||||||
{
|
{
|
||||||
foreach (long PlatformId in Game.Platforms.Ids)
|
foreach (long multiplayerModeId in Game.MultiplayerModes.Ids)
|
||||||
{
|
{
|
||||||
Platform GamePlatform = Platforms.GetPlatform(PlatformId);
|
MultiplayerMode multiplayerMode = MultiplayerModes.GetGame_MultiplayerModes(multiplayerModeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Game.Screenshots != null)
|
if (Game.PlayerPerspectives != null)
|
||||||
{
|
{
|
||||||
foreach (long ScreenshotId in Game.Screenshots.Ids)
|
foreach (long PerspectiveId in Game.PlayerPerspectives.Ids)
|
||||||
{
|
{
|
||||||
Screenshot GameScreenshot = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game));
|
PlayerPerspective GamePlayPerspective = PlayerPerspectives.GetGame_PlayerPerspectives(PerspectiveId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Game.Videos != null)
|
if (Game.Themes != null)
|
||||||
{
|
{
|
||||||
foreach (long GameVideoId in Game.Videos.Ids)
|
foreach (long ThemeId in Game.Themes.Ids)
|
||||||
{
|
{
|
||||||
GameVideo gameVideo = GamesVideos.GetGame_Videos(GameVideoId);
|
Theme GameTheme = Themes.GetGame_Themes(ThemeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Game.AgeRatings != null)
|
||||||
|
{
|
||||||
|
foreach (long AgeRatingId in Game.AgeRatings.Ids)
|
||||||
|
{
|
||||||
|
AgeRating GameAgeRating = AgeRatings.GetAgeRatings(AgeRatingId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AgeGroups.GetAgeGroup(Game);
|
||||||
|
|
||||||
|
if (Game.ReleaseDates != null)
|
||||||
|
{
|
||||||
|
foreach (long ReleaseDateId in Game.ReleaseDates.Ids)
|
||||||
|
{
|
||||||
|
ReleaseDate GameReleaseDate = ReleaseDates.GetReleaseDates(ReleaseDateId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional metadata - usually downloaded as needed
|
||||||
|
if (getAllMetadata == true)
|
||||||
|
{
|
||||||
|
if (Game.AlternativeNames != null)
|
||||||
|
{
|
||||||
|
foreach (long AlternativeNameId in Game.AlternativeNames.Ids)
|
||||||
|
{
|
||||||
|
AlternativeName GameAlternativeName = AlternativeNames.GetAlternativeNames(AlternativeNameId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Game.Artworks != null)
|
||||||
|
{
|
||||||
|
foreach (long ArtworkId in Game.Artworks.Ids)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Artwork GameArtwork = Artworks.GetArtwork(ArtworkId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Critical, "Game Metadata", "Unable to fetch artwork id: " + ArtworkId, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (followSubGames)
|
||||||
|
{
|
||||||
|
List<long> gamesToFetch = new List<long>();
|
||||||
|
if (Game.Bundles != null) { gamesToFetch.AddRange(Game.Bundles.Ids); }
|
||||||
|
if (Game.Dlcs != null) { gamesToFetch.AddRange(Game.Dlcs.Ids); }
|
||||||
|
if (Game.Expansions != null) { gamesToFetch.AddRange(Game.Expansions.Ids); }
|
||||||
|
if (Game.ParentGame != null) { gamesToFetch.Add((long)Game.ParentGame.Id); }
|
||||||
|
//if (Game.SimilarGames != null) { gamesToFetch.AddRange(Game.SimilarGames.Ids); }
|
||||||
|
if (Game.StandaloneExpansions != null) { gamesToFetch.AddRange(Game.StandaloneExpansions.Ids); }
|
||||||
|
if (Game.VersionParent != null) { gamesToFetch.Add((long)Game.VersionParent.Id); }
|
||||||
|
|
||||||
|
foreach (long gameId in gamesToFetch)
|
||||||
|
{
|
||||||
|
Game relatedGame = GetGame(gameId, false, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Game.Collection != null)
|
||||||
|
{
|
||||||
|
Collection GameCollection = Collections.GetCollections(Game.Collection.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Game.ExternalGames != null)
|
||||||
|
{
|
||||||
|
foreach (long ExternalGameId in Game.ExternalGames.Ids)
|
||||||
|
{
|
||||||
|
ExternalGame GameExternalGame = ExternalGames.GetExternalGames(ExternalGameId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Game.Franchise != null)
|
||||||
|
{
|
||||||
|
Franchise GameFranchise = Franchises.GetFranchises(Game.Franchise.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Game.Franchises != null)
|
||||||
|
{
|
||||||
|
foreach (long FranchiseId in Game.Franchises.Ids)
|
||||||
|
{
|
||||||
|
Franchise GameFranchise = Franchises.GetFranchises(FranchiseId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Game.InvolvedCompanies != null)
|
||||||
|
{
|
||||||
|
foreach (long involvedCompanyId in Game.InvolvedCompanies.Ids)
|
||||||
|
{
|
||||||
|
InvolvedCompany involvedCompany = InvolvedCompanies.GetInvolvedCompanies(involvedCompanyId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Game.Platforms != null)
|
||||||
|
{
|
||||||
|
foreach (long PlatformId in Game.Platforms.Ids)
|
||||||
|
{
|
||||||
|
Platform GamePlatform = Platforms.GetPlatform(PlatformId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Game.Screenshots != null)
|
||||||
|
{
|
||||||
|
foreach (long ScreenshotId in Game.Screenshots.Ids)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Screenshot GameScreenshot = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Critical, "Game Metadata", "Unable to fetch screenshot id: " + ScreenshotId, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Game.Videos != null)
|
||||||
|
{
|
||||||
|
foreach (long GameVideoId in Game.Videos.Ids)
|
||||||
|
{
|
||||||
|
GameVideo gameVideo = GamesVideos.GetGame_Videos(GameVideoId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,46 +313,212 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
private static async Task<Game> GetObjectFromServer(string WhereClause)
|
private static async Task<Game> GetObjectFromServer(string WhereClause)
|
||||||
{
|
{
|
||||||
// get Game metadata
|
// get Game metadata
|
||||||
var results = await igdb.QueryAsync<Game>(IGDBClient.Endpoints.Games, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<Game>(IGDBClient.Endpoints.Games, fieldList, WhereClause);
|
||||||
var result = results.First();
|
var result = results.First();
|
||||||
|
|
||||||
|
// add artificial unknown platform mapping
|
||||||
|
List<long> platformIds = new List<long>();
|
||||||
|
platformIds.Add(0);
|
||||||
|
if (result.Platforms != null)
|
||||||
|
{
|
||||||
|
if (result.Platforms.Ids != null)
|
||||||
|
{
|
||||||
|
platformIds.AddRange(result.Platforms.Ids.ToList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.Platforms = new IdentitiesOrValues<Platform>(
|
||||||
|
ids: platformIds.ToArray<long>()
|
||||||
|
);
|
||||||
|
|
||||||
|
// get cover art from parent if this has no cover
|
||||||
|
if (result.Cover == null)
|
||||||
|
{
|
||||||
|
if (result.ParentGame != null)
|
||||||
|
{
|
||||||
|
if (result.ParentGame.Id != null)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Game Metadata", "Game has no cover art, fetching cover art from parent game");
|
||||||
|
Game parentGame = GetGame((long)result.ParentGame.Id, false, false, false);
|
||||||
|
result.Cover = parentGame.Cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get missing metadata from parent if this is a port
|
||||||
|
if (result.Category == Category.Port)
|
||||||
|
{
|
||||||
|
if (result.Summary == null)
|
||||||
|
{
|
||||||
|
if (result.ParentGame != null)
|
||||||
|
{
|
||||||
|
if (result.ParentGame.Id != null)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Game Metadata", "Game has no summary, fetching summary from parent game");
|
||||||
|
Game parentGame = GetGame((long)result.ParentGame.Id, false, false, false);
|
||||||
|
result.Summary = parentGame.Summary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void AssignAllGamesToPlatformIdZero()
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "SELECT * FROM Game;";
|
||||||
|
DataTable gamesTable = db.ExecuteCMD(sql);
|
||||||
|
foreach (DataRow gameRow in gamesTable.Rows)
|
||||||
|
{
|
||||||
|
sql = "DELETE FROM Relation_Game_Platforms WHERE PlatformsId = 0 AND GameId = @Id; INSERT INTO Relation_Game_Platforms (GameId, PlatformsId) VALUES (@Id, 0);";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("Id", (long)gameRow["Id"]);
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool AllowNoPlatformSearch = false;
|
||||||
|
|
||||||
public static Game[] SearchForGame(string SearchString, long PlatformId, SearchType searchType)
|
public static Game[] SearchForGame(string SearchString, long PlatformId, SearchType searchType)
|
||||||
{
|
{
|
||||||
Task<Game[]> games = _SearchForGame(SearchString, PlatformId, searchType);
|
// search local first
|
||||||
|
Logging.Log(Logging.LogType.Information, "Game Search", "Attempting local search of type '" + searchType.ToString() + "' for " + SearchString);
|
||||||
|
Task<Game[]> games = _SearchForGameDatabase(SearchString, PlatformId, searchType);
|
||||||
|
if (games.Result.Length == 0)
|
||||||
|
{
|
||||||
|
// fall back to online search
|
||||||
|
Logging.Log(Logging.LogType.Information, "Game Search", "Falling back to remote search of type '" + searchType.ToString() + "' for " + SearchString);
|
||||||
|
games = _SearchForGameRemote(SearchString, PlatformId, searchType);
|
||||||
|
}
|
||||||
return games.Result;
|
return games.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<Game[]> _SearchForGame(string SearchString, long PlatformId, SearchType searchType)
|
private static async Task<Game[]> _SearchForGameDatabase(string SearchString, long PlatformId, SearchType searchType)
|
||||||
{
|
{
|
||||||
string searchBody = "";
|
string whereClause = "";
|
||||||
searchBody += "fields id,name,slug,platforms,summary; ";
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
bool allowSearch = true;
|
||||||
switch (searchType)
|
switch (searchType)
|
||||||
{
|
{
|
||||||
case SearchType.searchNoPlatform:
|
case SearchType.searchNoPlatform:
|
||||||
searchBody += "search \"" + SearchString + "\"; ";
|
whereClause = "MATCH(`Name`) AGAINST (@gamename)";
|
||||||
|
dbDict.Add("platformid", PlatformId);
|
||||||
|
dbDict.Add("gamename", SearchString);
|
||||||
|
|
||||||
|
allowSearch = AllowNoPlatformSearch;
|
||||||
break;
|
break;
|
||||||
case SearchType.search:
|
case SearchType.search:
|
||||||
searchBody += "search \"" + SearchString + "\"; ";
|
whereClause = "PlatformsId = @platformid AND MATCH(`Name`) AGAINST (@gamename)";
|
||||||
searchBody += "where platforms = (" + PlatformId + ");";
|
dbDict.Add("platformid", PlatformId);
|
||||||
|
dbDict.Add("gamename", SearchString);
|
||||||
break;
|
break;
|
||||||
case SearchType.wherefuzzy:
|
case SearchType.wherefuzzy:
|
||||||
searchBody += "where platforms = (" + PlatformId + ") & name ~ *\"" + SearchString + "\"*;";
|
whereClause = "PlatformsId = @platformid AND `Name` LIKE @gamename";
|
||||||
|
dbDict.Add("platformid", PlatformId);
|
||||||
|
dbDict.Add("gamename", "%" + SearchString + "%");
|
||||||
break;
|
break;
|
||||||
case SearchType.where:
|
case SearchType.where:
|
||||||
searchBody += "where platforms = (" + PlatformId + ") & name ~ \"" + SearchString + "\";";
|
whereClause = "PlatformsId = @platformid AND `Name` = @gamename";
|
||||||
|
dbDict.Add("platformid", PlatformId);
|
||||||
|
dbDict.Add("gamename", SearchString);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
var results = await igdb.QueryAsync<Game>(IGDBClient.Endpoints.Games, query: searchBody);
|
Game[]? results = new Game[0];
|
||||||
|
if (allowSearch == true)
|
||||||
|
{
|
||||||
|
List<Game> searchResults = new List<Game>();
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
DataTable data = db.ExecuteCMD(sql, dbDict);
|
||||||
|
foreach (DataRow row in data.Rows)
|
||||||
|
{
|
||||||
|
Game game = new Game{
|
||||||
|
Id = (long)row["Id"],
|
||||||
|
Name = (string)Common.ReturnValueIfNull(row["Name"], ""),
|
||||||
|
Slug = (string)Common.ReturnValueIfNull(row["Slug"], ""),
|
||||||
|
Summary = (string)Common.ReturnValueIfNull(row["Summary"], "")
|
||||||
|
};
|
||||||
|
searchResults.Add(game);
|
||||||
|
}
|
||||||
|
|
||||||
|
results = searchResults.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<Game[]> _SearchForGameRemote(string SearchString, long PlatformId, SearchType searchType)
|
||||||
|
{
|
||||||
|
string searchBody = "";
|
||||||
|
string searchFields = "fields id,name,slug,platforms,summary; ";
|
||||||
|
bool allowSearch = true;
|
||||||
|
switch (searchType)
|
||||||
|
{
|
||||||
|
case SearchType.searchNoPlatform:
|
||||||
|
searchBody = "search \"" + SearchString + "\"; ";
|
||||||
|
|
||||||
|
allowSearch = AllowNoPlatformSearch;
|
||||||
|
break;
|
||||||
|
case SearchType.search:
|
||||||
|
searchBody = "search \"" + SearchString + "\"; where platforms = (" + PlatformId + ");";
|
||||||
|
break;
|
||||||
|
case SearchType.wherefuzzy:
|
||||||
|
searchBody = "where platforms = (" + PlatformId + ") & name ~ *\"" + SearchString + "\"*;";
|
||||||
|
break;
|
||||||
|
case SearchType.where:
|
||||||
|
searchBody = "where platforms = (" + PlatformId + ") & name ~ \"" + SearchString + "\";";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check search cache
|
||||||
|
Game[]? games = Communications.GetSearchCache<Game[]?>(searchFields, searchBody);
|
||||||
|
|
||||||
|
if (games == null)
|
||||||
|
{
|
||||||
|
// cache miss
|
||||||
|
// get Game metadata
|
||||||
|
Communications comms = new Communications();
|
||||||
|
Game[]? results = new Game[0];
|
||||||
|
if (allowSearch == true)
|
||||||
|
{
|
||||||
|
results = await comms.APIComm<Game>(IGDBClient.Endpoints.Games, searchFields, searchBody);
|
||||||
|
|
||||||
|
Communications.SetSearchCache<Game[]?>(searchFields, searchBody, results);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return games.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<KeyValuePair<long, string>> GetAvailablePlatforms(long GameId)
|
||||||
|
{
|
||||||
|
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`;";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("gameid", GameId);
|
||||||
|
DataTable data = db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
List<KeyValuePair<long, string>> platforms = new List<KeyValuePair<long, string>>();
|
||||||
|
foreach (DataRow row in data.Rows)
|
||||||
|
{
|
||||||
|
KeyValuePair<long, string> valuePair = new KeyValuePair<long, string>((long)row["PlatformId"], (string)row["Name"]);
|
||||||
|
platforms.Add(valuePair);
|
||||||
|
}
|
||||||
|
|
||||||
|
return platforms;
|
||||||
|
}
|
||||||
|
|
||||||
public enum SearchType
|
public enum SearchType
|
||||||
{
|
{
|
||||||
where = 0,
|
where = 0,
|
||||||
@@ -284,5 +526,48 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
search = 2,
|
search = 2,
|
||||||
searchNoPlatform = 3
|
searchNoPlatform = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class MinimalGameItem
|
||||||
|
{
|
||||||
|
public MinimalGameItem()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public MinimalGameItem(Game gameObject)
|
||||||
|
{
|
||||||
|
this.Id = gameObject.Id;
|
||||||
|
this.Name = gameObject.Name;
|
||||||
|
this.TotalRating = gameObject.TotalRating;
|
||||||
|
this.TotalRatingCount = gameObject.TotalRatingCount;
|
||||||
|
this.Cover = gameObject.Cover;
|
||||||
|
this.Artworks = gameObject.Artworks;
|
||||||
|
this.FirstReleaseDate = gameObject.FirstReleaseDate;
|
||||||
|
|
||||||
|
// compile age ratings
|
||||||
|
this.AgeRatings = new List<AgeRating>();
|
||||||
|
if (gameObject.AgeRatings != null)
|
||||||
|
{
|
||||||
|
foreach (long ageRatingId in gameObject.AgeRatings.Ids)
|
||||||
|
{
|
||||||
|
AgeRating? rating = Classes.Metadata.AgeRatings.GetAgeRatings(ageRatingId);
|
||||||
|
if (rating != null)
|
||||||
|
{
|
||||||
|
this.AgeRatings.Add(rating);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long? Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public double? TotalRating { get; set; }
|
||||||
|
public int? TotalRatingCount { get; set; }
|
||||||
|
public bool HasSavedGame { get; set; } = false;
|
||||||
|
public DateTimeOffset? FirstReleaseDate { get; set; }
|
||||||
|
public IGDB.IdentityOrValue<IGDB.Models.Cover> Cover { get; set; }
|
||||||
|
public IGDB.IdentitiesOrValues<IGDB.Models.Artwork> Artworks { get; set; }
|
||||||
|
public List<IGDB.Models.AgeRating> AgeRatings { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
using MySqlX.XDevAPI.Common;
|
|
||||||
using static gaseous_tools.Config.ConfigFile;
|
|
||||||
|
|
||||||
namespace gaseous_server.Classes.Metadata
|
namespace gaseous_server.Classes.Metadata
|
||||||
{
|
{
|
||||||
@@ -15,12 +13,6 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
public static Genre? GetGenres(long? Id)
|
public static Genre? GetGenres(long? Id)
|
||||||
{
|
{
|
||||||
if ((Id == 0) || (Id == null))
|
if ((Id == 0) || (Id == null))
|
||||||
@@ -68,18 +60,23 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
}
|
}
|
||||||
|
|
||||||
Genre returnValue = new Genre();
|
Genre returnValue = new Genre();
|
||||||
bool forceImageDownload = false;
|
|
||||||
switch (cacheStatus)
|
switch (cacheStatus)
|
||||||
{
|
{
|
||||||
case Storage.CacheStatus.NotPresent:
|
case Storage.CacheStatus.NotPresent:
|
||||||
returnValue = await GetObjectFromServer(WhereClause);
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
Storage.NewCacheValue(returnValue);
|
Storage.NewCacheValue(returnValue);
|
||||||
forceImageDownload = true;
|
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Expired:
|
case Storage.CacheStatus.Expired:
|
||||||
returnValue = await GetObjectFromServer(WhereClause);
|
try
|
||||||
Storage.NewCacheValue(returnValue, true);
|
{
|
||||||
forceImageDownload = true;
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<Genre>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
returnValue = Storage.GetCacheValue<Genre>(returnValue, "id", (long)searchValue);
|
returnValue = Storage.GetCacheValue<Genre>(returnValue, "id", (long)searchValue);
|
||||||
@@ -100,7 +97,8 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
private static async Task<Genre> GetObjectFromServer(string WhereClause)
|
private static async Task<Genre> GetObjectFromServer(string WhereClause)
|
||||||
{
|
{
|
||||||
// get Genres metadata
|
// get Genres metadata
|
||||||
var results = await igdb.QueryAsync<Genre>(IGDBClient.Endpoints.Genres, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<Genre>(IGDBClient.Endpoints.Genres, fieldList, WhereClause);
|
||||||
var result = results.First();
|
var result = results.First();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
|
|
||||||
@@ -13,12 +12,6 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
public static InvolvedCompany? GetInvolvedCompanies(long? Id)
|
public static InvolvedCompany? GetInvolvedCompanies(long? Id)
|
||||||
{
|
{
|
||||||
if ((Id == 0) || (Id == null))
|
if ((Id == 0) || (Id == null))
|
||||||
@@ -74,9 +67,16 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
UpdateSubClasses(returnValue);
|
UpdateSubClasses(returnValue);
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Expired:
|
case Storage.CacheStatus.Expired:
|
||||||
returnValue = await GetObjectFromServer(WhereClause);
|
try
|
||||||
Storage.NewCacheValue(returnValue, true);
|
{
|
||||||
UpdateSubClasses(returnValue);
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<InvolvedCompany>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
returnValue = Storage.GetCacheValue<InvolvedCompany>(returnValue, "id", (long)searchValue);
|
returnValue = Storage.GetCacheValue<InvolvedCompany>(returnValue, "id", (long)searchValue);
|
||||||
@@ -107,7 +107,8 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
// get InvolvedCompanies metadata
|
// get InvolvedCompanies metadata
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var results = await igdb.QueryAsync<InvolvedCompany>(IGDBClient.Endpoints.InvolvedCompanies, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<InvolvedCompany>(IGDBClient.Endpoints.InvolvedCompanies, fieldList, WhereClause);
|
||||||
var result = results.First();
|
var result = results.First();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
108
gaseous-server/Classes/Metadata/MultiplayerModes.cs
Normal file
108
gaseous-server/Classes/Metadata/MultiplayerModes.cs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
using System;
|
||||||
|
using IGDB;
|
||||||
|
using IGDB.Models;
|
||||||
|
|
||||||
|
|
||||||
|
namespace gaseous_server.Classes.Metadata
|
||||||
|
{
|
||||||
|
public class MultiplayerModes
|
||||||
|
{
|
||||||
|
const string fieldList = "fields campaigncoop,checksum,dropin,game,lancoop,offlinecoop,offlinecoopmax,offlinemax,onlinecoop,onlinecoopmax,onlinemax,platform,splitscreen,splitscreenonline;";
|
||||||
|
|
||||||
|
public MultiplayerModes()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MultiplayerMode? GetGame_MultiplayerModes(long? Id)
|
||||||
|
{
|
||||||
|
if ((Id == 0) || (Id == null))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Task<MultiplayerMode> RetVal = _GetGame_MultiplayerModes(SearchUsing.id, Id);
|
||||||
|
return RetVal.Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MultiplayerMode GetGame_MultiplayerModes(string Slug)
|
||||||
|
{
|
||||||
|
Task<MultiplayerMode> RetVal = _GetGame_MultiplayerModes(SearchUsing.slug, Slug);
|
||||||
|
return RetVal.Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<MultiplayerMode> _GetGame_MultiplayerModes(SearchUsing searchUsing, object searchValue)
|
||||||
|
{
|
||||||
|
// check database first
|
||||||
|
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
||||||
|
if (searchUsing == SearchUsing.id)
|
||||||
|
{
|
||||||
|
cacheStatus = Storage.GetCacheStatus("MultiplayerMode", (long)searchValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cacheStatus = Storage.GetCacheStatus("MultiplayerMode", (string)searchValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up where clause
|
||||||
|
string WhereClause = "";
|
||||||
|
switch (searchUsing)
|
||||||
|
{
|
||||||
|
case SearchUsing.id:
|
||||||
|
WhereClause = "where id = " + searchValue;
|
||||||
|
break;
|
||||||
|
case SearchUsing.slug:
|
||||||
|
WhereClause = "where slug = " + searchValue;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("Invalid search type");
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiplayerMode returnValue = new MultiplayerMode();
|
||||||
|
switch (cacheStatus)
|
||||||
|
{
|
||||||
|
case Storage.CacheStatus.NotPresent:
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue);
|
||||||
|
break;
|
||||||
|
case Storage.CacheStatus.Expired:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<MultiplayerMode>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Storage.CacheStatus.Current:
|
||||||
|
returnValue = Storage.GetCacheValue<MultiplayerMode>(returnValue, "id", (long)searchValue);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("How did you get here?");
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum SearchUsing
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<MultiplayerMode> GetObjectFromServer(string WhereClause)
|
||||||
|
{
|
||||||
|
// get Game_MultiplayerModes metadata
|
||||||
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<MultiplayerMode>(IGDBClient.Endpoints.MultiplayerModes, fieldList, WhereClause);
|
||||||
|
var result = results.First();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
using MySqlX.XDevAPI.Common;
|
|
||||||
using static gaseous_tools.Config.ConfigFile;
|
|
||||||
|
|
||||||
namespace gaseous_server.Classes.Metadata
|
namespace gaseous_server.Classes.Metadata
|
||||||
{
|
{
|
||||||
@@ -15,13 +13,7 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
public static PlatformLogo? GetPlatformLogo(long? Id, string ImagePath)
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
public static PlatformLogo? GetPlatformLogo(long? Id, string LogoPath)
|
|
||||||
{
|
{
|
||||||
if ((Id == 0) || (Id == null))
|
if ((Id == 0) || (Id == null))
|
||||||
{
|
{
|
||||||
@@ -29,18 +21,18 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Task<PlatformLogo> RetVal = _GetPlatformLogo(SearchUsing.id, Id, LogoPath);
|
Task<PlatformLogo> RetVal = _GetPlatformLogo(SearchUsing.id, Id, ImagePath);
|
||||||
return RetVal.Result;
|
return RetVal.Result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PlatformLogo GetPlatformLogo(string Slug, string LogoPath)
|
public static PlatformLogo GetPlatformLogo(string Slug, string ImagePath)
|
||||||
{
|
{
|
||||||
Task<PlatformLogo> RetVal = _GetPlatformLogo(SearchUsing.slug, Slug, LogoPath);
|
Task<PlatformLogo> RetVal = _GetPlatformLogo(SearchUsing.slug, Slug, ImagePath);
|
||||||
return RetVal.Result;
|
return RetVal.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<PlatformLogo> _GetPlatformLogo(SearchUsing searchUsing, object searchValue, string LogoPath)
|
private static async Task<PlatformLogo> _GetPlatformLogo(SearchUsing searchUsing, object searchValue, string ImagePath)
|
||||||
{
|
{
|
||||||
// check database first
|
// check database first
|
||||||
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
||||||
@@ -72,7 +64,7 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
switch (cacheStatus)
|
switch (cacheStatus)
|
||||||
{
|
{
|
||||||
case Storage.CacheStatus.NotPresent:
|
case Storage.CacheStatus.NotPresent:
|
||||||
returnValue = await GetObjectFromServer(WhereClause, LogoPath);
|
returnValue = await GetObjectFromServer(WhereClause, ImagePath);
|
||||||
if (returnValue != null)
|
if (returnValue != null)
|
||||||
{
|
{
|
||||||
Storage.NewCacheValue(returnValue);
|
Storage.NewCacheValue(returnValue);
|
||||||
@@ -80,12 +72,17 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Expired:
|
case Storage.CacheStatus.Expired:
|
||||||
returnValue = await GetObjectFromServer(WhereClause, LogoPath);
|
try
|
||||||
if (returnValue != null)
|
|
||||||
{
|
{
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause, ImagePath);
|
||||||
Storage.NewCacheValue(returnValue, true);
|
Storage.NewCacheValue(returnValue, true);
|
||||||
forceImageDownload = true;
|
forceImageDownload = true;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<PlatformLogo>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
returnValue = Storage.GetCacheValue<PlatformLogo>(returnValue, "id", (long)searchValue);
|
returnValue = Storage.GetCacheValue<PlatformLogo>(returnValue, "id", (long)searchValue);
|
||||||
@@ -96,10 +93,14 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
|
|
||||||
if (returnValue != null)
|
if (returnValue != null)
|
||||||
{
|
{
|
||||||
if ((!File.Exists(Path.Combine(LogoPath, "Logo.jpg"))) || forceImageDownload == true)
|
// check for presence of "original" quality file - download if absent or force download is true
|
||||||
|
string localFile = Path.Combine(ImagePath, Communications.IGDBAPI_ImageSize.original.ToString(), returnValue.ImageId + ".jpg");
|
||||||
|
if ((!File.Exists(localFile)) || forceImageDownload == true)
|
||||||
{
|
{
|
||||||
GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_thumb);
|
Logging.Log(Logging.LogType.Information, "Metadata: " + returnValue.GetType().Name, "Platform logo download forced.");
|
||||||
GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_logo_med);
|
|
||||||
|
Communications comms = new Communications();
|
||||||
|
comms.GetSpecificImageFromServer(ImagePath, returnValue.ImageId, Communications.IGDBAPI_ImageSize.original, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,63 +113,14 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
slug
|
slug
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<PlatformLogo?> GetObjectFromServer(string WhereClause, string LogoPath)
|
private static async Task<PlatformLogo> GetObjectFromServer(string WhereClause, string ImagePath)
|
||||||
{
|
{
|
||||||
// get PlatformLogo metadata
|
// get Artwork metadata
|
||||||
var results = await igdb.QueryAsync<PlatformLogo>(IGDBClient.Endpoints.PlatformLogos, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
if (results.Length > 0)
|
var results = await comms.APIComm<PlatformLogo>(IGDBClient.Endpoints.PlatformLogos, fieldList, WhereClause);
|
||||||
{
|
var result = results.First();
|
||||||
var result = results.First();
|
|
||||||
|
|
||||||
GetImageFromServer(result.Url, LogoPath, LogoSize.t_thumb);
|
return result;
|
||||||
GetImageFromServer(result.Url, LogoPath, LogoSize.t_logo_med);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void GetImageFromServer(string Url, string LogoPath, LogoSize logoSize)
|
|
||||||
{
|
|
||||||
using (var client = new HttpClient())
|
|
||||||
{
|
|
||||||
string fileName = "Logo.jpg";
|
|
||||||
string extension = "jpg";
|
|
||||||
switch (logoSize)
|
|
||||||
{
|
|
||||||
case LogoSize.t_thumb:
|
|
||||||
fileName = "Logo_Thumb";
|
|
||||||
extension = "jpg";
|
|
||||||
break;
|
|
||||||
case LogoSize.t_logo_med:
|
|
||||||
fileName = "Logo_Medium";
|
|
||||||
extension = "png";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fileName = "Logo";
|
|
||||||
extension = "jpg";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
string imageUrl = Url.Replace(LogoSize.t_thumb.ToString(), logoSize.ToString()).Replace("jpg", extension);
|
|
||||||
|
|
||||||
using (var s = client.GetStreamAsync("https:" + imageUrl))
|
|
||||||
{
|
|
||||||
if (!Directory.Exists(LogoPath)) { Directory.CreateDirectory(LogoPath); }
|
|
||||||
using (var fs = new FileStream(Path.Combine(LogoPath, fileName + "." + extension), FileMode.OpenOrCreate))
|
|
||||||
{
|
|
||||||
s.Result.CopyTo(fs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum LogoSize
|
|
||||||
{
|
|
||||||
t_thumb,
|
|
||||||
t_logo_med
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
|
|
||||||
@@ -14,12 +13,6 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
public static PlatformVersion? GetPlatformVersion(long Id, Platform ParentPlatform)
|
public static PlatformVersion? GetPlatformVersion(long Id, Platform ParentPlatform)
|
||||||
{
|
{
|
||||||
if (Id == 0)
|
if (Id == 0)
|
||||||
@@ -78,12 +71,17 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
}
|
}
|
||||||
return returnValue;
|
return returnValue;
|
||||||
case Storage.CacheStatus.Expired:
|
case Storage.CacheStatus.Expired:
|
||||||
returnValue = await GetObjectFromServer(WhereClause);
|
try
|
||||||
if (returnValue != null)
|
|
||||||
{
|
{
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
Storage.NewCacheValue(returnValue, true);
|
Storage.NewCacheValue(returnValue, true);
|
||||||
UpdateSubClasses(ParentPlatform, returnValue);
|
UpdateSubClasses(ParentPlatform, returnValue);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<PlatformVersion>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
return returnValue;
|
return returnValue;
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
return Storage.GetCacheValue<PlatformVersion>(returnValue, "id", (long)searchValue);
|
return Storage.GetCacheValue<PlatformVersion>(returnValue, "id", (long)searchValue);
|
||||||
@@ -96,7 +94,14 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
if (platformVersion.PlatformLogo != null)
|
if (platformVersion.PlatformLogo != null)
|
||||||
{
|
{
|
||||||
PlatformLogo platformLogo = PlatformLogos.GetPlatformLogo(platformVersion.PlatformLogo.Id, Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(ParentPlatform), "Versions", platformVersion.Slug));
|
try
|
||||||
|
{
|
||||||
|
PlatformLogo platformLogo = PlatformLogos.GetPlatformLogo(platformVersion.PlatformLogo.Id, Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(ParentPlatform), "Versions", platformVersion.Slug));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Platform Update", "Unable to fetch platform logo", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +114,8 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
private static async Task<PlatformVersion?> GetObjectFromServer(string WhereClause)
|
private static async Task<PlatformVersion?> GetObjectFromServer(string WhereClause)
|
||||||
{
|
{
|
||||||
// get PlatformVersion metadata
|
// get PlatformVersion metadata
|
||||||
var results = await igdb.QueryAsync<PlatformVersion>(IGDBClient.Endpoints.PlatformVersions, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<PlatformVersion>(IGDBClient.Endpoints.PlatformVersions, fieldList, WhereClause);
|
||||||
if (results.Length > 0)
|
if (results.Length > 0)
|
||||||
{
|
{
|
||||||
var result = results.First();
|
var result = results.First();
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
|
|
||||||
@@ -16,13 +15,7 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
public static Platform? GetPlatform(long Id, bool forceRefresh = false)
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
public static Platform? GetPlatform(long Id)
|
|
||||||
{
|
{
|
||||||
if (Id == 0)
|
if (Id == 0)
|
||||||
{
|
{
|
||||||
@@ -46,18 +39,26 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Task<Platform> RetVal = _GetPlatform(SearchUsing.id, Id);
|
try
|
||||||
return RetVal.Result;
|
{
|
||||||
|
Task<Platform> RetVal = _GetPlatform(SearchUsing.id, Id, forceRefresh);
|
||||||
|
return RetVal.Result;
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata", "An error occurred fetching Platform Id " + Id, ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Platform GetPlatform(string Slug)
|
public static Platform GetPlatform(string Slug, bool forceRefresh = false)
|
||||||
{
|
{
|
||||||
Task<Platform> RetVal = _GetPlatform(SearchUsing.slug, Slug);
|
Task<Platform> RetVal = _GetPlatform(SearchUsing.slug, Slug, forceRefresh);
|
||||||
return RetVal.Result;
|
return RetVal.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<Platform> _GetPlatform(SearchUsing searchUsing, object searchValue)
|
private static async Task<Platform> _GetPlatform(SearchUsing searchUsing, object searchValue, bool forceRefresh)
|
||||||
{
|
{
|
||||||
// check database first
|
// check database first
|
||||||
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
||||||
@@ -70,6 +71,11 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
cacheStatus = Storage.GetCacheStatus("Platform", (string)searchValue);
|
cacheStatus = Storage.GetCacheStatus("Platform", (string)searchValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (forceRefresh == true)
|
||||||
|
{
|
||||||
|
if (cacheStatus == Storage.CacheStatus.Current) { cacheStatus = Storage.CacheStatus.Expired; }
|
||||||
|
}
|
||||||
|
|
||||||
// set up where clause
|
// set up where clause
|
||||||
string WhereClause = "";
|
string WhereClause = "";
|
||||||
switch (searchUsing)
|
switch (searchUsing)
|
||||||
@@ -91,12 +97,22 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
returnValue = await GetObjectFromServer(WhereClause);
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
Storage.NewCacheValue(returnValue);
|
Storage.NewCacheValue(returnValue);
|
||||||
UpdateSubClasses(returnValue);
|
UpdateSubClasses(returnValue);
|
||||||
|
AddPlatformMapping(returnValue);
|
||||||
return returnValue;
|
return returnValue;
|
||||||
case Storage.CacheStatus.Expired:
|
case Storage.CacheStatus.Expired:
|
||||||
returnValue = await GetObjectFromServer(WhereClause);
|
try
|
||||||
Storage.NewCacheValue(returnValue, true);
|
{
|
||||||
UpdateSubClasses(returnValue);
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
return returnValue;
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
UpdateSubClasses(returnValue);
|
||||||
|
AddPlatformMapping(returnValue);
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
return Storage.GetCacheValue<Platform>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
return Storage.GetCacheValue<Platform>(returnValue, "id", (long)searchValue);
|
return Storage.GetCacheValue<Platform>(returnValue, "id", (long)searchValue);
|
||||||
default:
|
default:
|
||||||
@@ -116,7 +132,39 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
|
|
||||||
if (platform.PlatformLogo != null)
|
if (platform.PlatformLogo != null)
|
||||||
{
|
{
|
||||||
PlatformLogo platformLogo = PlatformLogos.GetPlatformLogo(platform.PlatformLogo.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(platform));
|
try
|
||||||
|
{
|
||||||
|
PlatformLogo platformLogo = PlatformLogos.GetPlatformLogo(platform.PlatformLogo.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(platform));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Platform Update", "Unable to fetch platform logo", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddPlatformMapping(Platform platform)
|
||||||
|
{
|
||||||
|
// ensure a mapping item exists for this platform
|
||||||
|
Models.PlatformMapping.PlatformMapItem item = new Models.PlatformMapping.PlatformMapItem();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Platform Map", "Checking if " + platform.Name + " is in database.");
|
||||||
|
item = Models.PlatformMapping.GetPlatformMap((long)platform.Id);
|
||||||
|
// exists - skip
|
||||||
|
Logging.Log(Logging.LogType.Information, "Platform Map", "Skipping import of " + platform.Name + " - already in database.");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Platform Map", "Importing " + platform.Name + " from predefined data.");
|
||||||
|
// doesn't exist - add it
|
||||||
|
item = new Models.PlatformMapping.PlatformMapItem{
|
||||||
|
IGDBId = (long)platform.Id,
|
||||||
|
IGDBName = platform.Name,
|
||||||
|
IGDBSlug = platform.Slug,
|
||||||
|
AlternateNames = new List<string>{ platform.AlternativeName }
|
||||||
|
};
|
||||||
|
Models.PlatformMapping.WritePlatformMap(item, false, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,11 +177,26 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
private static async Task<Platform> GetObjectFromServer(string WhereClause)
|
private static async Task<Platform> GetObjectFromServer(string WhereClause)
|
||||||
{
|
{
|
||||||
// get platform metadata
|
// get platform metadata
|
||||||
var results = await igdb.QueryAsync<Platform>(IGDBClient.Endpoints.Platforms, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<Platform>(IGDBClient.Endpoints.Platforms, fieldList, WhereClause);
|
||||||
var result = results.First();
|
var result = results.First();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void AssignAllPlatformsToGameIdZero()
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "SELECT * FROM Platform;";
|
||||||
|
DataTable platformsTable = db.ExecuteCMD(sql);
|
||||||
|
foreach (DataRow platformRow in platformsTable.Rows)
|
||||||
|
{
|
||||||
|
sql = "DELETE FROM Relation_Game_Platforms WHERE GameId = 0 AND PlatformsId = @Id; INSERT INTO Relation_Game_Platforms (GameId, PlatformsId) VALUES (0, @Id);";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("Id", (long)platformRow["Id"]);
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
110
gaseous-server/Classes/Metadata/PlayerPerspectives.cs
Normal file
110
gaseous-server/Classes/Metadata/PlayerPerspectives.cs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
using System;
|
||||||
|
using IGDB;
|
||||||
|
using IGDB.Models;
|
||||||
|
|
||||||
|
|
||||||
|
namespace gaseous_server.Classes.Metadata
|
||||||
|
{
|
||||||
|
public class PlayerPerspectives
|
||||||
|
{
|
||||||
|
const string fieldList = "fields checksum,created_at,name,slug,updated_at,url;";
|
||||||
|
|
||||||
|
public PlayerPerspectives()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PlayerPerspective? GetGame_PlayerPerspectives(long? Id)
|
||||||
|
{
|
||||||
|
if ((Id == 0) || (Id == null))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Task<PlayerPerspective> RetVal = _GetGame_PlayerPerspectives(SearchUsing.id, Id);
|
||||||
|
return RetVal.Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PlayerPerspective GetGame_PlayerPerspectives(string Slug)
|
||||||
|
{
|
||||||
|
Task<PlayerPerspective> RetVal = _GetGame_PlayerPerspectives(SearchUsing.slug, Slug);
|
||||||
|
return RetVal.Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<PlayerPerspective> _GetGame_PlayerPerspectives(SearchUsing searchUsing, object searchValue)
|
||||||
|
{
|
||||||
|
// check database first
|
||||||
|
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
||||||
|
if (searchUsing == SearchUsing.id)
|
||||||
|
{
|
||||||
|
cacheStatus = Storage.GetCacheStatus("PlayerPerspective", (long)searchValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cacheStatus = Storage.GetCacheStatus("PlayerPerspective", (string)searchValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up where clause
|
||||||
|
string WhereClause = "";
|
||||||
|
switch (searchUsing)
|
||||||
|
{
|
||||||
|
case SearchUsing.id:
|
||||||
|
WhereClause = "where id = " + searchValue;
|
||||||
|
break;
|
||||||
|
case SearchUsing.slug:
|
||||||
|
WhereClause = "where slug = " + searchValue;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("Invalid search type");
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerPerspective returnValue = new PlayerPerspective();
|
||||||
|
bool forceImageDownload = false;
|
||||||
|
switch (cacheStatus)
|
||||||
|
{
|
||||||
|
case Storage.CacheStatus.NotPresent:
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue);
|
||||||
|
forceImageDownload = true;
|
||||||
|
break;
|
||||||
|
case Storage.CacheStatus.Expired:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<PlayerPerspective>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Storage.CacheStatus.Current:
|
||||||
|
returnValue = Storage.GetCacheValue<PlayerPerspective>(returnValue, "id", (long)searchValue);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("How did you get here?");
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum SearchUsing
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<PlayerPerspective> GetObjectFromServer(string WhereClause)
|
||||||
|
{
|
||||||
|
// get Game_PlayerPerspectives metadata
|
||||||
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<PlayerPerspective>(IGDBClient.Endpoints.PlayerPerspectives, fieldList, WhereClause);
|
||||||
|
var result = results.First();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
108
gaseous-server/Classes/Metadata/ReleaseDates.cs
Normal file
108
gaseous-server/Classes/Metadata/ReleaseDates.cs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
using System;
|
||||||
|
using IGDB;
|
||||||
|
using IGDB.Models;
|
||||||
|
|
||||||
|
|
||||||
|
namespace gaseous_server.Classes.Metadata
|
||||||
|
{
|
||||||
|
public class ReleaseDates
|
||||||
|
{
|
||||||
|
const string fieldList = "fields category,checksum,created_at,date,game,human,m,platform,region,status,updated_at,y;";
|
||||||
|
|
||||||
|
public ReleaseDates()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ReleaseDate? GetReleaseDates(long? Id)
|
||||||
|
{
|
||||||
|
if ((Id == 0) || (Id == null))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Task<ReleaseDate> RetVal = _GetReleaseDates(SearchUsing.id, Id);
|
||||||
|
return RetVal.Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ReleaseDate GetReleaseDates(string Slug)
|
||||||
|
{
|
||||||
|
Task<ReleaseDate> RetVal = _GetReleaseDates(SearchUsing.slug, Slug);
|
||||||
|
return RetVal.Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<ReleaseDate> _GetReleaseDates(SearchUsing searchUsing, object searchValue)
|
||||||
|
{
|
||||||
|
// check database first
|
||||||
|
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
||||||
|
if (searchUsing == SearchUsing.id)
|
||||||
|
{
|
||||||
|
cacheStatus = Storage.GetCacheStatus("ReleaseDate", (long)searchValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cacheStatus = Storage.GetCacheStatus("ReleaseDate", (string)searchValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up where clause
|
||||||
|
string WhereClause = "";
|
||||||
|
switch (searchUsing)
|
||||||
|
{
|
||||||
|
case SearchUsing.id:
|
||||||
|
WhereClause = "where id = " + searchValue;
|
||||||
|
break;
|
||||||
|
case SearchUsing.slug:
|
||||||
|
WhereClause = "where slug = " + searchValue;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("Invalid search type");
|
||||||
|
}
|
||||||
|
|
||||||
|
ReleaseDate returnValue = new ReleaseDate();
|
||||||
|
switch (cacheStatus)
|
||||||
|
{
|
||||||
|
case Storage.CacheStatus.NotPresent:
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue);
|
||||||
|
break;
|
||||||
|
case Storage.CacheStatus.Expired:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<ReleaseDate>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Storage.CacheStatus.Current:
|
||||||
|
returnValue = Storage.GetCacheValue<ReleaseDate>(returnValue, "id", (long)searchValue);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("How did you get here?");
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum SearchUsing
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<ReleaseDate> GetObjectFromServer(string WhereClause)
|
||||||
|
{
|
||||||
|
// get ReleaseDates metadata
|
||||||
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<ReleaseDate>(IGDBClient.Endpoints.ReleaseDates, fieldList, WhereClause);
|
||||||
|
var result = results.First();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
using MySqlX.XDevAPI.Common;
|
|
||||||
using static gaseous_tools.Config.ConfigFile;
|
|
||||||
|
|
||||||
namespace gaseous_server.Classes.Metadata
|
namespace gaseous_server.Classes.Metadata
|
||||||
{
|
{
|
||||||
@@ -15,13 +13,7 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
public static Screenshot? GetScreenshot(long? Id, string ImagePath, bool GetImages)
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
public static Screenshot? GetScreenshot(long? Id, string LogoPath)
|
|
||||||
{
|
{
|
||||||
if ((Id == 0) || (Id == null))
|
if ((Id == 0) || (Id == null))
|
||||||
{
|
{
|
||||||
@@ -29,18 +21,18 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Task<Screenshot> RetVal = _GetScreenshot(SearchUsing.id, Id, LogoPath);
|
Task<Screenshot> RetVal = _GetScreenshot(SearchUsing.id, Id, ImagePath, GetImages);
|
||||||
return RetVal.Result;
|
return RetVal.Result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Screenshot GetScreenshot(string Slug, string LogoPath)
|
public static Screenshot GetScreenshot(string Slug, string ImagePath, bool GetImages)
|
||||||
{
|
{
|
||||||
Task<Screenshot> RetVal = _GetScreenshot(SearchUsing.slug, Slug, LogoPath);
|
Task<Screenshot> RetVal = _GetScreenshot(SearchUsing.slug, Slug, ImagePath, GetImages);
|
||||||
return RetVal.Result;
|
return RetVal.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<Screenshot> _GetScreenshot(SearchUsing searchUsing, object searchValue, string LogoPath)
|
private static async Task<Screenshot> _GetScreenshot(SearchUsing searchUsing, object searchValue, string ImagePath, bool GetImages = true)
|
||||||
{
|
{
|
||||||
// check database first
|
// check database first
|
||||||
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
||||||
@@ -69,18 +61,26 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
|
|
||||||
Screenshot returnValue = new Screenshot();
|
Screenshot returnValue = new Screenshot();
|
||||||
bool forceImageDownload = false;
|
bool forceImageDownload = false;
|
||||||
LogoPath = Path.Combine(LogoPath, "Screenshots");
|
ImagePath = Path.Combine(ImagePath, "Screenshots");
|
||||||
switch (cacheStatus)
|
switch (cacheStatus)
|
||||||
{
|
{
|
||||||
case Storage.CacheStatus.NotPresent:
|
case Storage.CacheStatus.NotPresent:
|
||||||
returnValue = await GetObjectFromServer(WhereClause, LogoPath);
|
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:
|
||||||
returnValue = await GetObjectFromServer(WhereClause, LogoPath);
|
try
|
||||||
Storage.NewCacheValue(returnValue, true);
|
{
|
||||||
forceImageDownload = true;
|
returnValue = await GetObjectFromServer(WhereClause, ImagePath);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
forceImageDownload = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
returnValue = Storage.GetCacheValue<Screenshot>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Storage.CacheStatus.Current:
|
case Storage.CacheStatus.Current:
|
||||||
returnValue = Storage.GetCacheValue<Screenshot>(returnValue, "id", (long)searchValue);
|
returnValue = Storage.GetCacheValue<Screenshot>(returnValue, "id", (long)searchValue);
|
||||||
@@ -89,11 +89,17 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
throw new Exception("How did you get here?");
|
throw new Exception("How did you get here?");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!File.Exists(Path.Combine(LogoPath, "Screenshot.jpg"))) || forceImageDownload == true)
|
// check for presence of "original" quality file - download if absent or force download is true
|
||||||
|
string localFile = Path.Combine(ImagePath, Communications.IGDBAPI_ImageSize.original.ToString(), returnValue.ImageId + ".jpg");
|
||||||
|
if (GetImages == true)
|
||||||
{
|
{
|
||||||
//GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_thumb, returnValue.ImageId);
|
if ((!File.Exists(localFile)) || forceImageDownload == true)
|
||||||
//GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_logo_med, returnValue.ImageId);
|
{
|
||||||
GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_original, returnValue.ImageId);
|
Logging.Log(Logging.LogType.Information, "Metadata: " + returnValue.GetType().Name, "Screenshot download forced.");
|
||||||
|
|
||||||
|
Communications comms = new Communications();
|
||||||
|
comms.GetSpecificImageFromServer(ImagePath, returnValue.ImageId, Communications.IGDBAPI_ImageSize.original, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnValue;
|
return returnValue;
|
||||||
@@ -105,64 +111,15 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
slug
|
slug
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<Screenshot> GetObjectFromServer(string WhereClause, string LogoPath)
|
private static async Task<Screenshot> GetObjectFromServer(string WhereClause, string ImagePath)
|
||||||
{
|
{
|
||||||
// get Screenshot metadata
|
// get Screenshot metadata
|
||||||
var results = await igdb.QueryAsync<Screenshot>(IGDBClient.Endpoints.Screenshots, query: fieldList + " " + WhereClause + ";");
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<Screenshot>(IGDBClient.Endpoints.Screenshots, fieldList, WhereClause);
|
||||||
var result = results.First();
|
var result = results.First();
|
||||||
|
|
||||||
//GetImageFromServer(result.Url, LogoPath, LogoSize.t_thumb, result.ImageId);
|
|
||||||
//GetImageFromServer(result.Url, LogoPath, LogoSize.t_logo_med, result.ImageId);
|
|
||||||
GetImageFromServer(result.Url, LogoPath, LogoSize.t_original, result.ImageId);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GetImageFromServer(string Url, string LogoPath, LogoSize logoSize, string ImageId)
|
|
||||||
{
|
|
||||||
using (var client = new HttpClient())
|
|
||||||
{
|
|
||||||
string fileName = "Artwork.jpg";
|
|
||||||
string extension = "jpg";
|
|
||||||
switch (logoSize)
|
|
||||||
{
|
|
||||||
case LogoSize.t_thumb:
|
|
||||||
fileName = "_Thumb";
|
|
||||||
extension = "jpg";
|
|
||||||
break;
|
|
||||||
case LogoSize.t_logo_med:
|
|
||||||
fileName = "_Medium";
|
|
||||||
extension = "png";
|
|
||||||
break;
|
|
||||||
case LogoSize.t_original:
|
|
||||||
fileName = "";
|
|
||||||
extension = "png";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fileName = "Artwork";
|
|
||||||
extension = "jpg";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fileName = ImageId + fileName;
|
|
||||||
string imageUrl = Url.Replace(LogoSize.t_thumb.ToString(), logoSize.ToString()).Replace("jpg", extension);
|
|
||||||
|
|
||||||
using (var s = client.GetStreamAsync("https:" + imageUrl))
|
|
||||||
{
|
|
||||||
if (!Directory.Exists(LogoPath)) { Directory.CreateDirectory(LogoPath); }
|
|
||||||
using (var fs = new FileStream(Path.Combine(LogoPath, fileName + "." + extension), FileMode.OpenOrCreate))
|
|
||||||
{
|
|
||||||
s.Result.CopyTo(fs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum LogoSize
|
|
||||||
{
|
|
||||||
t_thumb,
|
|
||||||
t_logo_med,
|
|
||||||
t_original
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
using IGDB;
|
||||||
using IGDB.Models;
|
using IGDB.Models;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
||||||
namespace gaseous_server.Classes.Metadata
|
namespace gaseous_server.Classes.Metadata
|
||||||
{
|
{
|
||||||
@@ -18,12 +18,12 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
return _GetCacheStatus(Endpoint, "id", Id);
|
return _GetCacheStatus(Endpoint, "id", Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CacheStatus GetCacheStatus(DataRow Row)
|
public static CacheStatus GetCacheStatus(DataRow Row)
|
||||||
@@ -48,7 +48,7 @@ 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 gaseous_tools.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;
|
||||||
|
|
||||||
@@ -134,6 +134,9 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
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);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "int32[]":
|
case "int32[]":
|
||||||
newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectValue);
|
newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectValue);
|
||||||
@@ -156,7 +159,7 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
}
|
}
|
||||||
|
|
||||||
// execute sql
|
// execute sql
|
||||||
Database db = new gaseous_tools.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +167,7 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
{
|
{
|
||||||
string Endpoint = EndpointType.GetType().Name;
|
string Endpoint = EndpointType.GetType().Name;
|
||||||
|
|
||||||
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
|
||||||
string sql = "SELECT * FROM " + Endpoint + " WHERE " + SearchField + " = @" + SearchField;
|
string sql = "SELECT * FROM " + Endpoint + " WHERE " + SearchField + " = @" + SearchField;
|
||||||
|
|
||||||
@@ -181,7 +184,9 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
DataRow dataRow = dt.Rows[0];
|
DataRow dataRow = dt.Rows[0];
|
||||||
return BuildCacheObject<T>(EndpointType, dataRow);
|
object returnObject = BuildCacheObject<T>(EndpointType, dataRow);
|
||||||
|
|
||||||
|
return (T)returnObject;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,6 +369,15 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
case "[igdb.models.startdatecategory":
|
case "[igdb.models.startdatecategory":
|
||||||
property.SetValue(EndpointType, (StartDateCategory)dataRow[property.Name]);
|
property.SetValue(EndpointType, (StartDateCategory)dataRow[property.Name]);
|
||||||
break;
|
break;
|
||||||
|
case "[igdb.models.releasedateregion":
|
||||||
|
property.SetValue(EndpointType, (ReleaseDateRegion)dataRow[property.Name]);
|
||||||
|
break;
|
||||||
|
case "[igdb.models.releasedatecategory":
|
||||||
|
property.SetValue(EndpointType, (ReleaseDateCategory)dataRow[property.Name]);
|
||||||
|
break;
|
||||||
|
case "[gaseous_server.classes.metadata.agegroups+agerestrictiongroupings":
|
||||||
|
property.SetValue(EndpointType, (AgeGroups.AgeRestrictionGroupings)dataRow[property.Name]);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
property.SetValue(EndpointType, dataRow[property.Name]);
|
property.SetValue(EndpointType, dataRow[property.Name]);
|
||||||
break;
|
break;
|
||||||
@@ -380,6 +394,52 @@ namespace gaseous_server.Classes.Metadata
|
|||||||
|
|
||||||
return EndpointType;
|
return EndpointType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void StoreRelations(string PrimaryTable, string SecondaryTable, long ObjectId, string Relations)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// clean existing records for this object
|
||||||
|
sql = "DELETE FROM " + TableName + " WHERE `" + PrimaryTable + "Id` = @objectid";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("objectid", ObjectId);
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert data
|
||||||
|
long[] RelationValues = Newtonsoft.Json.JsonConvert.DeserializeObject<long[]>(Relations);
|
||||||
|
foreach (long RelationValue in RelationValues)
|
||||||
|
{
|
||||||
|
sql = "INSERT INTO " + TableName + " (`" + PrimaryTable + "Id`, `" + SecondaryTable + "Id`) VALUES (@objectid, @relationvalue);";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("objectid", ObjectId);
|
||||||
|
dbDict.Add("relationvalue", RelationValue);
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemoryCacheObject
|
||||||
|
{
|
||||||
|
public object Object { get; set; }
|
||||||
|
public DateTime CreationTime { get; } = DateTime.UtcNow;
|
||||||
|
public DateTime ExpiryTime
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return CreationTime.AddMinutes(60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
110
gaseous-server/Classes/Metadata/Themes.cs
Normal file
110
gaseous-server/Classes/Metadata/Themes.cs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
using System;
|
||||||
|
using IGDB;
|
||||||
|
using IGDB.Models;
|
||||||
|
|
||||||
|
|
||||||
|
namespace gaseous_server.Classes.Metadata
|
||||||
|
{
|
||||||
|
public class Themes
|
||||||
|
{
|
||||||
|
const string fieldList = "fields checksum,created_at,name,slug,updated_at,url;";
|
||||||
|
|
||||||
|
public Themes()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Theme? GetGame_Themes(long? Id)
|
||||||
|
{
|
||||||
|
if ((Id == 0) || (Id == null))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Task<Theme> RetVal = _GetGame_Themes(SearchUsing.id, Id);
|
||||||
|
return RetVal.Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Theme GetGame_Themes(string Slug)
|
||||||
|
{
|
||||||
|
Task<Theme> RetVal = _GetGame_Themes(SearchUsing.slug, Slug);
|
||||||
|
return RetVal.Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<Theme> _GetGame_Themes(SearchUsing searchUsing, object searchValue)
|
||||||
|
{
|
||||||
|
// check database first
|
||||||
|
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
||||||
|
if (searchUsing == SearchUsing.id)
|
||||||
|
{
|
||||||
|
cacheStatus = Storage.GetCacheStatus("Theme", (long)searchValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cacheStatus = Storage.GetCacheStatus("Theme", (string)searchValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up where clause
|
||||||
|
string WhereClause = "";
|
||||||
|
switch (searchUsing)
|
||||||
|
{
|
||||||
|
case SearchUsing.id:
|
||||||
|
WhereClause = "where id = " + searchValue;
|
||||||
|
break;
|
||||||
|
case SearchUsing.slug:
|
||||||
|
WhereClause = "where slug = " + searchValue;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("Invalid search type");
|
||||||
|
}
|
||||||
|
|
||||||
|
Theme returnValue = new Theme();
|
||||||
|
bool forceImageDownload = false;
|
||||||
|
switch (cacheStatus)
|
||||||
|
{
|
||||||
|
case Storage.CacheStatus.NotPresent:
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue);
|
||||||
|
forceImageDownload = true;
|
||||||
|
break;
|
||||||
|
case Storage.CacheStatus.Expired:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
returnValue = await GetObjectFromServer(WhereClause);
|
||||||
|
Storage.NewCacheValue(returnValue, true);
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||||
|
return Storage.GetCacheValue<Theme>(returnValue, "id", (long)searchValue);
|
||||||
|
}
|
||||||
|
case Storage.CacheStatus.Current:
|
||||||
|
returnValue = Storage.GetCacheValue<Theme>(returnValue, "id", (long)searchValue);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("How did you get here?");
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum SearchUsing
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<Theme> GetObjectFromServer(string WhereClause)
|
||||||
|
{
|
||||||
|
// get Game_Themes metadata
|
||||||
|
Communications comms = new Communications();
|
||||||
|
var results = await comms.APIComm<Theme>(IGDBClient.Endpoints.Themes, fieldList, WhereClause);
|
||||||
|
var result = results.First();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,29 +1,74 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using gaseous_tools;
|
using gaseous_server.Models;
|
||||||
|
|
||||||
namespace gaseous_server.Classes
|
namespace gaseous_server.Classes
|
||||||
{
|
{
|
||||||
public class MetadataManagement
|
public class MetadataManagement : QueueItemStatus
|
||||||
{
|
{
|
||||||
public static void RefreshMetadata(bool forceRefresh = false)
|
public void RefreshMetadata(bool forceRefresh = false)
|
||||||
{
|
{
|
||||||
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
string sql = "SELECT Id, `Name` FROM Game;";
|
string sql = "";
|
||||||
DataTable dt = db.ExecuteCMD(sql);
|
DataTable dt = new DataTable();
|
||||||
|
|
||||||
|
// disabling forceRefresh
|
||||||
|
forceRefresh = false;
|
||||||
|
|
||||||
|
// update platforms
|
||||||
|
sql = "SELECT Id, `Name` FROM Platform;";
|
||||||
|
dt = db.ExecuteCMD(sql);
|
||||||
|
|
||||||
|
int StatusCounter = 1;
|
||||||
foreach (DataRow dr in dt.Rows)
|
foreach (DataRow dr in dt.Rows)
|
||||||
{
|
{
|
||||||
|
SetStatus(StatusCounter, dt.Rows.Count, "Refreshing metadata for platform " + dr["name"]);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logging.Log(Logging.LogType.Information, "Metadata Refresh", "Refreshing metadata for game " + dr["name"] + " (" + dr["id"] + ")");
|
Logging.Log(Logging.LogType.Information, "Metadata Refresh", "(" + StatusCounter + "/" + dt.Rows.Count + "): Refreshing metadata for platform " + dr["name"] + " (" + dr["id"] + ")");
|
||||||
Metadata.Games.GetGame((long)dr["id"], true, forceRefresh);
|
Metadata.Platforms.GetPlatform((long)dr["id"], true);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.Log(Logging.LogType.Critical, "Metadata Refresh", "An error occurred while refreshing metadata for " + dr["name"], ex);
|
Logging.Log(Logging.LogType.Critical, "Metadata Refresh", "An error occurred while refreshing metadata for " + dr["name"], ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StatusCounter += 1;
|
||||||
}
|
}
|
||||||
|
ClearStatus();
|
||||||
|
|
||||||
|
// update games
|
||||||
|
if (forceRefresh == true)
|
||||||
|
{
|
||||||
|
// when forced, only update games with ROMs for
|
||||||
|
sql = "SELECT Id, `Name` FROM view_GamesWithRoms;";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// when run normally, update all games (since this will honour cache timeouts)
|
||||||
|
sql = "SELECT Id, `Name` FROM Game;";
|
||||||
|
}
|
||||||
|
dt = db.ExecuteCMD(sql);
|
||||||
|
|
||||||
|
StatusCounter = 1;
|
||||||
|
foreach (DataRow dr in dt.Rows)
|
||||||
|
{
|
||||||
|
SetStatus(StatusCounter, dt.Rows.Count, "Refreshing metadata for game " + dr["name"]);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Metadata Refresh", "(" + StatusCounter + "/" + dt.Rows.Count + "): Refreshing metadata for game " + dr["name"] + " (" + dr["id"] + ")");
|
||||||
|
Metadata.Games.GetGame((long)dr["id"], true, false, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Critical, "Metadata Refresh", "An error occurred while refreshing metadata for " + dr["name"], ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusCounter += 1;
|
||||||
|
}
|
||||||
|
ClearStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
41
gaseous-server/Classes/QueueItemStatus.cs
Normal file
41
gaseous-server/Classes/QueueItemStatus.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
namespace gaseous_server.Classes
|
||||||
|
{
|
||||||
|
public class QueueItemStatus
|
||||||
|
{
|
||||||
|
internal ProcessQueue.QueueItem? CallingQueueItem = null;
|
||||||
|
|
||||||
|
private int _CurrentItemNumber = 0;
|
||||||
|
private int _MaxItemsNumber = 0;
|
||||||
|
private string _StatusText = "";
|
||||||
|
|
||||||
|
public int CurrentItemNumber => _CurrentItemNumber;
|
||||||
|
public int MaxItemsNumber => _MaxItemsNumber;
|
||||||
|
public string StatusText => _StatusText;
|
||||||
|
|
||||||
|
public void SetStatus(int CurrentItemNumber, int MaxItemsNumber, string StatusText)
|
||||||
|
{
|
||||||
|
this._CurrentItemNumber = CurrentItemNumber;
|
||||||
|
this._MaxItemsNumber = MaxItemsNumber;
|
||||||
|
this._StatusText = StatusText;
|
||||||
|
|
||||||
|
if (CallingQueueItem != null)
|
||||||
|
{
|
||||||
|
CallingQueueItem.CurrentState = _CurrentItemNumber + " of " + _MaxItemsNumber + ": " + _StatusText;
|
||||||
|
CallingQueueItem.CurrentStateProgress = _CurrentItemNumber + " of " + _MaxItemsNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearStatus()
|
||||||
|
{
|
||||||
|
this._CurrentItemNumber = 0;
|
||||||
|
this._MaxItemsNumber = 0;
|
||||||
|
this._StatusText = "";
|
||||||
|
|
||||||
|
if (CallingQueueItem != null)
|
||||||
|
{
|
||||||
|
CallingQueueItem.CurrentState = "";
|
||||||
|
CallingQueueItem.CurrentStateProgress = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
480
gaseous-server/Classes/RomMediaGroup.cs
Normal file
480
gaseous-server/Classes/RomMediaGroup.cs
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data;
|
||||||
|
using gaseous_signature_parser.models.RomSignatureObject;
|
||||||
|
using Microsoft.VisualBasic;
|
||||||
|
using IGDB.Models;
|
||||||
|
using gaseous_server.Classes.Metadata;
|
||||||
|
using System.IO.Compression;
|
||||||
|
|
||||||
|
namespace gaseous_server.Classes
|
||||||
|
{
|
||||||
|
public class RomMediaGroup
|
||||||
|
{
|
||||||
|
public class InvalidMediaGroupId : Exception
|
||||||
|
{
|
||||||
|
public InvalidMediaGroupId(long Id) : base("Unable to find media group by id " + Id)
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameRomMediaGroupItem CreateMediaGroup(long GameId, long PlatformId, List<long> RomIds)
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "INSERT INTO RomMediaGroup (Status, PlatformId, GameId) VALUES (@status, @platformid, @gameid); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("status", GameRomMediaGroupItem.GroupBuildStatus.WaitingForBuild);
|
||||||
|
dbDict.Add("gameid", GameId);
|
||||||
|
dbDict.Add("platformid", PlatformId);
|
||||||
|
DataTable mgInsert = db.ExecuteCMD(sql, dbDict);
|
||||||
|
long mgId = (long)mgInsert.Rows[0][0];
|
||||||
|
foreach (long RomId in RomIds)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Roms.GameRomItem gameRomItem = Roms.GetRom(RomId);
|
||||||
|
if (gameRomItem.PlatformId == PlatformId)
|
||||||
|
{
|
||||||
|
sql = "INSERT INTO RomMediaGroup_Members (GroupId, RomId) VALUES (@groupid, @romid);";
|
||||||
|
dbDict.Clear();
|
||||||
|
dbDict.Add("groupid", mgId);
|
||||||
|
dbDict.Add("romid", RomId);
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Media Group", "Unable to add ROM id " + RomId + " to group. ROM platform is different from group platform.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Roms.InvalidRomId irid)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Media Group", "Unable to add ROM id " + RomId + " to group. ROM doesn't exist", irid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StartMediaGroupBuild(mgId);
|
||||||
|
|
||||||
|
return GetMediaGroup(mgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameRomMediaGroupItem GetMediaGroup(long Id, string userid = "")
|
||||||
|
{
|
||||||
|
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.Id=@id;";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "id", Id },
|
||||||
|
{ "userid", userid }
|
||||||
|
};
|
||||||
|
|
||||||
|
DataTable dataTable = db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
if (dataTable.Rows.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidMediaGroupId(Id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GameRomMediaGroupItem mediaGroupItem = BuildMediaGroupFromRow(dataTable.Rows[0]);
|
||||||
|
return mediaGroupItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<GameRomMediaGroupItem> GetMediaGroupsFromGameId(long GameId, string userid = "")
|
||||||
|
{
|
||||||
|
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;";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "gameid", GameId },
|
||||||
|
{ "userid", userid }
|
||||||
|
};
|
||||||
|
|
||||||
|
DataTable dataTable = db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
List<GameRomMediaGroupItem> mediaGroupItems = new List<GameRomMediaGroupItem>();
|
||||||
|
|
||||||
|
foreach (DataRow row in dataTable.Rows)
|
||||||
|
{
|
||||||
|
mediaGroupItems.Add(BuildMediaGroupFromRow(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaGroupItems.Sort((x, y) => x.Platform.CompareTo(y.Platform));
|
||||||
|
|
||||||
|
return mediaGroupItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameRomMediaGroupItem EditMediaGroup(long Id, List<long> RomIds)
|
||||||
|
{
|
||||||
|
GameRomMediaGroupItem mg = GetMediaGroup(Id);
|
||||||
|
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
// delete roms from group
|
||||||
|
sql = "DELETE FROM RomMediaGroup_Members WHERE GroupId=@groupid;";
|
||||||
|
dbDict.Clear();
|
||||||
|
dbDict.Add("groupid", Id);
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
// add roms to group
|
||||||
|
foreach (long RomId in RomIds)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Roms.GameRomItem gameRomItem = Roms.GetRom(RomId);
|
||||||
|
if (gameRomItem.PlatformId == mg.PlatformId)
|
||||||
|
{
|
||||||
|
sql = "INSERT INTO RomMediaGroup_Members (GroupId, RomId) VALUES (@groupid, @romid);";
|
||||||
|
dbDict.Clear();
|
||||||
|
dbDict.Add("groupid", Id);
|
||||||
|
dbDict.Add("romid", RomId);
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Media Group", "Unable to add ROM id " + RomId + " to group. ROM platform is different from group platform.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Roms.InvalidRomId irid)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Media Group", "Unable to add ROM id " + RomId + " to group. ROM doesn't exist", irid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set group to rebuild
|
||||||
|
sql = "UPDATE RomMediaGroup SET Status=1 WHERE GroupId=@groupid;";
|
||||||
|
dbDict.Clear();
|
||||||
|
dbDict.Add("groupid", Id);
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
string MediaGroupZipPath = Path.Combine(Config.LibraryConfiguration.LibraryMediaGroupDirectory, Id + ".zip");
|
||||||
|
if (File.Exists(MediaGroupZipPath))
|
||||||
|
{
|
||||||
|
File.Delete(MediaGroupZipPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
StartMediaGroupBuild(Id);
|
||||||
|
|
||||||
|
// return to caller
|
||||||
|
return GetMediaGroup(Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DeleteMediaGroup(long Id)
|
||||||
|
{
|
||||||
|
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;";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("id", Id);
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
string MediaGroupZipPath = Path.Combine(Config.LibraryConfiguration.LibraryMediaGroupDirectory, Id + ".zip");
|
||||||
|
if (File.Exists(MediaGroupZipPath))
|
||||||
|
{
|
||||||
|
File.Delete(MediaGroupZipPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static GameRomMediaGroupItem BuildMediaGroupFromRow(DataRow row)
|
||||||
|
{
|
||||||
|
bool hasSaveStates = false;
|
||||||
|
if (row.Table.Columns.Contains("GameStateRomId"))
|
||||||
|
{
|
||||||
|
if (row["GameStateRomId"] != DBNull.Value)
|
||||||
|
{
|
||||||
|
hasSaveStates = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GameRomMediaGroupItem mediaGroupItem = new GameRomMediaGroupItem();
|
||||||
|
mediaGroupItem.Id = (long)row["Id"];
|
||||||
|
mediaGroupItem.Status = (GameRomMediaGroupItem.GroupBuildStatus)row["Status"];
|
||||||
|
mediaGroupItem.PlatformId = (long)row["PlatformId"];
|
||||||
|
mediaGroupItem.GameId = (long)row["GameId"];
|
||||||
|
mediaGroupItem.RomIds = new List<long>();
|
||||||
|
mediaGroupItem.Roms = new List<Roms.GameRomItem>();
|
||||||
|
mediaGroupItem.HasSaveStates = hasSaveStates;
|
||||||
|
|
||||||
|
// get members
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "SELECT * FROM RomMediaGroup_Members WHERE GroupId=@id;";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("id", mediaGroupItem.Id);
|
||||||
|
DataTable data = db.ExecuteCMD(sql, dbDict);
|
||||||
|
foreach (DataRow dataRow in data.Rows)
|
||||||
|
{
|
||||||
|
mediaGroupItem.RomIds.Add((long)dataRow["RomId"]);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mediaGroupItem.Roms.Add(Roms.GetRom((long)dataRow["RomId"]));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Rom Group", "Unable to load ROM data", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void StartMediaGroupBuild(long Id)
|
||||||
|
{
|
||||||
|
GameRomMediaGroupItem mediaGroupItem = GetMediaGroup(Id);
|
||||||
|
|
||||||
|
if (mediaGroupItem.Status != GameRomMediaGroupItem.GroupBuildStatus.Building)
|
||||||
|
{
|
||||||
|
// set collection item to waitingforbuild
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "UPDATE RomMediaGroup SET Status=@bs WHERE Id=@id";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("id", Id);
|
||||||
|
dbDict.Add("bs", GameRomMediaGroupItem.GroupBuildStatus.WaitingForBuild);
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
// start background task
|
||||||
|
ProcessQueue.QueueItem queueItem = new ProcessQueue.QueueItem(ProcessQueue.QueueItemType.MediaGroupCompiler, 1, false, true);
|
||||||
|
queueItem.Options = Id;
|
||||||
|
queueItem.ForceExecute();
|
||||||
|
ProcessQueue.QueueItems.Add(queueItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CompileMediaGroup(long Id)
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
|
||||||
|
GameRomMediaGroupItem mediaGroupItem = GetMediaGroup(Id);
|
||||||
|
if (mediaGroupItem.Status == GameRomMediaGroupItem.GroupBuildStatus.WaitingForBuild)
|
||||||
|
{
|
||||||
|
Game GameObject = Games.GetGame(mediaGroupItem.GameId, false, false, false);
|
||||||
|
Platform PlatformObject = Platforms.GetPlatform(mediaGroupItem.PlatformId, false);
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Information, "Media Group", "Beginning build of media group: " + GameObject.Name + " for platform " + PlatformObject.Name);
|
||||||
|
|
||||||
|
// set starting
|
||||||
|
string sql = "UPDATE RomMediaGroup SET Status=@bs WHERE Id=@id";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("id", mediaGroupItem.Id);
|
||||||
|
dbDict.Add("bs", GameRomMediaGroupItem.GroupBuildStatus.Building);
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
string ZipFilePath = Path.Combine(Config.LibraryConfiguration.LibraryMediaGroupDirectory, mediaGroupItem.Id + ".zip");
|
||||||
|
string ZipFileTempPath = Path.Combine(Config.LibraryConfiguration.LibraryTempDirectory, mediaGroupItem.Id.ToString());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// clean up if needed
|
||||||
|
if (File.Exists(ZipFilePath))
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Media Group", "Deleting existing build of media group: " + GameObject.Name + " for platform " + PlatformObject.Name);
|
||||||
|
File.Delete(ZipFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Directory.Exists(ZipFileTempPath))
|
||||||
|
{
|
||||||
|
Directory.Delete(ZipFileTempPath, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// gather media group files
|
||||||
|
Directory.CreateDirectory(ZipFileTempPath);
|
||||||
|
List<Roms.GameRomItem> romItems = new List<Roms.GameRomItem>();
|
||||||
|
List<string> M3UFileContents = new List<string>();
|
||||||
|
foreach (long RomId in mediaGroupItem.RomIds)
|
||||||
|
{
|
||||||
|
Roms.GameRomItem rom = Roms.GetRom(RomId);
|
||||||
|
if (File.Exists(rom.Path))
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Media Group", "Copying ROM: " + rom.Name);
|
||||||
|
File.Copy(rom.Path, Path.Combine(ZipFileTempPath, Path.GetFileName(rom.Path)));
|
||||||
|
|
||||||
|
romItems.Add(rom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build m3u
|
||||||
|
romItems.Sort((a, b) =>
|
||||||
|
{
|
||||||
|
if (a.MediaDetail != null)
|
||||||
|
{
|
||||||
|
if (a.MediaDetail.Number != null && a.MediaDetail.Side != null)
|
||||||
|
{
|
||||||
|
var firstCompare = a.MediaDetail.Number.ToString().CompareTo(b.MediaDetail.Number.ToString());
|
||||||
|
return firstCompare != 0 ? firstCompare : a.MediaDetail.Side.CompareTo(b.MediaDetail.Side);
|
||||||
|
}
|
||||||
|
else if (a.MediaDetail.Number != null && a.MediaDetail.Side == null)
|
||||||
|
{
|
||||||
|
return a.MediaDetail.Number.ToString().CompareTo(b.MediaDetail.Number.ToString());
|
||||||
|
}
|
||||||
|
else if (a.MediaDetail.Number == null && a.MediaDetail.Side != null)
|
||||||
|
{
|
||||||
|
return a.MediaDetail.Side.ToString().CompareTo(b.MediaDetail.Side.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return a.Name.CompareTo(b.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return a.Name.CompareTo(b.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
foreach (Roms.GameRomItem romItem in romItems)
|
||||||
|
{
|
||||||
|
string M3UFileContent = "";
|
||||||
|
M3UFileContent += romItem.Name;
|
||||||
|
if (romItem.MediaLabel.Length == 0)
|
||||||
|
{
|
||||||
|
if (romItem.RomTypeMedia.Length > 0)
|
||||||
|
{
|
||||||
|
M3UFileContent += "|" + romItem.RomTypeMedia;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
M3UFileContent += "|" + romItem.MediaLabel;
|
||||||
|
}
|
||||||
|
M3UFileContents.Add(M3UFileContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllText(Path.Combine(ZipFileTempPath, GameObject.Name + ".m3u"), String.Join(Environment.NewLine, M3UFileContents));
|
||||||
|
|
||||||
|
// compress to zip
|
||||||
|
Logging.Log(Logging.LogType.Information, "Media Group", "Compressing media group");
|
||||||
|
if (!Directory.Exists(Config.LibraryConfiguration.LibraryMediaGroupDirectory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(Config.LibraryConfiguration.LibraryMediaGroupDirectory);
|
||||||
|
}
|
||||||
|
ZipFile.CreateFromDirectory(ZipFileTempPath, ZipFilePath, CompressionLevel.SmallestSize, false);
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
if (Directory.Exists(ZipFileTempPath))
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Media Group", "Cleaning up");
|
||||||
|
Directory.Delete(ZipFileTempPath, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set completed
|
||||||
|
dbDict["bs"] = GameRomMediaGroupItem.GroupBuildStatus.Completed;
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// clean up
|
||||||
|
if (Directory.Exists(ZipFileTempPath))
|
||||||
|
{
|
||||||
|
Directory.Delete(ZipFileTempPath, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(ZipFilePath))
|
||||||
|
{
|
||||||
|
File.Delete(ZipFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set failed
|
||||||
|
dbDict["bs"] = GameRomMediaGroupItem.GroupBuildStatus.Failed;
|
||||||
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Critical, "Media Group", "Media Group building has failed", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GameRomMediaGroupItem
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public long GameId { get; set; }
|
||||||
|
public long PlatformId { get; set; }
|
||||||
|
public string Platform {
|
||||||
|
get
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Platforms.GetPlatform(PlatformId, false).Name;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Models.PlatformMapping.PlatformMapItem.WebEmulatorItem? Emulator { get; set; }
|
||||||
|
public List<long> RomIds { get; set; }
|
||||||
|
public List<Roms.GameRomItem> Roms { get; set; }
|
||||||
|
public bool HasSaveStates { get; set; } = false;
|
||||||
|
private GroupBuildStatus _Status { get; set; }
|
||||||
|
public GroupBuildStatus Status {
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_Status == GroupBuildStatus.Completed)
|
||||||
|
{
|
||||||
|
if (File.Exists(MediaGroupZipPath))
|
||||||
|
{
|
||||||
|
return GroupBuildStatus.Completed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return GroupBuildStatus.NoStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _Status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_Status = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public long? Size {
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Status == GroupBuildStatus.Completed)
|
||||||
|
{
|
||||||
|
if (File.Exists(MediaGroupZipPath))
|
||||||
|
{
|
||||||
|
FileInfo fi = new FileInfo(MediaGroupZipPath);
|
||||||
|
return fi.Length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internal string MediaGroupZipPath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(Config.LibraryConfiguration.LibraryMediaGroupDirectory, Id + ".zip");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public enum GroupBuildStatus
|
||||||
|
{
|
||||||
|
NoStatus = 0,
|
||||||
|
WaitingForBuild = 1,
|
||||||
|
Building = 2,
|
||||||
|
Completed = 3,
|
||||||
|
Failed = 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,39 +1,89 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using gaseous_tools;
|
using gaseous_signature_parser.models.RomSignatureObject;
|
||||||
|
using static gaseous_server.Classes.RomMediaGroup;
|
||||||
|
using gaseous_server.Classes.Metadata;
|
||||||
|
using IGDB.Models;
|
||||||
|
|
||||||
namespace gaseous_server.Classes
|
namespace gaseous_server.Classes
|
||||||
{
|
{
|
||||||
public class Roms
|
public class Roms
|
||||||
{
|
{
|
||||||
public static List<GameRomItem> GetRoms(long GameId)
|
public class InvalidRomId : Exception
|
||||||
|
{
|
||||||
|
public InvalidRomId(long Id) : base("Unable to find ROM by id " + Id)
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameRomObject GetRoms(long GameId, long PlatformId = -1, string NameSearch = "", int pageNumber = 0, int pageSize = 0, string userid = "")
|
||||||
{
|
{
|
||||||
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
GameRomObject GameRoms = new GameRomObject();
|
||||||
string sql = "SELECT * FROM Games_Roms WHERE GameId = @id ORDER BY `Name`";
|
|
||||||
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "";
|
||||||
|
string sqlCount = "";
|
||||||
|
string sqlPlatform = "";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
dbDict.Add("id", GameId);
|
dbDict.Add("id", GameId);
|
||||||
|
dbDict.Add("userid", userid);
|
||||||
|
|
||||||
|
string NameSearchWhere = "";
|
||||||
|
if (NameSearch.Length > 0)
|
||||||
|
{
|
||||||
|
NameSearchWhere = " AND Games_Roms.`Name` LIKE @namesearch";
|
||||||
|
dbDict.Add("namesearch", '%' + NameSearch + '%');
|
||||||
|
}
|
||||||
|
|
||||||
|
// platform query
|
||||||
|
sqlPlatform = "SELECT DISTINCT Games_Roms.PlatformId, Platform.`Name` FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id WHERE GameId = @id ORDER BY Platform.`Name`;";
|
||||||
|
|
||||||
|
if (PlatformId == -1) {
|
||||||
|
// 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;";
|
||||||
|
|
||||||
|
// 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
|
||||||
|
sqlCount = "SELECT COUNT(Games_Roms.Id) AS RomCount FROM Games_Roms WHERE Games_Roms.GameId = @id AND Games_Roms.PlatformId = @platformid" + NameSearchWhere + ";";
|
||||||
|
|
||||||
|
dbDict.Add("platformid", PlatformId);
|
||||||
|
}
|
||||||
DataTable romDT = db.ExecuteCMD(sql, dbDict);
|
DataTable romDT = db.ExecuteCMD(sql, dbDict);
|
||||||
|
Dictionary<string, object> rowCount = db.ExecuteCMDDict(sqlCount, dbDict)[0];
|
||||||
|
DataTable platformDT = db.ExecuteCMD(sqlPlatform, dbDict);
|
||||||
|
|
||||||
if (romDT.Rows.Count > 0)
|
if (romDT.Rows.Count > 0)
|
||||||
{
|
{
|
||||||
List<GameRomItem> romItems = new List<GameRomItem>();
|
// set count of roms
|
||||||
foreach (DataRow romDR in romDT.Rows)
|
GameRoms.Count = int.Parse((string)rowCount["RomCount"]);
|
||||||
|
|
||||||
|
int pageOffset = pageSize * (pageNumber - 1);
|
||||||
|
for (int i = 0; i < romDT.Rows.Count; i++)
|
||||||
{
|
{
|
||||||
romItems.Add(BuildRom(romDR));
|
GameRomItem gameRomItem = BuildRom(romDT.Rows[i]);
|
||||||
|
|
||||||
|
if ((i >= pageOffset && i < pageOffset + pageSize) || pageSize == 0)
|
||||||
|
{
|
||||||
|
GameRoms.GameRomItems.Add(gameRomItem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return romItems;
|
return GameRoms;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new Exception("Unknown Game Id");
|
throw new Games.InvalidGameId(GameId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GameRomItem GetRom(long RomId)
|
public static GameRomItem GetRom(long RomId)
|
||||||
{
|
{
|
||||||
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
string sql = "SELECT * FROM Games_Roms WHERE Id = @id";
|
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";
|
||||||
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);
|
||||||
@@ -46,7 +96,7 @@ namespace gaseous_server.Classes
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new Exception("Unknown ROM Id");
|
throw new InvalidRomId(RomId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,9 +106,9 @@ namespace gaseous_server.Classes
|
|||||||
IGDB.Models.Platform platform = Classes.Metadata.Platforms.GetPlatform(PlatformId);
|
IGDB.Models.Platform platform = Classes.Metadata.Platforms.GetPlatform(PlatformId);
|
||||||
|
|
||||||
// ensure metadata for gameid is present
|
// ensure metadata for gameid is present
|
||||||
IGDB.Models.Game game = Classes.Metadata.Games.GetGame(GameId, false, false);
|
IGDB.Models.Game game = Classes.Metadata.Games.GetGame(GameId, false, false, false);
|
||||||
|
|
||||||
Database db = new gaseous_tools.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);
|
||||||
@@ -74,66 +124,86 @@ namespace gaseous_server.Classes
|
|||||||
public static void DeleteRom(long RomId)
|
public static void DeleteRom(long RomId)
|
||||||
{
|
{
|
||||||
GameRomItem rom = GetRom(RomId);
|
GameRomItem rom = GetRom(RomId);
|
||||||
if (File.Exists(rom.Path))
|
if (rom.Library.IsDefaultLibrary == true)
|
||||||
{
|
{
|
||||||
File.Delete(rom.Path);
|
if (File.Exists(rom.Path))
|
||||||
}
|
{
|
||||||
|
File.Delete(rom.Path);
|
||||||
|
}
|
||||||
|
|
||||||
Database db = new gaseous_tools.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";
|
string sql = "DELETE FROM Games_Roms WHERE Id = @id; DELETE FROM GameState WHERE RomId = @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);
|
||||||
db.ExecuteCMD(sql, dbDict);
|
db.ExecuteCMD(sql, dbDict);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GameRomItem BuildRom(DataRow romDR)
|
private static GameRomItem BuildRom(DataRow romDR)
|
||||||
{
|
{
|
||||||
GameRomItem romItem = new GameRomItem
|
bool hasSaveStates = false;
|
||||||
|
if (romDR.Table.Columns.Contains("SavedStateRomId"))
|
||||||
|
{
|
||||||
|
if (romDR["SavedStateRomId"] != DBNull.Value)
|
||||||
|
{
|
||||||
|
hasSaveStates = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GameRomItem romItem = new GameRomItem
|
||||||
{
|
{
|
||||||
Id = (long)romDR["id"],
|
Id = (long)romDR["id"],
|
||||||
PlatformId = (long)romDR["platformid"],
|
PlatformId = (long)romDR["platformid"],
|
||||||
Platform = Classes.Metadata.Platforms.GetPlatform((long)romDR["platformid"]),
|
Platform = (string)romDR["platformname"],
|
||||||
GameId = (long)romDR["gameid"],
|
GameId = (long)romDR["gameid"],
|
||||||
Name = (string)romDR["name"],
|
Name = (string)romDR["name"],
|
||||||
Size = (long)romDR["size"],
|
Size = (long)romDR["size"],
|
||||||
CRC = (string)romDR["crc"],
|
Crc = ((string)romDR["crc"]).ToLower(),
|
||||||
MD5 = (string)romDR["md5"],
|
Md5 = ((string)romDR["md5"]).ToLower(),
|
||||||
SHA1 = (string)romDR["sha1"],
|
Sha1 = ((string)romDR["sha1"]).ToLower(),
|
||||||
DevelopmentStatus = (string)romDR["developmentstatus"],
|
DevelopmentStatus = (string)romDR["developmentstatus"],
|
||||||
Flags = Newtonsoft.Json.JsonConvert.DeserializeObject<string[]>((string)romDR["flags"]),
|
Attributes = Newtonsoft.Json.JsonConvert.DeserializeObject<List<KeyValuePair<string, object>>>((string)Common.ReturnValueIfNull(romDR["attributes"], "[ ]")),
|
||||||
RomType = (int)romDR["romtype"],
|
RomType = (HasheousClient.Models.LookupResponseModel.RomItem.RomTypes)(int)romDR["romtype"],
|
||||||
RomTypeMedia = (string)romDR["romtypemedia"],
|
RomTypeMedia = (string)romDR["romtypemedia"],
|
||||||
MediaLabel = (string)romDR["medialabel"],
|
MediaLabel = (string)romDR["medialabel"],
|
||||||
Path = (string)romDR["path"],
|
Path = (string)romDR["path"],
|
||||||
Source = (GameRomItem.SourceType)(Int32)romDR["metadatasource"]
|
SignatureSource = (gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType)(Int32)romDR["metadatasource"],
|
||||||
|
SignatureSourceGameTitle = (string)Common.ReturnValueIfNull(romDR["MetadataGameName"], ""),
|
||||||
|
HasSaveStates = hasSaveStates,
|
||||||
|
Library = GameLibrary.GetLibrary((int)romDR["LibraryId"])
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// check for a web emulator and update the romItem
|
||||||
|
foreach (Models.PlatformMapping.PlatformMapItem platformMapping in Models.PlatformMapping.PlatformMap)
|
||||||
|
{
|
||||||
|
if (platformMapping.IGDBId == romItem.PlatformId)
|
||||||
|
{
|
||||||
|
if (platformMapping.WebEmulator != null)
|
||||||
|
{
|
||||||
|
romItem.Emulator = platformMapping.WebEmulator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return romItem;
|
return romItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GameRomItem
|
public class GameRomObject
|
||||||
{
|
{
|
||||||
public long Id { get; set; }
|
public List<GameRomItem> GameRomItems { get; set; } = new List<GameRomItem>();
|
||||||
public long PlatformId { get; set; }
|
public int Count { get; set; }
|
||||||
public IGDB.Models.Platform Platform { get; set; }
|
}
|
||||||
public long GameId { get; set; }
|
|
||||||
public string? Name { get; set; }
|
|
||||||
public long Size { get; set; }
|
|
||||||
public string? CRC { get; set; }
|
|
||||||
public string? MD5 { get; set; }
|
|
||||||
public string? SHA1 { get; set; }
|
|
||||||
public string? DevelopmentStatus { get; set; }
|
|
||||||
public string[]? Flags { get; set; }
|
|
||||||
public int RomType { get; set; }
|
|
||||||
public string? RomTypeMedia { get; set; }
|
|
||||||
public string? MediaLabel { get; set; }
|
|
||||||
public string? Path { get; set; }
|
|
||||||
public SourceType Source { get; set; }
|
|
||||||
|
|
||||||
public enum SourceType
|
public class GameRomItem : HasheousClient.Models.LookupResponseModel.RomItem
|
||||||
{
|
{
|
||||||
None = 0,
|
public long PlatformId { get; set; }
|
||||||
TOSEC = 1
|
public string Platform { get; set; }
|
||||||
}
|
public Models.PlatformMapping.PlatformMapItem.WebEmulatorItem? Emulator { get; set; }
|
||||||
|
public long GameId { get; set; }
|
||||||
|
public string? Path { get; set; }
|
||||||
|
public string? SignatureSourceGameTitle { get; set;}
|
||||||
|
public bool HasSaveStates { get; set; } = false;
|
||||||
|
public GameLibrary.LibraryItem Library { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,46 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using MySql.Data.MySqlClient;
|
using gaseous_server.Classes;
|
||||||
using gaseous_romsignatureobject;
|
using gaseous_signature_parser.models.RomSignatureObject;
|
||||||
using gaseous_signature_parser.parsers;
|
using System.Data;
|
||||||
using gaseous_tools;
|
|
||||||
using MySqlX.XDevAPI;
|
|
||||||
|
|
||||||
namespace gaseous_server.SignatureIngestors.TOSEC
|
namespace gaseous_server.SignatureIngestors.XML
|
||||||
{
|
{
|
||||||
public class TOSECIngestor
|
public class XMLIngestor : QueueItemStatus
|
||||||
{
|
{
|
||||||
public void Import(string SearchPath)
|
public void Import(string SearchPath, gaseous_signature_parser.parser.SignatureParser XMLType)
|
||||||
{
|
{
|
||||||
// connect to database
|
// connect to database
|
||||||
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
|
||||||
// process provided files
|
// process provided files
|
||||||
Logging.Log(Logging.LogType.Information, "Signature Ingestor - TOSEC", "Importing from " + SearchPath);
|
Logging.Log(Logging.LogType.Information, "Signature Ingestor - XML", "Importing from " + SearchPath);
|
||||||
if (Directory.Exists(Config.LibraryConfiguration.LibrarySignatureImportDirectory_TOSEC))
|
if (!Directory.Exists(SearchPath))
|
||||||
{
|
{
|
||||||
string[] tosecPathContents = Directory.GetFiles(SearchPath);
|
Directory.CreateDirectory(SearchPath);
|
||||||
Array.Sort(tosecPathContents);
|
}
|
||||||
|
|
||||||
string sql = "";
|
string[] PathContents = Directory.GetFiles(SearchPath);
|
||||||
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
Array.Sort(PathContents);
|
||||||
System.Data.DataTable sigDB;
|
|
||||||
|
|
||||||
for (UInt16 i = 0; i < tosecPathContents.Length; ++i)
|
string sql = "";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
System.Data.DataTable sigDB;
|
||||||
|
|
||||||
|
for (UInt16 i = 0; i < PathContents.Length; ++i)
|
||||||
|
{
|
||||||
|
string XMLFile = PathContents[i];
|
||||||
|
|
||||||
|
SetStatus(i + 1, PathContents.Length, "Processing signature file: " + XMLFile);
|
||||||
|
|
||||||
|
if (Common.SkippableFiles.Contains(Path.GetFileName(XMLFile), StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
string tosecXMLFile = tosecPathContents[i];
|
Logging.Log(Logging.LogType.Information, "Signature Ingestor - XML", "Skipping file: " + XMLFile);
|
||||||
|
}
|
||||||
// check tosec file md5
|
else
|
||||||
Common.hashObject hashObject = new Common.hashObject(tosecXMLFile);
|
{
|
||||||
|
// check xml file md5
|
||||||
|
Common.hashObject hashObject = new Common.hashObject(XMLFile);
|
||||||
sql = "SELECT * FROM Signatures_Sources WHERE SourceMD5=@sourcemd5";
|
sql = "SELECT * FROM Signatures_Sources WHERE SourceMD5=@sourcemd5";
|
||||||
dbDict = new Dictionary<string, object>();
|
dbDict = new Dictionary<string, object>();
|
||||||
dbDict.Add("sourcemd5", hashObject.md5hash);
|
dbDict.Add("sourcemd5", hashObject.md5hash);
|
||||||
@@ -41,31 +50,40 @@ namespace gaseous_server.SignatureIngestors.TOSEC
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logging.Log(Logging.LogType.Information, "Signature Ingestor - TOSEC", "Importing file: " + tosecXMLFile);
|
Logging.Log(Logging.LogType.Information, "Signature Ingestor - XML", "Importing file: " + XMLFile);
|
||||||
|
|
||||||
// start parsing file
|
// start parsing file
|
||||||
TosecParser tosecParser = new TosecParser();
|
gaseous_signature_parser.parser Parser = new gaseous_signature_parser.parser();
|
||||||
RomSignatureObject tosecObject = tosecParser.Parse(tosecXMLFile);
|
RomSignatureObject Object = Parser.ParseSignatureDAT(XMLFile, XMLType);
|
||||||
|
|
||||||
// store in database
|
// store in database
|
||||||
|
string[] flipNameAndDescription = {
|
||||||
|
"MAMEArcade",
|
||||||
|
"MAMEMess"
|
||||||
|
};
|
||||||
|
|
||||||
// store source object
|
// store source object
|
||||||
bool processGames = false;
|
bool processGames = false;
|
||||||
if (tosecObject.SourceMd5 != null)
|
if (Object.SourceMd5 != null)
|
||||||
{
|
{
|
||||||
sql = "SELECT * FROM Signatures_Sources WHERE SourceMD5=@sourcemd5";
|
sql = "SELECT * FROM Signatures_Sources WHERE SourceMD5=@sourcemd5";
|
||||||
dbDict = new Dictionary<string, object>();
|
dbDict = new Dictionary<string, object>();
|
||||||
dbDict.Add("name", Common.ReturnValueIfNull(tosecObject.Name, ""));
|
string sourceUriStr = "";
|
||||||
dbDict.Add("description", Common.ReturnValueIfNull(tosecObject.Description, ""));
|
if (Object.Url != null)
|
||||||
dbDict.Add("category", Common.ReturnValueIfNull(tosecObject.Category, ""));
|
{
|
||||||
dbDict.Add("version", Common.ReturnValueIfNull(tosecObject.Version, ""));
|
sourceUriStr = Object.Url.ToString();
|
||||||
dbDict.Add("author", Common.ReturnValueIfNull(tosecObject.Author, ""));
|
}
|
||||||
dbDict.Add("email", Common.ReturnValueIfNull(tosecObject.Email, ""));
|
dbDict.Add("name", Common.ReturnValueIfNull(Object.Name, ""));
|
||||||
dbDict.Add("homepage", Common.ReturnValueIfNull(tosecObject.Homepage, ""));
|
dbDict.Add("description", Common.ReturnValueIfNull(Object.Description, ""));
|
||||||
dbDict.Add("uri", Common.ReturnValueIfNull(tosecObject.Url, ""));
|
dbDict.Add("category", Common.ReturnValueIfNull(Object.Category, ""));
|
||||||
dbDict.Add("sourcetype", Common.ReturnValueIfNull(tosecObject.SourceType, ""));
|
dbDict.Add("version", Common.ReturnValueIfNull(Object.Version, ""));
|
||||||
dbDict.Add("sourcemd5", tosecObject.SourceMd5);
|
dbDict.Add("author", Common.ReturnValueIfNull(Object.Author, ""));
|
||||||
dbDict.Add("sourcesha1", tosecObject.SourceSHA1);
|
dbDict.Add("email", Common.ReturnValueIfNull(Object.Email, ""));
|
||||||
|
dbDict.Add("homepage", Common.ReturnValueIfNull(Object.Homepage, ""));
|
||||||
|
dbDict.Add("uri", sourceUriStr);
|
||||||
|
dbDict.Add("sourcetype", Common.ReturnValueIfNull(Object.SourceType, ""));
|
||||||
|
dbDict.Add("sourcemd5", Object.SourceMd5);
|
||||||
|
dbDict.Add("sourcesha1", Object.SourceSHA1);
|
||||||
|
|
||||||
sigDB = db.ExecuteCMD(sql, dbDict);
|
sigDB = db.ExecuteCMD(sql, dbDict);
|
||||||
if (sigDB.Rows.Count == 0)
|
if (sigDB.Rows.Count == 0)
|
||||||
@@ -80,14 +98,22 @@ namespace gaseous_server.SignatureIngestors.TOSEC
|
|||||||
|
|
||||||
if (processGames == true)
|
if (processGames == true)
|
||||||
{
|
{
|
||||||
for (int x = 0; x < tosecObject.Games.Count; ++x)
|
for (int x = 0; x < Object.Games.Count; ++x)
|
||||||
{
|
{
|
||||||
RomSignatureObject.Game gameObject = tosecObject.Games[x];
|
RomSignatureObject.Game gameObject = Object.Games[x];
|
||||||
|
|
||||||
// set up game dictionary
|
// set up game dictionary
|
||||||
dbDict = new Dictionary<string, object>();
|
dbDict = new Dictionary<string, object>();
|
||||||
dbDict.Add("name", Common.ReturnValueIfNull(gameObject.Name, ""));
|
if (flipNameAndDescription.Contains(Object.SourceType))
|
||||||
dbDict.Add("description", Common.ReturnValueIfNull(gameObject.Description, ""));
|
{
|
||||||
|
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("year", Common.ReturnValueIfNull(gameObject.Year, ""));
|
||||||
dbDict.Add("publisher", Common.ReturnValueIfNull(gameObject.Publisher, ""));
|
dbDict.Add("publisher", Common.ReturnValueIfNull(gameObject.Publisher, ""));
|
||||||
dbDict.Add("demo", (int)gameObject.Demo);
|
dbDict.Add("demo", (int)gameObject.Demo);
|
||||||
@@ -165,7 +191,7 @@ namespace gaseous_server.SignatureIngestors.TOSEC
|
|||||||
// store rom
|
// store rom
|
||||||
foreach (RomSignatureObject.Game.Rom romObject in gameObject.Roms)
|
foreach (RomSignatureObject.Game.Rom romObject in gameObject.Roms)
|
||||||
{
|
{
|
||||||
if (romObject.Md5 != null)
|
if (romObject.Md5 != null || romObject.Sha1 != null)
|
||||||
{
|
{
|
||||||
int romId = 0;
|
int romId = 0;
|
||||||
sql = "SELECT * FROM Signatures_Roms WHERE GameId=@gameid AND MD5=@md5";
|
sql = "SELECT * FROM Signatures_Roms WHERE GameId=@gameid AND MD5=@md5";
|
||||||
@@ -173,36 +199,37 @@ namespace gaseous_server.SignatureIngestors.TOSEC
|
|||||||
dbDict.Add("gameid", gameId);
|
dbDict.Add("gameid", gameId);
|
||||||
dbDict.Add("name", Common.ReturnValueIfNull(romObject.Name, ""));
|
dbDict.Add("name", Common.ReturnValueIfNull(romObject.Name, ""));
|
||||||
dbDict.Add("size", Common.ReturnValueIfNull(romObject.Size, ""));
|
dbDict.Add("size", Common.ReturnValueIfNull(romObject.Size, ""));
|
||||||
dbDict.Add("crc", Common.ReturnValueIfNull(romObject.Crc, ""));
|
dbDict.Add("crc", Common.ReturnValueIfNull(romObject.Crc, "").ToString().ToLower());
|
||||||
dbDict.Add("md5", romObject.Md5);
|
dbDict.Add("md5", Common.ReturnValueIfNull(romObject.Md5, "").ToString().ToLower());
|
||||||
dbDict.Add("sha1", Common.ReturnValueIfNull(romObject.Sha1, ""));
|
dbDict.Add("sha1", Common.ReturnValueIfNull(romObject.Sha1, "").ToString().ToLower());
|
||||||
dbDict.Add("developmentstatus", Common.ReturnValueIfNull(romObject.DevelopmentStatus, ""));
|
dbDict.Add("developmentstatus", Common.ReturnValueIfNull(romObject.DevelopmentStatus, ""));
|
||||||
|
|
||||||
if (romObject.flags != null)
|
if (romObject.Attributes != null)
|
||||||
{
|
{
|
||||||
if (romObject.flags.Count > 0)
|
if (romObject.Attributes.Count > 0)
|
||||||
{
|
{
|
||||||
dbDict.Add("flags", Newtonsoft.Json.JsonConvert.SerializeObject(romObject.flags));
|
dbDict.Add("attributes", Newtonsoft.Json.JsonConvert.SerializeObject(romObject.Attributes));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dbDict.Add("flags", "[ ]");
|
dbDict.Add("attributes", "[ ]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dbDict.Add("flags", "[ ]");
|
dbDict.Add("attributes", "[ ]");
|
||||||
}
|
}
|
||||||
dbDict.Add("romtype", (int)romObject.RomType);
|
dbDict.Add("romtype", (int)romObject.RomType);
|
||||||
dbDict.Add("romtypemedia", Common.ReturnValueIfNull(romObject.RomTypeMedia, ""));
|
dbDict.Add("romtypemedia", Common.ReturnValueIfNull(romObject.RomTypeMedia, ""));
|
||||||
dbDict.Add("medialabel", Common.ReturnValueIfNull(romObject.MediaLabel, ""));
|
dbDict.Add("medialabel", Common.ReturnValueIfNull(romObject.MediaLabel, ""));
|
||||||
dbDict.Add("metadatasource", Classes.Roms.GameRomItem.SourceType.TOSEC);
|
dbDict.Add("metadatasource", romObject.SignatureSource);
|
||||||
|
dbDict.Add("ingestorversion", 2);
|
||||||
|
|
||||||
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_Roms (GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Flags, RomType, RomTypeMedia, MediaLabel, MetadataSource) VALUES (@gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @flags, @romtype, @romtypemedia, @medialabel, @metadatasource); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
|
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);
|
||||||
|
|
||||||
|
|
||||||
@@ -220,15 +247,16 @@ namespace gaseous_server.SignatureIngestors.TOSEC
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.Log(Logging.LogType.Warning, "Signature Ingestor - TOSEC", "Invalid import file: " + tosecXMLFile, ex);
|
Logging.Log(Logging.LogType.Warning, "Signature Ingestor - XML", "Invalid import file: " + XMLFile, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logging.Log(Logging.LogType.Debug, "Signature Ingestor - TOSEC", "Rejecting already imported file: " + tosecXMLFile);
|
Logging.Log(Logging.LogType.Debug, "Signature Ingestor - XML", "Rejecting already imported file: " + XMLFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ClearStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
81
gaseous-server/Classes/SignatureManagement.cs
Normal file
81
gaseous-server/Classes/SignatureManagement.cs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
using System.Data;
|
||||||
|
using gaseous_signature_parser.models.RomSignatureObject;
|
||||||
|
|
||||||
|
namespace gaseous_server.Classes
|
||||||
|
{
|
||||||
|
public class SignatureManagement
|
||||||
|
{
|
||||||
|
public List<gaseous_server.Models.Signatures_Games> GetSignature(string md5 = "", string sha1 = "")
|
||||||
|
{
|
||||||
|
if (md5.Length > 0)
|
||||||
|
{
|
||||||
|
return _GetSignature("Signatures_Roms.md5 = @searchstring", md5);
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
return _GetSignature("Signatures_Roms.sha1 = @searchstring", sha1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<gaseous_server.Models.Signatures_Games> GetByTosecName(string TosecName = "")
|
||||||
|
{
|
||||||
|
if (TosecName.Length > 0)
|
||||||
|
{
|
||||||
|
return _GetSignature("Signatures_Roms.name = @searchstring", TosecName);
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<gaseous_server.Models.Signatures_Games> _GetSignature(string sqlWhere, string searchString)
|
||||||
|
{
|
||||||
|
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "SELECT view_Signatures_Games.*, Signatures_Roms.Id AS romid, Signatures_Roms.Name AS romname, Signatures_Roms.Size, Signatures_Roms.CRC, Signatures_Roms.MD5, Signatures_Roms.SHA1, Signatures_Roms.DevelopmentStatus, Signatures_Roms.Attributes, Signatures_Roms.RomType, Signatures_Roms.RomTypeMedia, Signatures_Roms.MediaLabel, Signatures_Roms.MetadataSource FROM Signatures_Roms INNER JOIN view_Signatures_Games ON Signatures_Roms.GameId = view_Signatures_Games.Id WHERE " + sqlWhere;
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("searchString", searchString);
|
||||||
|
|
||||||
|
DataTable sigDb = db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
List<gaseous_server.Models.Signatures_Games> GamesList = new List<gaseous_server.Models.Signatures_Games>();
|
||||||
|
|
||||||
|
foreach (DataRow sigDbRow in sigDb.Rows)
|
||||||
|
{
|
||||||
|
gaseous_server.Models.Signatures_Games gameItem = new gaseous_server.Models.Signatures_Games
|
||||||
|
{
|
||||||
|
Game = new gaseous_server.Models.Signatures_Games.GameItem
|
||||||
|
{
|
||||||
|
Id = (Int32)sigDbRow["Id"],
|
||||||
|
Name = (string)sigDbRow["Name"],
|
||||||
|
Description = (string)sigDbRow["Description"],
|
||||||
|
Year = (string)sigDbRow["Year"],
|
||||||
|
Publisher = (string)sigDbRow["Publisher"],
|
||||||
|
Demo = (gaseous_server.Models.Signatures_Games.GameItem.DemoTypes)(int)sigDbRow["Demo"],
|
||||||
|
System = (string)sigDbRow["Platform"],
|
||||||
|
SystemVariant = (string)sigDbRow["SystemVariant"],
|
||||||
|
Video = (string)sigDbRow["Video"],
|
||||||
|
Country = (string)sigDbRow["Country"],
|
||||||
|
Language = (string)sigDbRow["Language"],
|
||||||
|
Copyright = (string)sigDbRow["Copyright"]
|
||||||
|
},
|
||||||
|
Rom = new gaseous_server.Models.Signatures_Games.RomItem
|
||||||
|
{
|
||||||
|
Id = (Int32)sigDbRow["romid"],
|
||||||
|
Name = (string)sigDbRow["romname"],
|
||||||
|
Size = (Int64)sigDbRow["Size"],
|
||||||
|
Crc = (string)sigDbRow["CRC"],
|
||||||
|
Md5 = ((string)sigDbRow["MD5"]).ToLower(),
|
||||||
|
Sha1 = ((string)sigDbRow["SHA1"]).ToLower(),
|
||||||
|
DevelopmentStatus = (string)sigDbRow["DevelopmentStatus"],
|
||||||
|
Attributes = Newtonsoft.Json.JsonConvert.DeserializeObject<List<KeyValuePair<string, object>>>((string)Common.ReturnValueIfNull(sigDbRow["Attributes"], "[]")),
|
||||||
|
RomType = (gaseous_server.Models.Signatures_Games.RomItem.RomTypes)(int)sigDbRow["RomType"],
|
||||||
|
RomTypeMedia = (string)sigDbRow["RomTypeMedia"],
|
||||||
|
MediaLabel = (string)sigDbRow["MediaLabel"],
|
||||||
|
SignatureSource = (gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType)(Int32)sigDbRow["MetadataSource"]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
GamesList.Add(gameItem);
|
||||||
|
}
|
||||||
|
return GamesList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB.Models;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace gaseous_server.Controllers
|
|
||||||
{
|
|
||||||
[Route("api/v1/[controller]")]
|
|
||||||
[ApiController]
|
|
||||||
public class FilterController : ControllerBase
|
|
||||||
{
|
|
||||||
[HttpGet]
|
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
||||||
//[ResponseCache(CacheProfileName = "5Minute")]
|
|
||||||
public Dictionary<string, object> Filter()
|
|
||||||
{
|
|
||||||
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
|
||||||
|
|
||||||
Dictionary<string, object> FilterSet = new Dictionary<string, object>();
|
|
||||||
|
|
||||||
// platforms
|
|
||||||
List<Platform> platforms = new List<Platform>();
|
|
||||||
string sql = "SELECT Platform.Id, Platform.Abbreviation, Platform.AlternativeName, Platform.`Name`, Platform.PlatformLogo, (SELECT COUNT(Games_Roms.Id) AS RomCount FROM Games_Roms WHERE Games_Roms.PlatformId = Platform.Id) AS RomCount FROM Platform HAVING RomCount > 0 ORDER BY `Name`";
|
|
||||||
DataTable dbResponse = db.ExecuteCMD(sql);
|
|
||||||
|
|
||||||
foreach (DataRow dr in dbResponse.Rows)
|
|
||||||
{
|
|
||||||
platforms.Add(Classes.Metadata.Platforms.GetPlatform((long)dr["id"]));
|
|
||||||
}
|
|
||||||
FilterSet.Add("platforms", platforms);
|
|
||||||
|
|
||||||
// genres
|
|
||||||
List<Genre> genres = new List<Genre>();
|
|
||||||
sql = "SELECT DISTINCT t1.Id, t1.`Name` FROM Genre AS t1 JOIN (SELECT * FROM Game WHERE (SELECT COUNT(Id) FROM Games_Roms WHERE GameId = Game.Id) > 0) AS t2 ON JSON_CONTAINS(t2.Genres, CAST(t1.Id AS char), '$') ORDER BY t1.`Name`";
|
|
||||||
dbResponse = db.ExecuteCMD(sql);
|
|
||||||
|
|
||||||
foreach (DataRow dr in dbResponse.Rows)
|
|
||||||
{
|
|
||||||
genres.Add(Classes.Metadata.Genres.GetGenres((long)dr["id"]));
|
|
||||||
}
|
|
||||||
FilterSet.Add("genres", genres);
|
|
||||||
|
|
||||||
return FilterSet;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using gaseous_tools;
|
|
||||||
using IGDB;
|
|
||||||
using IGDB.Models;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using static gaseous_server.Classes.Metadata.Games;
|
|
||||||
using static gaseous_tools.Config.ConfigFile;
|
|
||||||
|
|
||||||
namespace gaseous_server.Controllers
|
|
||||||
{
|
|
||||||
[ApiController]
|
|
||||||
[Route("api/v1/[controller]")]
|
|
||||||
public class SearchController : Controller
|
|
||||||
{
|
|
||||||
private static IGDBClient igdb = new IGDBClient(
|
|
||||||
// Found in Twitch Developer portal for your app
|
|
||||||
Config.IGDB.ClientId,
|
|
||||||
Config.IGDB.Secret
|
|
||||||
);
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[Route("Platform")]
|
|
||||||
[ProducesResponseType(typeof(List<Platform>), StatusCodes.Status200OK)]
|
|
||||||
public async Task<ActionResult> SearchPlatform(string SearchString)
|
|
||||||
{
|
|
||||||
List<Platform> RetVal = await _SearchForPlatform(SearchString);
|
|
||||||
return Ok(RetVal);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<List<Platform>> _SearchForPlatform(string SearchString)
|
|
||||||
{
|
|
||||||
string searchBody = "";
|
|
||||||
searchBody += "fields abbreviation,alternative_name,category,checksum,created_at,generation,name,platform_family,platform_logo,slug,summary,updated_at,url,versions,websites; ";
|
|
||||||
searchBody += "where name ~ *\"" + SearchString + "\"*;";
|
|
||||||
|
|
||||||
// get Platform metadata
|
|
||||||
var results = await igdb.QueryAsync<Platform>(IGDBClient.Endpoints.Platforms, query: searchBody);
|
|
||||||
|
|
||||||
return results.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[Route("Game")]
|
|
||||||
[ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)]
|
|
||||||
public async Task<ActionResult> SearchGame(long PlatformId, string SearchString)
|
|
||||||
{
|
|
||||||
List<Game> RetVal = await _SearchForGame(PlatformId, SearchString);
|
|
||||||
return Ok(RetVal);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<List<Game>> _SearchForGame(long PlatformId, string SearchString)
|
|
||||||
{
|
|
||||||
string searchBody = "";
|
|
||||||
searchBody += "fields cover.*,first_release_date,name,platforms,slug; ";
|
|
||||||
searchBody += "search \"" + SearchString + "\";";
|
|
||||||
searchBody += "where platforms = (" + PlatformId + ");";
|
|
||||||
|
|
||||||
// get Platform metadata
|
|
||||||
var results = await igdb.QueryAsync<Game>(IGDBClient.Endpoints.Games, query: searchBody);
|
|
||||||
|
|
||||||
return results.ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using gaseous_tools;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
|
|
||||||
|
|
||||||
namespace gaseous_server.Controllers
|
|
||||||
{
|
|
||||||
[ApiController]
|
|
||||||
[Route("api/v1/[controller]/[action]")]
|
|
||||||
public class SignaturesController : ControllerBase
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Get the current signature counts from the database
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Number of sources, publishers, games, and rom signatures in the database</returns>
|
|
||||||
[HttpGet]
|
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
||||||
public Models.Signatures_Status Status()
|
|
||||||
{
|
|
||||||
return new Models.Signatures_Status();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
||||||
public List<Models.Signatures_Games> GetSignature(string md5 = "", string sha1 = "")
|
|
||||||
{
|
|
||||||
if (md5.Length > 0)
|
|
||||||
{
|
|
||||||
return _GetSignature("Signatures_Roms.md5 = @searchstring", md5);
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
return _GetSignature("Signatures_Roms.sha1 = @searchstring", sha1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
||||||
public List<Models.Signatures_Games> GetByTosecName(string TosecName = "")
|
|
||||||
{
|
|
||||||
if (TosecName.Length > 0)
|
|
||||||
{
|
|
||||||
return _GetSignature("Signatures_Roms.name = @searchstring", TosecName);
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Models.Signatures_Games> _GetSignature(string sqlWhere, string searchString)
|
|
||||||
{
|
|
||||||
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
|
||||||
string sql = "SELECT view_Signatures_Games.*, Signatures_Roms.Id AS romid, Signatures_Roms.Name AS romname, Signatures_Roms.Size, Signatures_Roms.CRC, Signatures_Roms.MD5, Signatures_Roms.SHA1, Signatures_Roms.DevelopmentStatus, Signatures_Roms.Flags, Signatures_Roms.RomType, Signatures_Roms.RomTypeMedia, Signatures_Roms.MediaLabel, Signatures_Roms.MetadataSource FROM Signatures_Roms INNER JOIN view_Signatures_Games ON Signatures_Roms.GameId = view_Signatures_Games.Id WHERE " + sqlWhere;
|
|
||||||
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
|
||||||
dbDict.Add("searchString", searchString);
|
|
||||||
|
|
||||||
DataTable sigDb = db.ExecuteCMD(sql, dbDict);
|
|
||||||
|
|
||||||
List<Models.Signatures_Games> GamesList = new List<Models.Signatures_Games>();
|
|
||||||
|
|
||||||
foreach (DataRow sigDbRow in sigDb.Rows)
|
|
||||||
{
|
|
||||||
Models.Signatures_Games gameItem = new Models.Signatures_Games
|
|
||||||
{
|
|
||||||
Game = new Models.Signatures_Games.GameItem
|
|
||||||
{
|
|
||||||
Id = (Int32)sigDbRow["Id"],
|
|
||||||
Name = (string)sigDbRow["Name"],
|
|
||||||
Description = (string)sigDbRow["Description"],
|
|
||||||
Year = (string)sigDbRow["Year"],
|
|
||||||
Publisher = (string)sigDbRow["Publisher"],
|
|
||||||
Demo = (Models.Signatures_Games.GameItem.DemoTypes)(int)sigDbRow["Demo"],
|
|
||||||
System = (string)sigDbRow["Platform"],
|
|
||||||
SystemVariant = (string)sigDbRow["SystemVariant"],
|
|
||||||
Video = (string)sigDbRow["Video"],
|
|
||||||
Country = (string)sigDbRow["Country"],
|
|
||||||
Language = (string)sigDbRow["Language"],
|
|
||||||
Copyright = (string)sigDbRow["Copyright"]
|
|
||||||
},
|
|
||||||
Rom = new Models.Signatures_Games.RomItem
|
|
||||||
{
|
|
||||||
Id = (Int32)sigDbRow["romid"],
|
|
||||||
Name = (string)sigDbRow["romname"],
|
|
||||||
Size = (Int64)sigDbRow["Size"],
|
|
||||||
Crc = (string)sigDbRow["CRC"],
|
|
||||||
Md5 = (string)sigDbRow["MD5"],
|
|
||||||
Sha1 = (string)sigDbRow["SHA1"],
|
|
||||||
DevelopmentStatus = (string)sigDbRow["DevelopmentStatus"],
|
|
||||||
flags = Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>>((string)sigDbRow["Flags"]),
|
|
||||||
RomType = (Models.Signatures_Games.RomItem.RomTypes)(int)sigDbRow["RomType"],
|
|
||||||
RomTypeMedia = (string)sigDbRow["RomTypeMedia"],
|
|
||||||
MediaLabel = (string)sigDbRow["MediaLabel"],
|
|
||||||
SignatureSource = (Models.Signatures_Games.RomItem.SignatureSourceType)(Int32)sigDbRow["MetadataSource"]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
GamesList.Add(gameItem);
|
|
||||||
}
|
|
||||||
return GamesList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using gaseous_tools;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace gaseous_server.Controllers
|
|
||||||
{
|
|
||||||
[ApiController]
|
|
||||||
[Route("api/v1/[controller]")]
|
|
||||||
public class SystemController : Controller
|
|
||||||
{
|
|
||||||
[HttpGet]
|
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
||||||
public Dictionary<string, object> GetSystemStatus()
|
|
||||||
{
|
|
||||||
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
|
||||||
|
|
||||||
Dictionary<string, object> ReturnValue = new Dictionary<string, object>();
|
|
||||||
|
|
||||||
// disk size
|
|
||||||
List<Dictionary<string, object>> Disks = new List<Dictionary<string, object>>();
|
|
||||||
//Disks.Add(GetDisk(gaseous_tools.Config.ConfigurationPath));
|
|
||||||
Disks.Add(GetDisk(gaseous_tools.Config.LibraryConfiguration.LibraryRootDirectory));
|
|
||||||
ReturnValue.Add("Paths", Disks);
|
|
||||||
|
|
||||||
// database size
|
|
||||||
string sql = "SELECT table_schema, SUM(data_length + index_length) FROM information_schema.tables WHERE table_schema = '" + Config.DatabaseConfiguration.DatabaseName + "'";
|
|
||||||
DataTable dbResponse = db.ExecuteCMD(sql);
|
|
||||||
ReturnValue.Add("DatabaseSize", dbResponse.Rows[0][1]);
|
|
||||||
|
|
||||||
return ReturnValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Dictionary<string, object> GetDisk(string Path)
|
|
||||||
{
|
|
||||||
Dictionary<string, object> DiskValues = new Dictionary<string, object>();
|
|
||||||
DiskValues.Add("LibraryPath", Path);
|
|
||||||
DiskValues.Add("SpaceUsed", gaseous_tools.Common.DirSize(new DirectoryInfo(Path)));
|
|
||||||
DiskValues.Add("SpaceAvailable", new DriveInfo(Path).AvailableFreeSpace);
|
|
||||||
DiskValues.Add("TotalSpace", new DriveInfo(Path).TotalSize);
|
|
||||||
|
|
||||||
return DiskValues;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
417
gaseous-server/Controllers/V1.0/AccountController.cs
Normal file
417
gaseous-server/Controllers/V1.0/AccountController.cs
Normal file
@@ -0,0 +1,417 @@
|
|||||||
|
using System.Data;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
using Authentication;
|
||||||
|
using gaseous_server.Classes;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Identity.UI.Services;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
|
||||||
|
namespace gaseous_server.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/v{version:apiVersion}/[controller]")]
|
||||||
|
[ApiVersion("1.0")]
|
||||||
|
[ApiVersion("1.1")]
|
||||||
|
[Authorize]
|
||||||
|
public class AccountController : Controller
|
||||||
|
{
|
||||||
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||||
|
private readonly IEmailSender _emailSender;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public AccountController(
|
||||||
|
UserManager<ApplicationUser> userManager,
|
||||||
|
SignInManager<ApplicationUser> signInManager,
|
||||||
|
IEmailSender emailSender,
|
||||||
|
ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
_userManager = userManager;
|
||||||
|
_signInManager = signInManager;
|
||||||
|
_emailSender = emailSender;
|
||||||
|
_logger = loggerFactory.CreateLogger<AccountController>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[AllowAnonymous]
|
||||||
|
[Route("Login")]
|
||||||
|
public async Task<IActionResult> Login(LoginViewModel model)
|
||||||
|
{
|
||||||
|
if (ModelState.IsValid)
|
||||||
|
{
|
||||||
|
// This doesn't count login failures towards account lockout
|
||||||
|
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
|
||||||
|
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
|
||||||
|
if (result.Succeeded)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Login", model.Email + " has logged in, from IP: " + HttpContext.Connection.RemoteIpAddress?.ToString());
|
||||||
|
return Ok(result.ToString());
|
||||||
|
}
|
||||||
|
// if (result.RequiresTwoFactor)
|
||||||
|
// {
|
||||||
|
// return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
|
||||||
|
// }
|
||||||
|
if (result.IsLockedOut)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Warning, "Login", model.Email + " was unable to login due to a locked account. Login attempt from IP: " + HttpContext.Connection.RemoteIpAddress?.ToString());
|
||||||
|
return Unauthorized();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Critical, "Login", "An unknown error occurred during login by " + model.Email + ". Login attempt from IP: " + HttpContext.Connection.RemoteIpAddress?.ToString());
|
||||||
|
return Unauthorized();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got this far, something failed, redisplay form
|
||||||
|
Logging.Log(Logging.LogType.Critical, "Login", "An unknown error occurred during login by " + model.Email + ". Login attempt from IP: " + HttpContext.Connection.RemoteIpAddress?.ToString());
|
||||||
|
return Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("LogOff")]
|
||||||
|
public async Task<IActionResult> LogOff()
|
||||||
|
{
|
||||||
|
var userName = User.FindFirstValue(ClaimTypes.Name);
|
||||||
|
await _signInManager.SignOutAsync();
|
||||||
|
if (userName != null)
|
||||||
|
{
|
||||||
|
Logging.Log(Logging.LogType.Information, "Login", userName + " has logged out");
|
||||||
|
}
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Route("Profile/Basic")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IActionResult> ProfileBasic()
|
||||||
|
{
|
||||||
|
ProfileBasicViewModel profile = new ProfileBasicViewModel();
|
||||||
|
profile.UserId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
|
ApplicationUser user = await _userManager.FindByIdAsync(profile.UserId);
|
||||||
|
profile.UserName = _userManager.GetUserName(HttpContext.User);
|
||||||
|
profile.EmailAddress = await _userManager.GetEmailAsync(user);
|
||||||
|
profile.Roles = new List<string>(await _userManager.GetRolesAsync(user));
|
||||||
|
profile.SecurityProfile = user.SecurityProfile;
|
||||||
|
profile.UserPreferences = user.UserPreferences;
|
||||||
|
profile.Roles.Sort();
|
||||||
|
|
||||||
|
return Ok(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Route("Profile/Basic/profile.js")]
|
||||||
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<IActionResult> ProfileBasicFile()
|
||||||
|
{
|
||||||
|
var user = await _userManager.GetUserAsync(User);
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
ProfileBasicViewModel profile = new ProfileBasicViewModel();
|
||||||
|
profile.UserId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
|
profile.UserName = _userManager.GetUserName(HttpContext.User);
|
||||||
|
profile.EmailAddress = await _userManager.GetEmailAsync(user);
|
||||||
|
profile.Roles = new List<string>(await _userManager.GetRolesAsync(user));
|
||||||
|
profile.SecurityProfile = user.SecurityProfile;
|
||||||
|
profile.UserPreferences = user.UserPreferences;
|
||||||
|
profile.Roles.Sort();
|
||||||
|
|
||||||
|
string profileString = "var userProfile = " + Newtonsoft.Json.JsonConvert.SerializeObject(profile, Newtonsoft.Json.Formatting.Indented) + ";";
|
||||||
|
|
||||||
|
byte[] bytes = Encoding.UTF8.GetBytes(profileString);
|
||||||
|
return File(bytes, "text/javascript");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string profileString = "var userProfile = null;";
|
||||||
|
|
||||||
|
byte[] bytes = Encoding.UTF8.GetBytes(profileString);
|
||||||
|
return File(bytes, "text/javascript");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("ChangePassword")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model)
|
||||||
|
{
|
||||||
|
if (ModelState.IsValid)
|
||||||
|
{
|
||||||
|
var user = await _userManager.GetUserAsync(User);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return RedirectToAction("Login");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangePasswordAsync changes the user password
|
||||||
|
var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
|
||||||
|
|
||||||
|
// The new password did not meet the complexity rules or
|
||||||
|
// the current password is incorrect. Add these errors to
|
||||||
|
// the ModelState and rerender ChangePassword view
|
||||||
|
if (!result.Succeeded)
|
||||||
|
{
|
||||||
|
foreach (var error in result.Errors)
|
||||||
|
{
|
||||||
|
ModelState.AddModelError(string.Empty, error.Description);
|
||||||
|
}
|
||||||
|
return Unauthorized(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upon successfully changing the password refresh sign-in cookie
|
||||||
|
await _signInManager.RefreshSignInAsync(user);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Route("Users")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> GetAllUsers()
|
||||||
|
{
|
||||||
|
List<UserViewModel> users = new List<UserViewModel>();
|
||||||
|
|
||||||
|
foreach (ApplicationUser rawUser in _userManager.Users)
|
||||||
|
{
|
||||||
|
UserViewModel user = new UserViewModel();
|
||||||
|
user.Id = rawUser.Id;
|
||||||
|
user.EmailAddress = rawUser.NormalizedEmail.ToLower();
|
||||||
|
user.LockoutEnabled = rawUser.LockoutEnabled;
|
||||||
|
user.LockoutEnd = rawUser.LockoutEnd;
|
||||||
|
user.SecurityProfile = rawUser.SecurityProfile;
|
||||||
|
|
||||||
|
// get roles
|
||||||
|
ApplicationUser? aUser = await _userManager.FindByIdAsync(rawUser.Id);
|
||||||
|
if (aUser != null)
|
||||||
|
{
|
||||||
|
IList<string> aUserRoles = await _userManager.GetRolesAsync(aUser);
|
||||||
|
user.Roles = aUserRoles.ToList();
|
||||||
|
|
||||||
|
user.Roles.Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
users.Add(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(users);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("Users")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> NewUser(RegisterViewModel model)
|
||||||
|
{
|
||||||
|
if (ModelState.IsValid)
|
||||||
|
{
|
||||||
|
ApplicationUser user = new ApplicationUser
|
||||||
|
{
|
||||||
|
UserName = model.UserName,
|
||||||
|
NormalizedUserName = model.UserName.ToUpper(),
|
||||||
|
Email = model.Email,
|
||||||
|
NormalizedEmail = model.Email.ToUpper()
|
||||||
|
};
|
||||||
|
var result = await _userManager.CreateAsync(user, model.Password);
|
||||||
|
if (result.Succeeded)
|
||||||
|
{
|
||||||
|
// add new users to the player role
|
||||||
|
await _userManager.AddToRoleAsync(user, "Player");
|
||||||
|
|
||||||
|
Logging.Log(Logging.LogType.Information, "User Management", User.FindFirstValue(ClaimTypes.Name) + " created user " + model.Email + " with password.");
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Route("Users/{UserId}")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> GetUser(string UserId)
|
||||||
|
{
|
||||||
|
ApplicationUser? rawUser = await _userManager.FindByIdAsync(UserId);
|
||||||
|
|
||||||
|
if (rawUser != null)
|
||||||
|
{
|
||||||
|
UserViewModel user = new UserViewModel();
|
||||||
|
user.Id = rawUser.Id;
|
||||||
|
user.EmailAddress = rawUser.NormalizedEmail.ToLower();
|
||||||
|
user.LockoutEnabled = rawUser.LockoutEnabled;
|
||||||
|
user.LockoutEnd = rawUser.LockoutEnd;
|
||||||
|
user.SecurityProfile = rawUser.SecurityProfile;
|
||||||
|
|
||||||
|
// get roles
|
||||||
|
IList<string> aUserRoles = await _userManager.GetRolesAsync(rawUser);
|
||||||
|
user.Roles = aUserRoles.ToList();
|
||||||
|
|
||||||
|
user.Roles.Sort();
|
||||||
|
|
||||||
|
return Ok(user);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete]
|
||||||
|
[Route("Users/{UserId}")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> DeleteUser(string UserId)
|
||||||
|
{
|
||||||
|
// get user
|
||||||
|
ApplicationUser? user = await _userManager.FindByIdAsync(UserId);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _userManager.DeleteAsync(user);
|
||||||
|
Logging.Log(Logging.LogType.Information, "User Management", User.FindFirstValue(ClaimTypes.Name) + " deleted user " + user.Email);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("Users/{UserId}/Roles")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> SetUserRoles(string UserId, string RoleName)
|
||||||
|
{
|
||||||
|
ApplicationUser? user = await _userManager.FindByIdAsync(UserId);
|
||||||
|
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
// get roles
|
||||||
|
List<string> userRoles = (await _userManager.GetRolesAsync(user)).ToList();
|
||||||
|
|
||||||
|
// delete all roles
|
||||||
|
foreach (string role in userRoles)
|
||||||
|
{
|
||||||
|
if ((new string[] { "Admin", "Gamer", "Player" }).Contains(role) )
|
||||||
|
{
|
||||||
|
await _userManager.RemoveFromRoleAsync(user, role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add only requested roles
|
||||||
|
switch (RoleName)
|
||||||
|
{
|
||||||
|
case "Admin":
|
||||||
|
await _userManager.AddToRoleAsync(user, "Admin");
|
||||||
|
await _userManager.AddToRoleAsync(user, "Gamer");
|
||||||
|
await _userManager.AddToRoleAsync(user, "Player");
|
||||||
|
break;
|
||||||
|
case "Gamer":
|
||||||
|
await _userManager.AddToRoleAsync(user, "Gamer");
|
||||||
|
await _userManager.AddToRoleAsync(user, "Player");
|
||||||
|
break;
|
||||||
|
case "Player":
|
||||||
|
await _userManager.AddToRoleAsync(user, "Player");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
await _userManager.AddToRoleAsync(user, RoleName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("Users/{UserId}/SecurityProfile")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> SetUserSecurityProfile(string UserId, SecurityProfileViewModel securityProfile)
|
||||||
|
{
|
||||||
|
if (ModelState.IsValid)
|
||||||
|
{
|
||||||
|
ApplicationUser? user = await _userManager.FindByIdAsync(UserId);
|
||||||
|
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
user.SecurityProfile = securityProfile;
|
||||||
|
await _userManager.UpdateAsync(user);
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("Users/{UserId}/Password")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> ResetPassword(string UserId, SetPasswordViewModel model)
|
||||||
|
{
|
||||||
|
if (ModelState.IsValid)
|
||||||
|
{
|
||||||
|
// we can reset the users password
|
||||||
|
ApplicationUser? user = await _userManager.FindByIdAsync(UserId);
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
string resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);
|
||||||
|
IdentityResult passwordChangeResult = await _userManager.ResetPasswordAsync(user, resetToken, model.NewPassword);
|
||||||
|
if (passwordChangeResult.Succeeded == true)
|
||||||
|
{
|
||||||
|
return Ok(passwordChangeResult);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Ok(passwordChangeResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("Preferences")]
|
||||||
|
public async Task<IActionResult> SetPreferences(List<UserPreferenceViewModel> model)
|
||||||
|
{
|
||||||
|
ApplicationUser? user = await _userManager.GetUserAsync(User);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return Unauthorized();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
user.UserPreferences = model;
|
||||||
|
await _userManager.UpdateAsync(user);
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user