Compare commits

...

17 Commits

Author SHA1 Message Date
Michael Green
129fd6d4e4 Fix broken xml file (#81)
* Fix for a broken XML file not included in the release

* EJS version bump
2023-09-02 01:52:33 +10:00
Michael Green
28823d940f Filter settings are now stored in a session cookie (#80) 2023-09-02 01:33:55 +10:00
Michael Green
4b6174e3e2 Added a version number display (#78)
* Added a version number

* Appends version number when loading subpages
2023-09-01 23:48:08 +10:00
Michael Green
8e922182e2 chore(deps): EmulatorJS version bump (#77) 2023-09-01 21:15:01 +10:00
Michael Green
25f1895fe5 Added feature to build collections of ROM's based on a filter (#76)
* fix: added visual feed back for mass rom matching

* chore(deps): EmulatorJS version bump

* chore(deps): nuget package version bump

* feat: added cover art to the emulator

* ci: updated .gitignore

* ci: remove .DS_Store files

* feat: updated the about box, and labeled the IGDB user score

* chore(deps): EmulatorJS version bump

* feat: start of collections build, and styling changes

* fix: updated PlatformMap.json file with more platforms and fixed SNES extensions

* feat: more progress on romsets

* doc: updated readme to include new screenshots and discord link

* fix: repairs an issue where the author column in signatures was too narrow

* chore(deps): EmulatorJS version bump

* feat: Collection build code mostly complete

* fix: renamed collection classes to avoid conflicts in Swagger

* Re-wrote collection builder to correct major bugs and performance

* Completed collection builder and zipper

* API changes completed

* Fixed some last minute Collections API bugs

* Collections mostly complete. Todo: delete button

* Completed collections build
2023-09-01 21:02:15 +10:00
Michael Green
07caab074a Create CODE_OF_CONDUCT.md (#61) 2023-08-15 16:43:42 +10:00
Michael Green
f7906f692d Add more filter types (#50)
* feat: support for filtering by igdb rating

* fix: new user rating filter was always excluding unrated games

* feat: added metadata for game multiplayer modes

* feat: added metadata for game player perspectives

* feat: added metadata for game themes

* chore(deps): EmulatorJS version bump

* feat: all filters added

* feat: filter options now have visible toggles

* feat: jazzed up the styling of the game rating
2023-08-13 17:41:56 +10:00
Michael Green
14f836d46a Improve metadata first load performance (#46)
* fix: reduces the number of full metadata calls - speeding up imports #45

* chore(deps): update EmulatorJS
2023-08-06 22:14:39 +10:00
Michael Green
c396a81c1b Implement Bulk Change Function (#44)
* feat: added Sega 32X and Sega CD mappings

* feat: added lazy loading to the main game library

* fix: using full file name when loading roms into the emulator #43

* feat: introduced bulk rom matching #25

* fix: xss fix
2023-08-04 10:30:22 +10:00
Michael Green
59df041cfd fix: typo in zip signature builder would cause a loop (#41) 2023-07-27 16:49:30 +10:00
Michael Green
2355c5ac97 fix: zip inspection now works during library scans as well as during import (#40) 2023-07-27 15:41:58 +10:00
Michael Green
7891acd218 feat: extract zip files smaller than 1GiB to attempt to get a signature from the files inside. The first hit is considered to be the signature for the archive. (#39) 2023-07-27 00:15:15 +10:00
Michael Green
4ad51e98e2 feat: minor styling updates (#38) 2023-07-26 22:36:50 +10:00
Michael Green
8a001f9fa4 feat: added platform disk usage breakdown on the settings page (#37) 2023-07-25 21:26:59 +10:00
Michael Green
98fb360483 feat: updated EmulatorJS to v4.0.5 (#36) 2023-07-24 21:39:11 +10:00
dependabot[bot]
56cbc441f5 chore(deps): bump MySql.Data from 8.0.33 to 8.1.0 (#33)
Bumps MySql.Data from 8.0.33 to 8.1.0.

---
updated-dependencies:
- dependency-name: MySql.Data
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-22 23:37:41 +10:00
Michael Green
113de6a009 ci: add dependabot support for EmulatorJS submodule (#32) 2023-07-22 22:52:04 +10:00
73 changed files with 4386 additions and 1026 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

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

View File

@@ -14,6 +14,12 @@ jobs:
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: Set up QEMU
uses: docker/setup-qemu-action@v2

810
.gitignore vendored
View File

@@ -1,405 +1,405 @@
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/
# Mac bundle stuff
*.dmg
*.app
# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# content below from: https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/
# Mac bundle stuff
*.dmg
*.app
# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# content below from: https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/

128
CODE_OF_CONDUCT.md Normal file
View 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
View File

@@ -0,0 +1,3 @@
<Project>
<Import Project="build\Version.props" />
</Project>

View File

@@ -22,6 +22,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
README.MD = README.MD
LICENSE = LICENSE
.gitignore = .gitignore
.github\dependabot.yml = .github\dependabot.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "screenshots", "screenshots", "{F1A847C7-57BC-4DA9-8F83-CD060A7F5122}"

View File

@@ -5,6 +5,7 @@ This is the server for the Gaseous system. All your games and metadata are store
## Screenshots
![Library](./screenshots/Library.png)
![Game](./screenshots/Game.png)
![Emulator](./screenshots/Emulator.png)
## Requirements
* MySQL Server 8+
@@ -18,6 +19,9 @@ The following projects are used by Gaseous
* https://github.com/kamranayub/igdb-dotnet
* https://github.com/EmulatorJS/EmulatorJS
## Discord Server
[![Join our Discord server!](https://invite.casperiv.dev/?inviteCode=Nhu7wpT3k4&format=svg)](https://discord.gg/Nhu7wpT3k4)
## 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).

5
build/Version.props Normal file
View File

@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<Version>1.5.0</Version>
</PropertyGroup>
</Project>

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,607 @@
using System;
using System.Data;
using System.IO.Compression;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using gaseous_server.Classes.Metadata;
using gaseous_server.Controllers;
using gaseous_tools;
using IGDB.Models;
using Newtonsoft.Json;
namespace gaseous_server.Classes
{
public class Collections
{
public Collections()
{
}
public static List<CollectionItem> GetCollections() {
Database db = new gaseous_tools.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 gaseous_tools.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 gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "INSERT INTO RomCollections (`Name`, Description, Platforms, Genres, Players, PlayerPerspectives, Themes, MinimumRating, MaximumRating, MaximumRomsPerPlatform, MaximumBytesPerPlatform, MaximumCollectionSizeInBytes, BuiltStatus) VALUES (@name, @description, @platforms, @genres, @players, @playerperspectives, @themes, @minimumrating, @maximumrating, @maximumromsperplatform, @maximumbytesperplatform, @maximumcollectionsizeinbytes, @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("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)
{
Database db = new gaseous_tools.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, 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("builtstatus", CollectionItem.CollectionBuildStatus.WaitingForBuild);
db.ExecuteCMD(sql, dbDict);
string CollectionZipFile = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + ".zip");
if (File.Exists(CollectionZipFile))
{
File.Delete(CollectionZipFile);
}
CollectionItem collectionItem = GetCollection(Id);
StartCollectionItemBuild(Id);
return collectionItem;
}
public static void DeleteCollection(long Id)
{
Database db = new gaseous_tools.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 gaseous_tools.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
foreach (ProcessQueue.QueueItem qi in ProcessQueue.QueueItems)
{
if (qi.ItemType == ProcessQueue.QueueItemType.CollectionCompiler) {
qi.ForceExecute();
break;
}
}
}
}
public static CollectionContents GetCollectionContent(CollectionItem collectionItem) {
List<CollectionContents.CollectionPlatformItem> collectionPlatformItems = new List<CollectionContents.CollectionPlatformItem>();
// get platforms
List<Platform> platforms = new List<Platform>();
if (collectionItem.Platforms.Count > 0) {
foreach (long PlatformId in collectionItem.Platforms) {
platforms.Add(Platforms.GetPlatform(PlatformId));
}
} else {
// get all platforms to pull from
FilterController filterController = new FilterController();
platforms.AddRange((List<Platform>)filterController.Filter()["platforms"]);
}
// build collection
List<CollectionContents.CollectionPlatformItem> platformItems = new List<CollectionContents.CollectionPlatformItem>();
foreach (Platform platform in platforms) {
long TotalRomSize = 0;
long TotalGameCount = 0;
List<Game> 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>();
foreach (Game game in games) {
CollectionContents.CollectionPlatformItem.CollectionGameItem collectionGameItem = new CollectionContents.CollectionPlatformItem.CollectionGameItem(game);
List<Roms.GameRomItem> gameRoms = Roms.GetRoms((long)game.Id, (long)platform.Id);
bool AddGame = false;
// calculate total rom size for the game
long GameRomSize = 0;
foreach (Roms.GameRomItem gameRom in gameRoms) {
GameRomSize += 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);
}
}
}
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);
}
}
}
CollectionContents collectionContents = new CollectionContents();
collectionContents.Collection = collectionPlatformItems;
return collectionContents;
}
public static void CompileCollections()
{
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
List<CollectionItem> collectionItems = GetCollections();
foreach (CollectionItem collectionItem in collectionItems)
{
if (collectionItem.BuildStatus == CollectionItem.CollectionBuildStatus.WaitingForBuild)
{
// 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 + ".zip");
string ZipFileTempPath = Path.Combine(Config.LibraryConfiguration.LibraryTempDirectory, collectionItem.Id.ToString());
try
{
// clean up if needed
if (File.Exists(ZipFilePath))
{
File.Delete(ZipFilePath);
}
if (Directory.Exists(ZipFileTempPath))
{
Directory.Delete(ZipFileTempPath, true);
}
// gather collection files
Directory.CreateDirectory(ZipFileTempPath);
foreach (CollectionContents.CollectionPlatformItem collectionPlatformItem in collectionPlatformItems)
{
// create platform directory
string ZipPlatformPath = Path.Combine(ZipFileTempPath, collectionPlatformItem.Slug);
if (!Directory.Exists(ZipPlatformPath))
{
Directory.CreateDirectory(ZipPlatformPath);
}
foreach (CollectionContents.CollectionPlatformItem.CollectionGameItem collectionGameItem in collectionPlatformItem.Games)
{
// create game directory
string ZipGamePath = Path.Combine(ZipPlatformPath, collectionGameItem.Slug);
if (!Directory.Exists(ZipGamePath))
{
Directory.CreateDirectory(ZipGamePath);
}
// copy in roms
foreach (Roms.GameRomItem gameRomItem in collectionGameItem.Roms)
{
if (File.Exists(gameRomItem.Path))
{
File.Copy(gameRomItem.Path, Path.Combine(ZipGamePath, gameRomItem.Name));
}
}
}
}
// compress to zip
ZipFile.CreateFromDirectory(ZipFileTempPath, ZipFilePath, CompressionLevel.SmallestSize, false);
// clean up
if (Directory.Exists(ZipFileTempPath))
{
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"], "[ ]");
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.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; }
[JsonIgnore]
public CollectionBuildStatus BuildStatus
{
get
{
if (_BuildStatus == CollectionBuildStatus.Completed)
{
if (File.Exists(Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + ".zip")))
{
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 + ".zip");
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 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 += 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 List<Roms.GameRomItem> Roms { get; set; }
public long RomSize {
get {
long Size = 0;
foreach (Roms.GameRomItem Rom in Roms) {
Size += Rom.Size;
}
return Size;
}
}
}
}
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Data;
using System.IO.Compression;
using System.Security.Policy;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@@ -79,7 +80,6 @@ namespace gaseous_server.Classes
{
Logging.Log(Logging.LogType.Information, "Import Game", " " + GameFileImportPath + " not in database - processing");
// process as a single file
Models.Signatures_Games discoveredSignature = GetFileSignature(hash, fi, GameFileImportPath);
// get discovered platform
@@ -121,7 +121,42 @@ namespace gaseous_server.Classes
}
}
public static Models.Signatures_Games GetFileSignature(Common.hashObject hash, FileInfo fi, string GameFileImportPath)
public static Models.Signatures_Games GetFileSignature(Common.hashObject hash, FileInfo fi, string GameFileImportPath)
{
Models.Signatures_Games discoveredSignature = _GetFileSignature(hash, fi, GameFileImportPath);
if ((Path.GetExtension(GameFileImportPath) == ".zip") && (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, Path.GetRandomFileName());
if (!Directory.Exists(ExtractPath)) { Directory.CreateDirectory(ExtractPath); }
ZipFile.ExtractToDirectory(GameFileImportPath, ExtractPath);
// loop through contents until we find the first signature match
foreach (string file in Directory.GetFiles(ExtractPath))
{
FileInfo zfi = new FileInfo(file);
Common.hashObject zhash = new Common.hashObject(file);
Models.Signatures_Games zDiscoveredSignature = _GetFileSignature(zhash, zfi, file);
zDiscoveredSignature.Rom.Name = Path.ChangeExtension(zDiscoveredSignature.Rom.Name, ".zip");
if (zDiscoveredSignature.Score > discoveredSignature.Score)
{
discoveredSignature = zDiscoveredSignature;
break;
}
}
if (Directory.Exists(ExtractPath)) { Directory.Delete(ExtractPath, true); }
}
return discoveredSignature;
}
private static Models.Signatures_Games _GetFileSignature(Common.hashObject hash, FileInfo fi, string GameFileImportPath)
{
// check 1: do we have a signature for it?
gaseous_server.Controllers.SignaturesController sc = new Controllers.SignaturesController();
@@ -232,7 +267,7 @@ namespace gaseous_server.Classes
if (games.Length == 1)
{
// 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);
GameFound = true;
break;
@@ -340,7 +375,7 @@ namespace gaseous_server.Classes
}
dbDict.Add("platformid", Common.ReturnValueIfNull(determinedPlatform.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("md5", hash.md5hash);
dbDict.Add("sha1", hash.sha1hash);
@@ -390,7 +425,7 @@ namespace gaseous_server.Classes
// get metadata
IGDB.Models.Platform platform = gaseous_server.Classes.Metadata.Platforms.GetPlatform(rom.PlatformId);
IGDB.Models.Game game = gaseous_server.Classes.Metadata.Games.GetGame(rom.GameId, false, false);
IGDB.Models.Game game = gaseous_server.Classes.Metadata.Games.GetGame(rom.GameId, false, false, false);
// build path
string platformSlug = "Unknown Platform";

View File

@@ -0,0 +1,110 @@
using System;
using gaseous_tools;
using IGDB;
using IGDB.Models;
using MySqlX.XDevAPI.Common;
using static gaseous_tools.Config.ConfigFile;
namespace gaseous_server.Classes.Metadata
{
public class GameModes
{
const string fieldList = "fields checksum,created_at,name,slug,updated_at,url;";
public GameModes()
{
}
private static IGDBClient igdb = new IGDBClient(
// Found in Twitch Developer portal for your app
Config.IGDB.ClientId,
Config.IGDB.Secret
);
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();
bool forceImageDownload = false;
switch (cacheStatus)
{
case Storage.CacheStatus.NotPresent:
returnValue = await GetObjectFromServer(WhereClause);
Storage.NewCacheValue(returnValue);
forceImageDownload = true;
break;
case Storage.CacheStatus.Expired:
returnValue = await GetObjectFromServer(WhereClause);
Storage.NewCacheValue(returnValue, true);
forceImageDownload = true;
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
var results = await igdb.QueryAsync<GameMode>(IGDBClient.Endpoints.GameModes, query: fieldList + " " + WhereClause + ";");
var result = results.First();
return result;
}
}
}

View File

@@ -21,7 +21,7 @@ namespace gaseous_server.Classes.Metadata
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)
{
@@ -45,14 +45,14 @@ namespace gaseous_server.Classes.Metadata
}
else
{
Task<Game> RetVal = _GetGame(SearchUsing.id, Id, followSubGames, forceRefresh);
Task<Game> RetVal = _GetGame(SearchUsing.id, Id, getAllMetadata, followSubGames, forceRefresh);
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;
}
@@ -61,7 +61,7 @@ namespace gaseous_server.Classes.Metadata
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
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
@@ -99,12 +99,12 @@ namespace gaseous_server.Classes.Metadata
case Storage.CacheStatus.NotPresent:
returnValue = await GetObjectFromServer(WhereClause);
Storage.NewCacheValue(returnValue);
UpdateSubClasses(returnValue, followSubGames);
UpdateSubClasses(returnValue, getAllMetadata, followSubGames);
return returnValue;
case Storage.CacheStatus.Expired:
returnValue = await GetObjectFromServer(WhereClause);
Storage.NewCacheValue(returnValue, true);
UpdateSubClasses(returnValue, followSubGames);
UpdateSubClasses(returnValue, getAllMetadata, followSubGames);
return returnValue;
case Storage.CacheStatus.Current:
return Storage.GetCacheValue<Game>(returnValue, "id", (long)searchValue);
@@ -113,117 +113,152 @@ namespace gaseous_server.Classes.Metadata
}
}
private static void UpdateSubClasses(Game Game, bool followSubGames)
private static void UpdateSubClasses(Game Game, bool getAllMetadata, bool followSubGames)
{
if (Game.AgeRatings != null)
{
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)
{
Cover GameCover = Covers.GetCover(Game.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game));
}
if (Game.ExternalGames != null)
if (getAllMetadata == true)
{
foreach (long ExternalGameId in Game.ExternalGames.Ids)
if (Game.AgeRatings != null)
{
ExternalGame GameExternalGame = ExternalGames.GetExternalGames(ExternalGameId);
foreach (long AgeRatingId in Game.AgeRatings.Ids)
{
AgeRating GameAgeRating = AgeRatings.GetAgeRatings(AgeRatingId);
}
}
}
if (Game.Franchise != null)
{
Franchise GameFranchise = Franchises.GetFranchises(Game.Franchise.Id);
}
if (Game.Franchises != null)
{
foreach (long FranchiseId in Game.Franchises.Ids)
if (Game.AlternativeNames != null)
{
Franchise GameFranchise = Franchises.GetFranchises(FranchiseId);
foreach (long AlternativeNameId in Game.AlternativeNames.Ids)
{
AlternativeName GameAlternativeName = AlternativeNames.GetAlternativeNames(AlternativeNameId);
}
}
}
if (Game.Genres != null)
{
foreach (long GenreId in Game.Genres.Ids)
if (Game.Artworks != null)
{
Genre GameGenre = Genres.GetGenres(GenreId);
foreach (long ArtworkId in Game.Artworks.Ids)
{
Artwork GameArtwork = Artworks.GetArtwork(ArtworkId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game));
}
}
}
if (Game.InvolvedCompanies != null)
{
foreach (long involvedCompanyId in Game.InvolvedCompanies.Ids)
if (followSubGames)
{
InvolvedCompany involvedCompany = InvolvedCompanies.GetInvolvedCompanies(involvedCompanyId);
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.Platforms != null)
{
foreach (long PlatformId in Game.Platforms.Ids)
if (Game.Collection != null)
{
Platform GamePlatform = Platforms.GetPlatform(PlatformId);
Collection GameCollection = Collections.GetCollections(Game.Collection.Id);
}
}
if (Game.Screenshots != null)
{
foreach (long ScreenshotId in Game.Screenshots.Ids)
if (Game.ExternalGames != null)
{
Screenshot GameScreenshot = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game));
foreach (long ExternalGameId in Game.ExternalGames.Ids)
{
ExternalGame GameExternalGame = ExternalGames.GetExternalGames(ExternalGameId);
}
}
}
if (Game.Videos != null)
{
foreach (long GameVideoId in Game.Videos.Ids)
if (Game.Franchise != null)
{
GameVideo gameVideo = GamesVideos.GetGame_Videos(GameVideoId);
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.Genres != null)
{
foreach (long GenreId in Game.Genres.Ids)
{
Genre GameGenre = Genres.GetGenres(GenreId);
}
}
if (Game.InvolvedCompanies != null)
{
foreach (long involvedCompanyId in Game.InvolvedCompanies.Ids)
{
InvolvedCompany involvedCompany = InvolvedCompanies.GetInvolvedCompanies(involvedCompanyId);
}
}
if (Game.GameModes != null)
{
foreach (long gameModeId in Game.GameModes.Ids)
{
GameMode gameMode = GameModes.GetGame_Modes(gameModeId);
}
}
if (Game.MultiplayerModes != null)
{
foreach (long multiplayerModeId in Game.MultiplayerModes.Ids)
{
MultiplayerMode multiplayerMode = MultiplayerModes.GetGame_MultiplayerModes(multiplayerModeId);
}
}
if (Game.Platforms != null)
{
foreach (long PlatformId in Game.Platforms.Ids)
{
Platform GamePlatform = Platforms.GetPlatform(PlatformId);
}
}
if (Game.PlayerPerspectives != null)
{
foreach (long PerspectiveId in Game.PlayerPerspectives.Ids)
{
PlayerPerspective GamePlayPerspective = PlayerPerspectives.GetGame_PlayerPerspectives(PerspectiveId);
}
}
if (Game.Screenshots != null)
{
foreach (long ScreenshotId in Game.Screenshots.Ids)
{
Screenshot GameScreenshot = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game));
}
}
if (Game.Themes != null)
{
foreach (long ThemeId in Game.Themes.Ids)
{
Theme GameTheme = Themes.GetGame_Themes(ThemeId);
}
}
if (Game.Videos != null)
{
foreach (long GameVideoId in Game.Videos.Ids)
{
GameVideo gameVideo = GamesVideos.GetGame_Videos(GameVideoId);
}
}
}
}

View File

@@ -0,0 +1,110 @@
using System;
using gaseous_tools;
using IGDB;
using IGDB.Models;
using MySqlX.XDevAPI.Common;
using static gaseous_tools.Config.ConfigFile;
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()
{
}
private static IGDBClient igdb = new IGDBClient(
// Found in Twitch Developer portal for your app
Config.IGDB.ClientId,
Config.IGDB.Secret
);
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();
bool forceImageDownload = false;
switch (cacheStatus)
{
case Storage.CacheStatus.NotPresent:
returnValue = await GetObjectFromServer(WhereClause);
Storage.NewCacheValue(returnValue);
forceImageDownload = true;
break;
case Storage.CacheStatus.Expired:
returnValue = await GetObjectFromServer(WhereClause);
Storage.NewCacheValue(returnValue, true);
forceImageDownload = true;
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
var results = await igdb.QueryAsync<MultiplayerMode>(IGDBClient.Endpoints.MultiplayerModes, query: fieldList + " " + WhereClause + ";");
var result = results.First();
return result;
}
}
}

View File

@@ -0,0 +1,110 @@
using System;
using gaseous_tools;
using IGDB;
using IGDB.Models;
using MySqlX.XDevAPI.Common;
using static gaseous_tools.Config.ConfigFile;
namespace gaseous_server.Classes.Metadata
{
public class PlayerPerspectives
{
const string fieldList = "fields checksum,created_at,name,slug,updated_at,url;";
public PlayerPerspectives()
{
}
private static IGDBClient igdb = new IGDBClient(
// Found in Twitch Developer portal for your app
Config.IGDB.ClientId,
Config.IGDB.Secret
);
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:
returnValue = await GetObjectFromServer(WhereClause);
Storage.NewCacheValue(returnValue, true);
forceImageDownload = true;
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
var results = await igdb.QueryAsync<PlayerPerspective>(IGDBClient.Endpoints.PlayerPerspectives, query: fieldList + " " + WhereClause + ";");
var result = results.First();
return result;
}
}
}

View File

@@ -0,0 +1,110 @@
using System;
using gaseous_tools;
using IGDB;
using IGDB.Models;
using MySqlX.XDevAPI.Common;
using static gaseous_tools.Config.ConfigFile;
namespace gaseous_server.Classes.Metadata
{
public class Themes
{
const string fieldList = "fields checksum,created_at,name,slug,updated_at,url;";
public Themes()
{
}
private static IGDBClient igdb = new IGDBClient(
// Found in Twitch Developer portal for your app
Config.IGDB.ClientId,
Config.IGDB.Secret
);
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:
returnValue = await GetObjectFromServer(WhereClause);
Storage.NewCacheValue(returnValue, true);
forceImageDownload = true;
break;
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
var results = await igdb.QueryAsync<Theme>(IGDBClient.Endpoints.Themes, query: fieldList + " " + WhereClause + ";");
var result = results.First();
return result;
}
}
}

View File

@@ -17,7 +17,7 @@ namespace gaseous_server.Classes
try
{
Logging.Log(Logging.LogType.Information, "Metadata Refresh", "Refreshing metadata for game " + dr["name"] + " (" + dr["id"] + ")");
Metadata.Games.GetGame((long)dr["id"], true, forceRefresh);
Metadata.Games.GetGame((long)dr["id"], true, true, forceRefresh);
}
catch (Exception ex)
{

View File

@@ -6,12 +6,19 @@ namespace gaseous_server.Classes
{
public class Roms
{
public static List<GameRomItem> GetRoms(long GameId)
public static List<GameRomItem> GetRoms(long GameId, long PlatformId = -1)
{
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM Games_Roms WHERE GameId = @id ORDER BY `Name`";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", GameId);
if (PlatformId == -1) {
sql = "SELECT * FROM Games_Roms WHERE GameId = @id ORDER BY `Name`";
} else {
sql = "SELECT * FROM Games_Roms WHERE GameId = @id AND PlatformId = @platformid ORDER BY `Name`";
dbDict.Add("platformid", PlatformId);
}
DataTable romDT = db.ExecuteCMD(sql, dbDict);
if (romDT.Rows.Count > 0)
@@ -56,7 +63,7 @@ namespace gaseous_server.Classes
IGDB.Models.Platform platform = Classes.Metadata.Platforms.GetPlatform(PlatformId);
// 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);
string sql = "UPDATE Games_Roms SET PlatformId=@platformid, GameId=@gameid WHERE Id = @id";

View File

@@ -25,13 +25,16 @@ namespace gaseous_server.Controllers
{
foreach (ProcessQueue.QueueItem qi in ProcessQueue.QueueItems)
{
if (TaskType == qi.ItemType)
{
if (ForceRun == true)
if (qi.AllowManualStart == true)
{
if (TaskType == qi.ItemType)
{
qi.ForceExecute();
if (ForceRun == true)
{
qi.ForceExecute();
}
return qi;
}
return qi;
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
@@ -25,6 +26,39 @@ namespace gaseous_server.Controllers
return Classes.Bios.GetBios(PlatformId, AvailableOnly);
}
[HttpGet]
[HttpHead]
[Route("zip/{PlatformId}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetBiosCompressed(long PlatformId)
{
try
{
IGDB.Models.Platform platform = Classes.Metadata.Platforms.GetPlatform(PlatformId);
string biosPath = Path.Combine(gaseous_tools.Config.LibraryConfiguration.LibraryBIOSDirectory, platform.Slug);
string tempFile = Path.GetTempFileName();
using (FileStream zipFile = System.IO.File.Create(tempFile))
using (var zipArchive = new ZipArchive(zipFile, ZipArchiveMode.Create))
{
foreach (string file in Directory.GetFiles(biosPath))
{
zipArchive.CreateEntryFromFile(file, Path.GetFileName(file));
}
}
var stream = new FileStream(tempFile, FileMode.Open);
return File(stream, "application/zip", platform.Slug + ".zip");
}
catch
{
return NotFound();
}
}
[HttpGet]
[HttpHead]
[Route("{PlatformId}/{BiosName}")]

View File

@@ -0,0 +1,191 @@
using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace gaseous_server.Controllers
{
[ApiController]
[Route("api/v1/[controller]")]
public class CollectionsController : Controller
{
/// <summary>
/// Gets all ROM collections
/// </summary>
/// <returns></returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public List<Classes.Collections.CollectionItem> GetCollections()
{
return Classes.Collections.GetCollections();
}
/// <summary>
/// Gets a specific ROM collection
/// </summary>
/// <param name="CollectionId"></param>
/// <param name="Build">Set to true to begin the collection build process</param>
/// <returns></returns>
[HttpGet]
[Route("{CollectionId}")]
[ProducesResponseType(typeof(Classes.Collections.CollectionItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetCollection(long CollectionId, bool Build = false)
{
try
{
if (Build == true)
{
Classes.Collections.StartCollectionItemBuild(CollectionId);
}
return Ok(Classes.Collections.GetCollection(CollectionId));
}
catch
{
return NotFound();
}
}
/// <summary>
/// Gets the contents of the specified ROM collection
/// </summary>
/// <param name="CollectionId"></param>
/// <returns></returns>
[HttpGet]
[Route("{CollectionId}/Roms")]
[ProducesResponseType(typeof(List<Classes.Collections.CollectionContents.CollectionPlatformItem>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetCollectionRoms(long CollectionId)
{
try
{
Classes.Collections.CollectionItem collectionItem = Classes.Collections.GetCollection(CollectionId);
return Ok(Classes.Collections.GetCollectionContent(collectionItem));
}
catch
{
return NotFound();
}
}
/// <summary>
/// Gets a preview of the provided collection item
/// </summary>
/// <param name="Item"></param>
/// <returns></returns>
[HttpPost]
[Route("Preview")]
[ProducesResponseType(typeof(List<Classes.Collections.CollectionContents.CollectionPlatformItem>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetCollectionRomsPreview(Classes.Collections.CollectionItem Item)
{
//try
//{
return Ok(Classes.Collections.GetCollectionContent(Item));
//}
//catch (Exception ex)
//{
// return NotFound(ex);
//}
}
/// <summary>
/// Gets ROM collection in zip format
/// </summary>
/// <param name="CollectionId"></param>
/// <returns></returns>
[HttpGet]
[Route("{CollectionId}/Roms/Zip")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetCollectionRomsZip(long CollectionId)
{
try
{
Classes.Collections.CollectionItem collectionItem = Classes.Collections.GetCollection(CollectionId);
string ZipFilePath = Path.Combine(gaseous_tools.Config.LibraryConfiguration.LibraryCollectionsDirectory, CollectionId + ".zip");
if (System.IO.File.Exists(ZipFilePath))
{
var stream = new FileStream(ZipFilePath, FileMode.Open);
return File(stream, "application/zip", collectionItem.Name + ".zip");
}
else
{
return NotFound();
}
}
catch
{
return NotFound();
}
}
/// <summary>
/// Creates a new ROM collection
/// </summary>
/// <param name="Item"></param>
/// <returns></returns>
[HttpPost]
[ProducesResponseType(typeof(Classes.Collections.CollectionItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult NewCollection(Classes.Collections.CollectionItem Item)
{
try
{
return Ok(Classes.Collections.NewCollection(Item));
}
catch (Exception ex)
{
return BadRequest(ex);
}
}
/// <summary>
/// Edits an existing collection
/// </summary>
/// <param name="CollectionId"></param>
/// <param name="Item"></param>
/// <returns></returns>
[HttpPatch]
[Route("{CollectionId}")]
[ProducesResponseType(typeof(Classes.Collections.CollectionItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult EditCollection(long CollectionId, Classes.Collections.CollectionItem Item)
{
try
{
return Ok(Classes.Collections.EditCollection(CollectionId, Item));
}
catch
{
return NotFound();
}
}
/// <summary>
/// Deletes the specified ROM collection
/// </summary>
/// <param name="CollectionId"></param>
[HttpDelete]
[Route("{CollectionId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult DeleteCollection(long CollectionId)
{
try
{
Classes.Collections.DeleteCollection(CollectionId);
return Ok();
}
catch
{
return NotFound();
}
}
}
}

View File

@@ -45,6 +45,39 @@ namespace gaseous_server.Controllers
}
FilterSet.Add("genres", genres);
// game modes
List<GameMode> gameModes = new List<GameMode>();
sql = "SELECT DISTINCT t1.Id, t1.`Name` FROM GameMode AS t1 JOIN (SELECT * FROM Game WHERE (SELECT COUNT(Id) FROM Games_Roms WHERE GameId = Game.Id) > 0) AS t2 ON JSON_CONTAINS(t2.GameModes, CAST(t1.Id AS char), '$') ORDER BY t1.Id";
dbResponse = db.ExecuteCMD(sql);
foreach (DataRow dr in dbResponse.Rows)
{
gameModes.Add(Classes.Metadata.GameModes.GetGame_Modes((long)dr["id"]));
}
FilterSet.Add("gamemodes", gameModes);
// player perspectives
List<PlayerPerspective> playerPerspectives = new List<PlayerPerspective>();
sql = "SELECT DISTINCT t1.Id, t1.`Name` FROM PlayerPerspective AS t1 JOIN (SELECT * FROM Game WHERE (SELECT COUNT(Id) FROM Games_Roms WHERE GameId = Game.Id) > 0) AS t2 ON JSON_CONTAINS(t2.PlayerPerspectives, CAST(t1.Id AS char), '$') ORDER BY t1.`Name`";
dbResponse = db.ExecuteCMD(sql);
foreach (DataRow dr in dbResponse.Rows)
{
playerPerspectives.Add(Classes.Metadata.PlayerPerspectives.GetGame_PlayerPerspectives((long)dr["id"]));
}
FilterSet.Add("playerperspectives", playerPerspectives);
// themes
List<Theme> themes = new List<Theme>();
sql = "SELECT DISTINCT t1.Id, t1.`Name` FROM Theme AS t1 JOIN (SELECT * FROM Game WHERE (SELECT COUNT(Id) FROM Games_Roms WHERE GameId = Game.Id) > 0) AS t2 ON JSON_CONTAINS(t2.Themes, CAST(t1.Id AS char), '$') ORDER BY t1.`Name`";
dbResponse = db.ExecuteCMD(sql);
foreach (DataRow dr in dbResponse.Rows)
{
themes.Add(Classes.Metadata.Themes.GetGame_Themes((long)dr["id"]));
}
FilterSet.Add("themes", themes);
return FilterSet;
}
}

View File

@@ -1,29 +1,53 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Threading.Tasks;
using gaseous_server.Classes.Metadata;
using gaseous_tools;
using IGDB.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting;
using Org.BouncyCastle.Asn1.X509;
using static gaseous_server.Classes.Metadata.AgeRatings;
namespace gaseous_server.Controllers
{
[Route("api/v1/[controller]")]
[ApiController]
public class GamesController : ControllerBase
{
[HttpGet]
[ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)]
public ActionResult Game(string name = "", string platform = "", string genre = "", bool sortdescending = false)
namespace gaseous_server.Controllers
{
[Route("api/v1/[controller]")]
[ApiController]
public class GamesController : ControllerBase
{
[HttpGet]
[ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)]
public ActionResult Game(
string name = "",
string platform = "",
string genre = "",
string gamemode = "",
string playerperspective = "",
string theme = "",
int minrating = -1,
int maxrating = -1,
bool sortdescending = false)
{
return Ok(GetGames(name, platform, genre, gamemode, playerperspective, theme, minrating, maxrating, sortdescending));
}
public static List<Game> GetGames(
string name = "",
string platform = "",
string genre = "",
string gamemode = "",
string playerperspective = "",
string theme = "",
int minrating = -1,
int maxrating = -1,
bool sortdescending = false)
{
string whereClause = "";
string havingClause = "";
@@ -41,6 +65,20 @@ namespace gaseous_server.Controllers
havingClauses.Add(tempVal);
}
if (minrating != -1)
{
string ratingTempMinVal = "totalRating >= @totalMinRating";
whereParams.Add("@totalMinRating", minrating);
havingClauses.Add(ratingTempMinVal);
}
if (maxrating != -1)
{
string ratingTempMaxVal = "totalRating <= @totalMaxRating";
whereParams.Add("@totalMaxRating", maxrating);
havingClauses.Add(ratingTempMaxVal);
}
if (platform.Length > 0)
{
tempVal = "Games_Roms.PlatformId IN (";
@@ -77,6 +115,60 @@ namespace gaseous_server.Controllers
whereClauses.Add(tempVal);
}
if (gamemode.Length > 0)
{
tempVal = "(";
string[] gameModeClauseItems = gamemode.Split(",");
for (int i = 0; i < gameModeClauseItems.Length; i++)
{
if (i > 0)
{
tempVal += " AND ";
}
string gameModeLabel = "@GameMode" + i;
tempVal += "JSON_CONTAINS(Game.GameModes, " + gameModeLabel + ", '$')";
whereParams.Add(gameModeLabel, gameModeClauseItems[i]);
}
tempVal += ")";
whereClauses.Add(tempVal);
}
if (playerperspective.Length > 0)
{
tempVal = "(";
string[] playerPerspectiveClauseItems = playerperspective.Split(",");
for (int i = 0; i < playerPerspectiveClauseItems.Length; i++)
{
if (i > 0)
{
tempVal += " AND ";
}
string playerPerspectiveLabel = "@PlayerPerspective" + i;
tempVal += "JSON_CONTAINS(Game.PlayerPerspectives, " + playerPerspectiveLabel + ", '$')";
whereParams.Add(playerPerspectiveLabel, playerPerspectiveClauseItems[i]);
}
tempVal += ")";
whereClauses.Add(tempVal);
}
if (theme.Length > 0)
{
tempVal = "(";
string[] themeClauseItems = theme.Split(",");
for (int i = 0; i < themeClauseItems.Length; i++)
{
if (i > 0)
{
tempVal += " AND ";
}
string themeLabel = "@Theme" + i;
tempVal += "JSON_CONTAINS(Game.Themes, " + themeLabel + ", '$')";
whereParams.Add(themeLabel, themeClauseItems[i]);
}
tempVal += ")";
whereClauses.Add(tempVal);
}
// build where clause
if (whereClauses.Count > 0)
{
@@ -124,19 +216,19 @@ namespace gaseous_server.Controllers
RetVal.Add(Classes.Metadata.Games.GetGame(dr));
}
return Ok(RetVal);
}
[HttpGet]
[Route("{GameId}")]
[ProducesResponseType(typeof(Game), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "5Minute")]
return RetVal;
}
[HttpGet]
[Route("{GameId}")]
[ProducesResponseType(typeof(Game), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "5Minute")]
public ActionResult Game(long GameId, bool forceRefresh = false)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, forceRefresh);
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, forceRefresh, false, forceRefresh);
if (gameObject != null)
{
@@ -151,18 +243,18 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/alternativename")]
[ProducesResponseType(typeof(List<AlternativeName>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
}
[HttpGet]
[Route("{GameId}/alternativename")]
[ProducesResponseType(typeof(List<AlternativeName>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameAlternativeNames(long GameId)
{
try
{
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject.AlternativeNames != null)
{
@@ -182,18 +274,18 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/agerating")]
[ProducesResponseType(typeof(List<AgeRatings.GameAgeRating>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
}
[HttpGet]
[Route("{GameId}/agerating")]
[ProducesResponseType(typeof(List<AgeRatings.GameAgeRating>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameAgeClassification(long GameId)
{
try
{
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject.AgeRatings != null)
{
@@ -213,12 +305,12 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/agerating/{RatingId}/image")]
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
}
[HttpGet]
[Route("{GameId}/agerating/{RatingId}/image")]
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameAgeClassification(long GameId, long RatingId)
{
try
@@ -292,18 +384,18 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/artwork")]
[ProducesResponseType(typeof(List<Artwork>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
}
[HttpGet]
[Route("{GameId}/artwork")]
[ProducesResponseType(typeof(List<Artwork>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameArtwork(long GameId)
{
try
{
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
List<Artwork> artworks = new List<Artwork>();
if (gameObject.Artworks != null)
@@ -321,18 +413,18 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/artwork/{ArtworkId}")]
[ProducesResponseType(typeof(Artwork), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
}
[HttpGet]
[Route("{GameId}/artwork/{ArtworkId}")]
[ProducesResponseType(typeof(Artwork), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameArtwork(long GameId, long ArtworkId)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
try
{
@@ -355,17 +447,17 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/artwork/{ArtworkId}/image")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
}
[HttpGet]
[Route("{GameId}/artwork/{ArtworkId}/image")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameCoverImage(long GameId, long ArtworkId)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
try
{
@@ -409,18 +501,18 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/cover")]
[ProducesResponseType(typeof(Cover), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
}
[HttpGet]
[Route("{GameId}/cover")]
[ProducesResponseType(typeof(Cover), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameCover(long GameId)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject != null)
{
IGDB.Models.Cover coverObject = Covers.GetCover(gameObject.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject));
@@ -442,17 +534,17 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/cover/image")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
}
[HttpGet]
[Route("{GameId}/cover/image")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameCoverImage(long GameId)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
string coverFilePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Cover.png");
if (System.IO.File.Exists(coverFilePath)) {
@@ -481,18 +573,18 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/genre")]
[ProducesResponseType(typeof(List<Genre>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
}
[HttpGet]
[Route("{GameId}/genre")]
[ProducesResponseType(typeof(List<Genre>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameGenre(long GameId)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject != null)
{
List<IGDB.Models.Genre> genreObjects = new List<Genre>();
@@ -517,18 +609,18 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/companies")]
[ProducesResponseType(typeof(List<Dictionary<string, object>>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
}
[HttpGet]
[Route("{GameId}/companies")]
[ProducesResponseType(typeof(List<Dictionary<string, object>>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameInvolvedCompanies(long GameId)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject != null)
{
List<Dictionary<string, object>> icObjects = new List<Dictionary<string, object>>();
@@ -560,18 +652,18 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/companies/{CompanyId}")]
[ProducesResponseType(typeof(Dictionary<string, object>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
}
[HttpGet]
[Route("{GameId}/companies/{CompanyId}")]
[ProducesResponseType(typeof(Dictionary<string, object>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameInvolvedCompanies(long GameId, long CompanyId)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject != null)
{
List<Dictionary<string, object>> icObjects = new List<Dictionary<string, object>>();
@@ -601,17 +693,17 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/companies/{CompanyId}/image")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
}
[HttpGet]
[Route("{GameId}/companies/{CompanyId}/image")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameCompanyImage(long GameId, long CompanyId)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
InvolvedCompany involvedCompany = Classes.Metadata.InvolvedCompanies.GetInvolvedCompanies(CompanyId);
Company company = Classes.Metadata.Companies.GetCompanies(involvedCompany.Company.Id);
@@ -644,18 +736,18 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/roms")]
[ProducesResponseType(typeof(List<Classes.Roms.GameRomItem>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
//[ResponseCache(CacheProfileName = "5Minute")]
}
[HttpGet]
[Route("{GameId}/roms")]
[ProducesResponseType(typeof(List<Classes.Roms.GameRomItem>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
//[ResponseCache(CacheProfileName = "5Minute")]
public ActionResult GameRom(long GameId)
{
try
{
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
List<Classes.Roms.GameRomItem> roms = Classes.Roms.GetRoms(GameId);
@@ -665,18 +757,18 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/roms/{RomId}")]
[ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)]
}
[HttpGet]
[Route("{GameId}/roms/{RomId}")]
[ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
//[ResponseCache(CacheProfileName = "5Minute")]
//[ResponseCache(CacheProfileName = "5Minute")]
public ActionResult GameRom(long GameId, long RomId)
{
try
{
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId);
if (rom.GameId == GameId)
@@ -692,17 +784,17 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpPatch]
[Route("{GameId}/roms/{RomId}")]
[ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
}
[HttpPatch]
[Route("{GameId}/roms/{RomId}")]
[ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomRename(long GameId, long RomId, long NewPlatformId, long NewGameId)
{
try
{
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId);
if (rom.GameId == GameId)
@@ -719,17 +811,17 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpDelete]
[Route("{GameId}/roms/{RomId}")]
[ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
}
[HttpDelete]
[Route("{GameId}/roms/{RomId}")]
[ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomDelete(long GameId, long RomId)
{
try
{
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId);
if (rom.GameId == GameId)
@@ -746,18 +838,18 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[HttpHead]
[Route("{GameId}/roms/{RomId}/file")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
}
[HttpGet]
[HttpHead]
[Route("{GameId}/roms/{RomId}/file")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomFile(long GameId, long RomId)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId);
if (rom.GameId != GameId)
@@ -781,12 +873,47 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("search")]
[ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
}
[HttpGet]
[HttpHead]
[Route("{GameId}/roms/{RomId}/{FileName}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomFile(long GameId, long RomId, string FileName)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId);
if (rom.GameId != GameId || rom.Name != FileName)
{
return NotFound();
}
string romFilePath = rom.Path;
if (System.IO.File.Exists(romFilePath))
{
FileStream content = new FileStream(romFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
FileStreamResult response = File(content, "application/octet-stream", rom.Name);
return response;
}
else
{
return NotFound();
}
}
catch
{
return NotFound();
}
}
[HttpGet]
[Route("search")]
[ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameSearch(long RomId = 0, string SearchString = "")
{
try
@@ -818,18 +945,18 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/screenshots")]
[ProducesResponseType(typeof(List<Screenshot>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
}
[HttpGet]
[Route("{GameId}/screenshots")]
[ProducesResponseType(typeof(List<Screenshot>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameScreenshot(long GameId)
{
try
{
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
List<Screenshot> screenshots = new List<Screenshot>();
if (gameObject.Screenshots != null)
@@ -847,18 +974,18 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/screenshots/{ScreenshotId}")]
[ProducesResponseType(typeof(Screenshot), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
}
[HttpGet]
[Route("{GameId}/screenshots/{ScreenshotId}")]
[ProducesResponseType(typeof(Screenshot), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameScreenshot(long GameId, long ScreenshotId)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject != null) {
IGDB.Models.Screenshot screenshotObject = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject));
if (screenshotObject != null)
@@ -879,17 +1006,17 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/screenshots/{ScreenshotId}/image")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
}
[HttpGet]
[Route("{GameId}/screenshots/{ScreenshotId}/image")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameScreenshotImage(long GameId, long ScreenshotId)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
IGDB.Models.Screenshot screenshotObject = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject));
@@ -921,18 +1048,18 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
[HttpGet]
[Route("{GameId}/videos")]
[ProducesResponseType(typeof(List<GameVideo>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
}
[HttpGet]
[Route("{GameId}/videos")]
[ProducesResponseType(typeof(List<GameVideo>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameVideo(long GameId)
{
try
{
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false);
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
List<GameVideo> videos = new List<GameVideo>();
if (gameObject.Videos != null)
@@ -950,6 +1077,6 @@ namespace gaseous_server.Controllers
{
return NotFound();
}
}
}
}
}
}
}

View File

@@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using gaseous_tools;
using Microsoft.AspNetCore.Mvc;
@@ -14,35 +16,95 @@ namespace gaseous_server.Controllers
{
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public Dictionary<string, object> GetSystemStatus()
public SystemInfo GetSystemStatus()
{
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
Dictionary<string, object> ReturnValue = new Dictionary<string, object>();
SystemInfo ReturnValue = new SystemInfo();
// disk size
List<Dictionary<string, object>> Disks = new List<Dictionary<string, object>>();
List<SystemInfo.PathItem> Disks = new List<SystemInfo.PathItem>();
//Disks.Add(GetDisk(gaseous_tools.Config.ConfigurationPath));
Disks.Add(GetDisk(gaseous_tools.Config.LibraryConfiguration.LibraryRootDirectory));
ReturnValue.Add("Paths", Disks);
ReturnValue.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]);
ReturnValue.DatabaseSize = (long)(System.Decimal)dbResponse.Rows[0][1];
// platform statistics
sql = "SELECT Platform.`name`, grc.Count, grs.Size FROM Platform INNER JOIN (SELECT Platform.`name` AS `Name`, SUM(grs.Size) AS Size FROM Platform JOIN Games_Roms AS grs ON (grs.PlatformId = Platform.Id) GROUP BY Platform.`name`) grs ON (grs.`Name` = Platform.`name`) INNER JOIN (SELECT Platform.`name` AS `Name`, COUNT(grc.Size) AS Count FROM Platform JOIN Games_Roms AS grc ON (grc.PlatformId = Platform.Id) GROUP BY Platform.`name`) grc ON (grc.`Name` = Platform.`name`) ORDER BY Platform.`name`;";
dbResponse = db.ExecuteCMD(sql);
ReturnValue.PlatformStatistics = new List<SystemInfo.PlatformStatisticsItem>();
foreach (DataRow dr in dbResponse.Rows)
{
SystemInfo.PlatformStatisticsItem platformStatisticsItem = new SystemInfo.PlatformStatisticsItem
{
Platform = (string)dr["name"],
RomCount = (long)dr["Count"],
TotalSize = (long)(System.Decimal)dr["Size"]
};
ReturnValue.PlatformStatistics.Add(platformStatisticsItem);
}
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);
[HttpGet]
[Route("Version")]
[ProducesResponseType(StatusCodes.Status200OK)]
public Version GetSystemVersion() {
return Assembly.GetExecutingAssembly().GetName().Version;
}
return DiskValues;
[HttpGet]
[Route("VersionFile")]
[ProducesResponseType(StatusCodes.Status200OK)]
public FileContentResult GetSystemVersionAsFile() {
string ver = "var AppVersion = \"" + Assembly.GetExecutingAssembly().GetName().Version.ToString() + "\"";
byte[] bytes = Encoding.UTF8.GetBytes(ver);
return File(bytes, "text/javascript");
}
private SystemInfo.PathItem GetDisk(string Path)
{
SystemInfo.PathItem pathItem = new SystemInfo.PathItem {
LibraryPath = Path,
SpaceUsed = gaseous_tools.Common.DirSize(new DirectoryInfo(Path)),
SpaceAvailable = new DriveInfo(Path).AvailableFreeSpace,
TotalSpace = new DriveInfo(Path).TotalSize
};
return pathItem;
}
public class SystemInfo
{
public Version ApplicationVersion {
get
{
return Assembly.GetExecutingAssembly().GetName().Version;
}
}
public List<PathItem>? Paths { get; set; }
public long DatabaseSize { get; set; }
public List<PlatformStatisticsItem>? PlatformStatistics { get; set; }
public class PathItem
{
public string LibraryPath { get; set; }
public long SpaceUsed { get; set; }
public long SpaceAvailable { get; set; }
public long TotalSpace { get; set; }
}
public class PlatformStatisticsItem
{
public string Platform { get; set; }
public long RomCount { get; set; }
public long TotalSize { get; set; }
}
}
}
}

View File

@@ -9,20 +9,22 @@ namespace gaseous_server
public class QueueItem
{
public QueueItem(QueueItemType ItemType, int ExecutionInterval)
public QueueItem(QueueItemType ItemType, int ExecutionInterval, bool AllowManualStart = true)
{
_ItemType = ItemType;
_ItemState = QueueItemState.NeverStarted;
_LastRunTime = DateTime.UtcNow.AddMinutes(ExecutionInterval);
_Interval = ExecutionInterval;
_AllowManualStart = AllowManualStart;
}
public QueueItem(QueueItemType ItemType, int ExecutionInterval, List<QueueItemType> Blocks)
public QueueItem(QueueItemType ItemType, int ExecutionInterval, List<QueueItemType> Blocks, bool AllowManualStart = true)
{
_ItemType = ItemType;
_ItemState = QueueItemState.NeverStarted;
_LastRunTime = DateTime.UtcNow.AddMinutes(ExecutionInterval);
_Interval = ExecutionInterval;
_AllowManualStart = AllowManualStart;
_Blocks = Blocks;
}
@@ -34,6 +36,7 @@ namespace gaseous_server
private string _LastResult = "";
private string? _LastError = null;
private bool _ForceExecute = false;
private bool _AllowManualStart = true;
private List<QueueItemType> _Blocks = new List<QueueItemType>();
public QueueItemType ItemType => _ItemType;
@@ -50,6 +53,7 @@ namespace gaseous_server
public string LastResult => _LastResult;
public string? LastError => _LastError;
public bool Force => _ForceExecute;
public bool AllowManualStart => _AllowManualStart;
public List<QueueItemType> Blocks => _Blocks;
public void Execute()
@@ -96,6 +100,10 @@ namespace gaseous_server
Classes.ImportGame.LibraryScan();
break;
case QueueItemType.CollectionCompiler:
Logging.Log(Logging.LogType.Information, "Timered Event", "Starting Collection Compiler");
Classes.Collections.CompileCollections();
break;
}
}
catch (Exception ex)
@@ -125,7 +133,8 @@ namespace gaseous_server
TitleIngestor,
MetadataRefresh,
OrganiseLibrary,
LibraryScan
LibraryScan,
CollectionCompiler
}
public enum QueueItemState

View File

@@ -1,11 +1,13 @@
using System.Text.Json.Serialization;
using System.Reflection;
using System.Text.Json.Serialization;
using gaseous_server;
using gaseous_tools;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.OpenApi.Models;
Logging.Log(Logging.LogType.Information, "Startup", "Starting Gaseous Server");
Logging.Log(Logging.LogType.Information, "Startup", "Starting Gaseous Server " + Assembly.GetExecutingAssembly().GetName().Version);
// set up db
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
@@ -83,7 +85,31 @@ builder.Services.Configure<FormOptions>(options =>
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = "Gaseous Server API",
Description = "An API for managing the Gaseous Server",
TermsOfService = new Uri("https://github.com/gaseous-project/gaseous-server"),
Contact = new OpenApiContact
{
Name = "GitHub Repository",
Url = new Uri("https://github.com/gaseous-project/gaseous-server")
},
License = new OpenApiLicense
{
Name = "Gaseous Server License",
Url = new Uri("https://github.com/gaseous-project/gaseous-server/blob/main/LICENSE")
}
});
// using System.Reflection;
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
}
);
builder.Services.AddHostedService<TimedHostedService>();
var app = builder.Build();
@@ -114,7 +140,7 @@ app.MapControllers();
Config.LibraryConfiguration.InitLibrary();
// insert unknown platform and game if not present
gaseous_server.Classes.Metadata.Games.GetGame(0, false, false);
gaseous_server.Classes.Metadata.Games.GetGame(0, false, false, false);
gaseous_server.Classes.Metadata.Platforms.GetPlatform(0);
// organise library
@@ -145,6 +171,7 @@ ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.OrganiseLibrary
})
);
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(ProcessQueue.QueueItemType.CollectionCompiler, 5, false));
// start the app
app.Run();

View File

@@ -87,7 +87,15 @@
],
"WebEmulator": {
"Type": "EmulatorJS",
"Core": "segaMD"
"Core": "segaMD",
"Bios": [
{
"hash": "45e298905a08f9cfb38fd504cd6dbc84",
"description": "MegaDrive TMSS startup ROM",
"filename": "bios_MD.bin",
"region": ""
}
]
}
},
{
@@ -98,7 +106,9 @@
"N64"
],
"KnownFileExtensions": [
".Z64"
".Z64",
".V64",
".N64"
],
"WebEmulator": {
"Type": "EmulatorJS",
@@ -110,20 +120,32 @@
"IGDBName": "Nintendo Entertainment System",
"AlternateNames": [
"Nintendo Entertainment System",
"NES"
"NES",
"Nintendo Famicom & Entertainment System"
],
"KnownFileExtensions": [
".NES",
".FDS",
".FIG",
".MGD",
".SFC",
".SMC",
".SWC"
".NEZ",
".UNF",
".UNIF"
],
"WebEmulator": {
"Type": "EmulatorJS",
"Core": "nes"
"Core": "nes",
"Bios": [
{
"hash": "ca30b50f880eb660a320674ed365ef7a",
"description": "Family Computer Disk System BIOS - Required for Famicom Disk System emulation",
"filename": "disksys.rom",
"region": ""
},
{
"hash": "7f98d77d7a094ad7d069b74bd553ec98",
"description": "Game Genie add-on cartridge - Required for Game Genei Add-on emulation (Only supported on the fceumm core)",
"filename": "gamegenie.nes",
"region": ""
}
]
}
},
{
@@ -135,7 +157,10 @@
"Super Nintendo",
"SNES"
],
"KnownFileExtensions": [],
"KnownFileExtensions": [
".SFC",
".SMC"
],
"WebEmulator": {
"Type": "EmulatorJS",
"Core": "snes"
@@ -187,5 +212,55 @@
"Core": "arcade",
"Bios": []
}
},
{
"IGDBId": 30,
"IGDBName": "Sega 32X",
"AlternateNames": [
"Sega 32X",
"Sega32",
"Sega32X"
],
"KnownFileExtensions": [],
"WebEmulator": {
"Type": "EmulatorJS",
"Core": "sega32x",
"Bios": []
}
},
{
"IGDBId": 78,
"IGDBName": "Sega CD",
"AlternateNames": [
"Sega CD",
"Mega CD",
"segacd",
"Sega Mega-CD & Sega CD"
],
"KnownFileExtensions": [],
"WebEmulator": {
"Type": "EmulatorJS",
"Core": "segaCD",
"Bios": [
{
"hash": "e66fa1dc5820d254611fdcdba0662372",
"description": "MegaCD EU BIOS - Required",
"filename": "bios_CD_E.bin",
"region": "EU"
},
{
"hash": "2efd74e3232ff260e371b99f84024f7f",
"description": "SegaCD US BIOS - Required",
"filename": "bios_CD_U.bin",
"region": "US"
},
{
"hash": "278a9397d192149e84e820ac621a8edd",
"description": "MegaCD JP BIOS - Required",
"filename": "bios_CD_J.bin",
"region": "JP"
}
]
}
}
]

View File

@@ -13,10 +13,14 @@
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\net7.0\gaseous-server.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\net7.0\gaseous-server.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.9" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.10" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.8" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.9" />
<PackageReference Include="IGDB" Version="2.3.2" />
</ItemGroup>
@@ -88,24 +92,21 @@
<Folder Include="Classes\" />
<Folder Include="Classes\SignatureIngestors\" />
<Folder Include="Support\" />
<Folder Include="wwwroot\" />
<Folder Include="Classes\Metadata\" />
<Folder Include="Assets\" />
<Folder Include="Assets\Ratings\" />
<Folder Include="Assets\Ratings\ESRB\" />
<Folder Include="Assets\Ratings\ACB\" />
<Folder Include="Assets\Ratings\PEGI\" />
<Folder Include="wwwroot\scripts\" />
<Folder Include="wwwroot\images\" />
<Folder Include="wwwroot\styles\" />
<Folder Include="wwwroot\pages\" />
<Folder Include="Assets\Ratings\CERO\" />
<Folder Include="Assets\Ratings\USK\" />
<Folder Include="Assets\Ratings\GRAC\" />
<Folder Include="Assets\Ratings\CLASS_IND\" />
<Folder Include="wwwroot\fonts\" />
<Folder Include="wwwroot\pages\dialogs\" />
<Folder Include="wwwroot\pages\settings\" />
</ItemGroup>
<ItemGroup>
<None Include="wwwroot\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\gaseous-tools\gaseous-tools.csproj">
@@ -118,10 +119,6 @@
<GlobalPropertiesToRemove></GlobalPropertiesToRemove>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Remove="Support\PlatformMap.json" />
<Content Remove="wwwroot\pages\settings\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Support\PlatformMap.json" Condition="'$(ExcludeConfigFilesFromBuildOutput)'!='true'">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="249.44 647.1 1461.24 702.85">
<title>IGDB logo</title>
<g id="text_logo">
<path fill="#000000" d=" M 249.44 647.14 C 736.51 647.13 1223.59 647.21 1710.67 647.10 C 1710.68 881.38 1710.69 1115.67 1710.66 1349.95 C 1615.86 1334.68 1520.63 1321.89 1425.05 1312.53 C 1080.16 1278.28 731.31 1284.02 387.73 1329.60 C 341.52 1335.59 295.47 1342.74 249.44 1349.92 C 249.43 1115.66 249.45 881.40 249.44 647.14 M 292.45 690.16 C 292.47 893.35 292.45 1096.55 292.45 1299.74 C 747.32 1230.12 1212.80 1230.06 1667.66 1299.76 C 1667.69 1096.55 1667.67 893.34 1667.67 690.13 C 1209.26 690.20 750.85 690.15 292.45 690.16 Z"/>
<path fill="#000000" d=" M 606.98 849.96 C 636.79 820.94 678.61 805.48 720.03 806.13 C 748.31 806.03 777.18 810.46 802.61 823.37 C 815.72 829.91 827.70 838.49 838.88 847.92 C 824.81 864.91 810.62 881.79 796.71 898.90 C 788.34 892.63 780.20 885.94 770.94 880.98 C 752.35 870.49 730.45 866.86 709.32 868.40 C 683.46 870.61 659.80 886.20 645.79 907.79 C 627.73 934.91 624.92 970.60 635.86 1000.97 C 642.48 1019.42 655.12 1035.79 671.77 1046.24 C 688.33 1056.88 708.44 1061.15 727.96 1059.98 C 747.98 1059.21 768.36 1053.75 784.66 1041.78 C 784.50 1027.38 784.66 1012.97 784.58 998.57 C 762.15 998.63 739.71 998.55 717.28 998.61 C 717.23 979.29 717.40 959.97 717.20 940.66 C 761.32 940.38 805.45 940.76 849.57 940.47 C 849.84 984.58 849.54 1028.70 849.72 1072.81 C 819.35 1099.03 781.02 1116.23 740.99 1120.14 C 701.91 1124.43 660.71 1117.24 627.40 1095.69 C 600.71 1078.68 580.09 1052.59 569.30 1022.87 C 558.65 993.63 556.55 961.41 562.53 930.93 C 568.76 900.29 584.34 871.56 606.98 849.96 Z"/>
<path fill="#000000" d=" M 412.22 811.37 C 434.59 811.42 456.96 811.33 479.33 811.42 C 479.28 912.90 479.33 1014.39 479.31 1115.88 C 456.95 1115.87 434.59 1115.84 412.23 1115.89 C 412.22 1014.39 412.24 912.88 412.22 811.37 Z"/>
<path fill="#000000" d=" M 931.25 811.38 C 972.17 811.39 1013.09 811.38 1054.01 811.39 C 1091.21 811.80 1128.88 823.34 1157.99 846.93 C 1183.44 867.28 1201.49 896.55 1208.37 928.40 C 1216.70 966.86 1211.27 1008.80 1190.62 1042.63 C 1174.23 1069.90 1148.49 1091.13 1119.07 1103.04 C 1098.46 1111.41 1076.23 1115.81 1053.98 1115.87 C 1013.08 1115.87 972.17 1115.85 931.26 1115.88 C 931.26 1014.38 931.28 912.88 931.25 811.38 M 998.55 871.86 C 998.45 933.04 998.56 994.22 998.49 1055.41 C 1011.33 1055.39 1024.18 1055.40 1037.02 1055.40 C 1047.36 1055.32 1057.79 1055.89 1068.03 1053.99 C 1088.10 1050.87 1107.44 1041.22 1120.71 1025.65 C 1132.31 1012.31 1139.05 995.20 1141.08 977.75 C 1142.97 960.03 1141.47 941.67 1134.60 925.11 C 1127.53 907.51 1114.31 892.45 1097.61 883.42 C 1082.53 875.02 1065.11 871.85 1047.99 871.84 C 1031.51 871.88 1015.03 871.84 998.55 871.86 Z"/>
<path fill="#000000" d=" M 1288.80 811.57 C 1330.15 811.13 1371.54 811.51 1412.90 811.38 C 1424.26 811.54 1435.65 810.91 1446.97 812.08 C 1467.08 813.95 1487.46 819.82 1503.55 832.45 C 1517.54 843.24 1527.36 859.35 1530.07 876.86 C 1532.03 893.42 1531.12 911.06 1522.97 925.99 C 1516.04 939.38 1504.10 949.32 1491.26 956.78 C 1508.37 963.22 1525.29 972.72 1535.94 988.04 C 1545.87 1002.19 1548.77 1020.07 1547.80 1037.03 C 1547.40 1054.27 1541.09 1071.56 1529.28 1084.27 C 1517.35 1097.30 1500.95 1105.31 1484.12 1109.96 C 1470.05 1113.75 1455.50 1115.70 1440.94 1115.84 C 1390.26 1115.88 1339.58 1115.89 1288.90 1115.84 C 1288.82 1014.41 1289.01 912.99 1288.80 811.57 M 1354.30 870.27 C 1354.37 891.67 1354.24 913.08 1354.36 934.49 C 1376.56 934.35 1398.76 934.52 1420.96 934.40 C 1431.03 933.86 1441.46 932.43 1450.36 927.35 C 1457.39 923.37 1462.73 916.24 1463.78 908.14 C 1465.26 899.01 1463.69 888.76 1457.02 881.93 C 1449.00 873.67 1437.04 871.03 1425.95 870.37 C 1402.07 870.17 1378.18 870.37 1354.30 870.27 M 1354.29 990.41 C 1354.32 1012.64 1354.35 1034.89 1354.27 1057.13 C 1375.50 1057.33 1396.74 1057.15 1417.98 1057.21 C 1428.58 1057.08 1439.29 1057.80 1449.79 1055.91 C 1458.99 1054.43 1468.48 1050.97 1474.69 1043.71 C 1480.95 1036.44 1482.26 1026.10 1480.69 1016.93 C 1479.44 1009.15 1474.55 1002.16 1467.79 998.15 C 1458.62 992.57 1447.64 990.81 1437.06 990.45 C 1409.47 990.37 1381.88 990.45 1354.29 990.41 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.0656 8.00389L11.25 7.99875H18.75C20.483 7.99875 21.8992 9.3552 21.9949 11.0643L22 11.2487V18.7487C22 20.4818 20.6435 21.898 18.9344 21.9936L18.75 21.9987H11.25C9.51697 21.9987 8.10075 20.6423 8.00515 18.9332L8 18.7487V11.2487C8 9.51571 9.35646 8.0995 11.0656 8.00389ZM18.75 9.49875H11.25C10.3318 9.49875 9.57881 10.2059 9.5058 11.1052L9.5 11.2487V18.7487C9.5 19.6669 10.2071 20.4199 11.1065 20.4929L11.25 20.4987H18.75C19.6682 20.4987 20.4212 19.7916 20.4942 18.8923L20.5 18.7487V11.2487C20.5 10.2822 19.7165 9.49875 18.75 9.49875ZM15 11C15.4142 11 15.75 11.3358 15.75 11.75V14.248L18.25 14.2487C18.6642 14.2487 19 14.5845 19 14.9987C19 15.413 18.6642 15.7487 18.25 15.7487L15.75 15.748V18.25C15.75 18.6642 15.4142 19 15 19C14.5858 19 14.25 18.6642 14.25 18.25V15.748L11.75 15.7487C11.3358 15.7487 11 15.413 11 14.9987C11 14.5845 11.3358 14.2487 11.75 14.2487L14.25 14.248V11.75C14.25 11.3358 14.5858 11 15 11ZM15.5818 4.23284L15.6345 4.40964L16.327 6.998H14.774L14.1856 4.79787C13.9355 3.86431 12.9759 3.31029 12.0423 3.56044L4.79787 5.50158C3.91344 5.73856 3.36966 6.61227 3.52756 7.49737L3.56044 7.64488L5.50158 14.8893C5.69372 15.6064 6.30445 16.0996 7.00045 16.1764L7.00056 17.6816C5.69932 17.6051 4.52962 16.7445 4.10539 15.4544L4.05269 15.2776L2.11155 8.03311C1.66301 6.35913 2.6067 4.6401 4.23284 4.10539L4.40964 4.05269L11.6541 2.11155C13.3281 1.66301 15.0471 2.6067 15.5818 4.23284Z" fill="#212121"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M960 160h-291.2a160 160 0 0 0-313.6 0H64a32 32 0 0 0 0 64h896a32 32 0 0 0 0-64zM512 96a96 96 0 0 1 90.24 64h-180.48A96 96 0 0 1 512 96zM844.16 290.56a32 32 0 0 0-34.88 6.72A32 32 0 0 0 800 320a32 32 0 1 0 64 0 33.6 33.6 0 0 0-9.28-22.72 32 32 0 0 0-10.56-6.72zM832 416a32 32 0 0 0-32 32v96a32 32 0 0 0 64 0v-96a32 32 0 0 0-32-32zM832 640a32 32 0 0 0-32 32v224a32 32 0 0 1-32 32H256a32 32 0 0 1-32-32V320a32 32 0 0 0-64 0v576a96 96 0 0 0 96 96h512a96 96 0 0 0 96-96v-224a32 32 0 0 0-32-32z" fill="#231815" /><path d="M384 768V352a32 32 0 0 0-64 0v416a32 32 0 0 0 64 0zM544 768V352a32 32 0 0 0-64 0v416a32 32 0 0 0 64 0zM704 768V352a32 32 0 0 0-64 0v416a32 32 0 0 0 64 0z" fill="#231815" /></svg>

After

Width:  |  Height:  |  Size: 946 B

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M24 12a5 5 0 0 1-5 5h-2v-1h2a3.99 3.99 0 0 0 .623-7.934l-.79-.124-.052-.798a5.293 5.293 0 0 0-10.214-1.57L8.17 6.59l-.977-.483A2.277 2.277 0 0 0 6.19 5.87a2.18 2.18 0 0 0-1.167.339 2.206 2.206 0 0 0-.98 1.395l-.113.505-.476.2A4 4 0 0 0 5 16h3v1H5a5 5 0 0 1-1.934-9.611 3.21 3.21 0 0 1 1.422-2.025 3.17 3.17 0 0 1 1.702-.493 3.268 3.268 0 0 1 1.446.34 6.293 6.293 0 0 1 12.143 1.867A4.988 4.988 0 0 1 24 12zm-11-1h-1v10.292l-2.646-2.646-.707.707 3.854 3.854 3.853-3.852-.707-.707L13 21.294z"/><path fill="none" d="M0 0h24v24H0z"/></svg>

After

Width:  |  Height:  |  Size: 752 B

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.2799 6.40005L11.7399 15.94C10.7899 16.89 7.96987 17.33 7.33987 16.7C6.70987 16.07 7.13987 13.25 8.08987 12.3L17.6399 2.75002C17.8754 2.49308 18.1605 2.28654 18.4781 2.14284C18.7956 1.99914 19.139 1.92124 19.4875 1.9139C19.8359 1.90657 20.1823 1.96991 20.5056 2.10012C20.8289 2.23033 21.1225 2.42473 21.3686 2.67153C21.6147 2.91833 21.8083 3.21243 21.9376 3.53609C22.0669 3.85976 22.1294 4.20626 22.1211 4.55471C22.1128 4.90316 22.0339 5.24635 21.8894 5.5635C21.7448 5.88065 21.5375 6.16524 21.2799 6.40005V6.40005Z" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 4H6C4.93913 4 3.92178 4.42142 3.17163 5.17157C2.42149 5.92172 2 6.93913 2 8V18C2 19.0609 2.42149 20.0783 3.17163 20.8284C3.92178 21.5786 4.93913 22 6 22H17C19.21 22 20 20.2 20 18V13" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>library</title>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Combined-Shape" fill="#000000" transform="translate(42.666667, 85.333333)">
<path d="M3.55271368e-14,298.666667 L426.666667,298.666667 L426.666667,341.333333 L3.55271368e-14,341.333333 L3.55271368e-14,298.666667 Z M42.6666667,1.42108547e-14 L106.666667,1.42108547e-14 L106.666667,277.333333 L42.6666667,277.333333 L42.6666667,1.42108547e-14 Z M128,21.3333333 L192,21.3333333 L192,277.333333 L128,277.333333 L128,21.3333333 Z M288.933802,36.9522088 L351.961498,25.8387255 L399.909944,277.333333 L330.641827,277.70319 L288.933802,36.9522088 Z M213.333333,21.3333333 L277.333333,21.3333333 L277.333333,277.333333 L213.333333,277.333333 L213.333333,21.3333333 Z">
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 3V8M3 8H8M3 8L6 5.29168C7.59227 3.86656 9.69494 3 12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.71683 21 4.13247 18.008 3.22302 14" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -2,11 +2,14 @@
<html>
<head>
<meta charset="utf-8" />
<link type="text/css" rel="stylesheet" href="/styles/style.css" />
<script src="/api/v1/System/VersionFile"></script>
<link type="text/css" rel="stylesheet" dat-href="/styles/style.css" />
<script src="/scripts/jquery-3.6.0.min.js"></script>
<script src="/scripts/moment.js"></script>
<link href="/styles/select2.min.css" rel="stylesheet" />
<link rel="stylesheet" href="/styles/dropzone.min.css" type="text/css" />
<link href="/styles/dropzone.min.css" rel="stylesheet" type="text/css" />
<script src="/scripts/jquery.lazy.min.js"></script>
<script src="/scripts/jquery.lazy.plugins.min.js"></script>
<script src="/scripts/select2.min.js"></script>
<script src="/scripts/dropzone.min.js"></script>
<script src="/scripts/main.js" type="text/javascript"></script>
@@ -17,21 +20,53 @@
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<title>Gaseous Games</title>
<script type="text/javascript">
var head = document.getElementsByTagName('head')[0];
// update links
var headLinks = document.getElementsByTagName('link');
for (var i = 0; i < headLinks.length; i++) {
if (headLinks[i].getAttribute('dat-href') && headLinks[i].rel == "stylesheet") {
var newLink = document.createElement('link');
newLink.rel = "stylesheet";
newLink.href = headLinks[i].getAttribute('dat-href') + '?v=' + AppVersion;
newLink.type = "text/css";
headLinks[i].parentElement.removeChild(headLinks[i]);
head.appendChild(newLink);
}
}
</script>
</head>
<body>
<div id="banner_icon" onclick="window.location.href = '/index.html';">
<img src="/images/logo.png" alt="Gaseous" id="banner_icon_image" />
</div>
<div id="banner_header">
<div id="banner_header_label">Gaseous Games</div>
</div>
<div id="banner_upload" onclick="showDialog('upload');">
<img src="/images/upload.svg" alt="Upload" title="Upload" id="banner_upload_image" />
<span id="banner_upload_label">Upload Games</span>
</div>
<div id="banner_cog" onclick="window.location.href = '/index.html?page=settings';">
<img src="/images/settings.svg" alt="Settings" title="Settings" id="banner_system_image" />
<div id="banner_cog" onclick="window.location.href = '/index.html?page=settings';" class="banner_button">
<img src="/images/settings.svg" alt="Settings" title="Settings" id="banner_system_image" class="banner_button_image" />
<span id="banner_system_label">Settings</span>
</div>
<div id="banner_upload" onclick="showDialog('upload');" class="banner_button">
<img src="/images/upload.svg" alt="Upload" title="Upload" id="banner_upload_image" class="banner_button_image" />
<span id="banner_upload_label">Upload</span>
</div>
<div id="banner_collections" onclick="window.location.href = '/index.html?page=collections';" class="banner_button">
<img src="/images/collections.svg" alt="Collections" title="Collections" id="banner_collections_image" class="banner_button_image" />
<span id="banner_collections_label">Collections</span>
</div>
<div id="banner_library" onclick="window.location.href = '/index.html';" class="banner_button">
<img src="/images/library.svg" alt="Library" title="Library" id="banner_library_image" class="banner_button_image" />
<span id="banner_library_label">Library</span>
</div>
<div id="banner_header_label" onclick="window.location.href = '/index.html';">Gaseous Games</div>
</div>
<div id="content">
@@ -49,17 +84,29 @@
</div>
<!-- The Modal -->
<div id="myModalSub" class="modal">
<!-- Modal content -->
<div class="modal-content-sub">
<span id="modal-close-sub" class="close">&times;</span>
<div id="modal-content-sub">Some text in the Modal..</div>
</div>
</div>
<script type="text/javascript">var modalVariables = null;
$(document).ready(function () {
const urlParams = new URLSearchParams(window.location.search);
var myParam = urlParams.get('page');
var myParam = getQueryString('page', 'string');
if (!myParam) {
myParam = 'home';
}
$('#content').load('/pages/' + myParam + '.html');
});</script>
$('#content').load('/pages/' + myParam + '.html?v=' + AppVersion);
});
</script>
</body>
</html>

View File

@@ -6,7 +6,7 @@
EJS_player = '#game';
// Can also be fceumm or nestopia
EJS_core = urlParams.get('core');
EJS_core = getQueryString('core', 'string');
// Lightgun
EJS_lightgun = false; // can be true or false
@@ -15,7 +15,7 @@
EJS_biosUrl = emuBios;
// URL to Game rom
EJS_gameUrl = decodeURIComponent(urlParams.get('rompath'));
EJS_gameUrl = decodeURIComponent(getQueryString('rompath', 'string'));
// Path to the data directory
EJS_pathtodata = '/emulators/EmulatorJS/data/';
@@ -23,5 +23,8 @@
EJS_DEBUG_XX = false;
EJS_startOnLoaded = false;
EJS_backgroundImage = emuBackground;
EJS_backgroundBlur = true;
</script>
<script src='/emulators/EmulatorJS/data/loader.js'></script>

View File

@@ -0,0 +1,80 @@
<div id="bgImage" style="background-image: url('/images/CollectionsWallpaper.jpg'); background-position: center; background-repeat: no-repeat; background-size: cover; filter: blur(10px); -webkit-filter: blur(10px);">
<div id="bgImage_Opacity"></div>
</div>
<div id="gamepage">
<div id="gametitle">
<h1 id="gametitle_label">Collections</h1>
</div>
<button id="collection_new" style="float: right;" onclick="showDialog('collectionedit');">New Collection</button>
<div id="collection_table_location">
</div>
</div>
<div id="settings_photocredit">
Wallpaper by <a href="https://wallpapercave.com/u/andrea16" class="romlink">andrea16</a> / <a href="https://wallpapercave.com/w/wp5206111" class="romlink">Wallpaper Cave</a>
</div>
<script type="text/javascript">
GetCollections();
function GetCollections() {
ajaxCall('/api/v1/Collections', 'GET', function (result) {
if (result) {
var targetDiv = document.getElementById('collection_table_location');
targetDiv.innerHTML = '';
var newTable = document.createElement('table');
newTable.id = 'romtable';
newTable.className = 'romtable';
newTable.setAttribute('cellspacing', 0);
newTable.appendChild(createTableRow(true, [ 'Name', 'Description', 'Download Status', 'Size', '' ]));
for (var i = 0; i < result.length; i++) {
var statusText = result[i].buildStatus;
var downloadLink = '';
var packageSize = '-';
switch (result[i].buildStatus) {
case 'NoStatus':
statusText = '-';
break;
case "WaitingForBuild":
statusText = 'Build pending';
break;
case "Building":
statusText = 'Building';
break;
case "Completed":
statusText = 'Available';
downloadLink = '<a href="/api/v1/Collections/' + result[i].id + '/Roms/Zip" class="romlink"><img src="/images/download.svg" class="banner_button_image" alt="Download" title="Download" /></a>';
packageSize = formatBytes(result[i].collectionBuiltSizeBytes);
break;
case "Failed":
statusText = 'Build error';
break;
default:
statusText = result[i].buildStatus;
break;
}
var editButton = '<a href="#" onclick="showDialog(\'collectionedit\', ' + result[i].id + ');" class="romlink"><img src="/images/edit.svg" class="banner_button_image" alt="Edit" title="Edit" /></a>';
var deleteButton = '<a href="#" onclick="showSubDialog(\'collectiondelete\', ' + result[i].id + ');" class="romlink"><img src="/images/delete.svg" class="banner_button_image" alt="Delete" title="Delete" /></a>';
var newRow = [
result[i].name,
result[i].description,
statusText,
packageSize,
'<div style="text-align: right;">' + downloadLink + editButton + deleteButton + '</div>'
];
newTable.appendChild(createTableRow(false, newRow, 'romrow', 'romcell'));
}
targetDiv.appendChild(newTable);
}
});
}
</script>

View File

@@ -0,0 +1,27 @@
<p>Are you sure you want to delete this collection?</p>
<p><strong>Warning:</strong> This cannot be undone!</p>
<div style="width: 100%; text-align: center;">
<div style="display: inline-block; margin-right: 20px;">
<button class="redbutton" value="Delete" onclick="deleteCollection();">Delete</button>
</div>
<div style="display: inline-block; margin-left: 20px;">
<button value="Cancel" onclick="closeSubDialog();">Cancel</button>
</div>
</div>
<script type="text/javascript">
function deleteCollection() {
ajaxCall(
'/api/v1/Collections/' + subModalVariables,
'DELETE',
function (result) {
GetCollections();
closeSubDialog();
},
function (error) {
GetCollections();
closeSubDialog();
}
);
}
</script>

View File

@@ -0,0 +1,494 @@
<input id="collection_name" type="text" placeholder="Collection Name" style="width: 80%; font-size: 2em;" />
<input id="collection_description" type="text" placeholder="Description" style="width: 80%; margin-top: 5px;" />
<div style="position: absolute; top: 90px; right: 5px; left: 10px; bottom: 5px;">
<div style="position: relative; width: 100%; height: 100%;">
<table style="position: absolute; top: 0px; left: 0px; bottom: 0px; width: 40%;">
<tr>
<td>
<h3>Filter</h3>
</td>
</tr>
</table>
<div id="collection_filter_box" style="position: absolute; top: 40px; left: 0px; bottom: 5px; width: 40%; max-width: 40%; overflow-x: scroll;">
<table style="width: 100%;">
<tr>
<th style="width: 25%;">Platforms</th>
<td><select id="collection_platform" style="width: 100%;" multiple="multiple"></select></td>
</tr>
<tr>
<th>Genres</th>
<td><select id="collection_genres" style="width: 100%;" multiple="multiple"></select></td>
</tr>
<tr>
<th>Players</th>
<td><select id="collection_players" style="width: 100%;" multiple="multiple"></select></td>
</tr>
<tr>
<th>Player Perspectives</th>
<td><select id="collection_playerperspectives" style="width: 100%;" multiple="multiple"></select></td>
</tr>
<tr>
<th>Themes</th>
<td><select id="collection_themes" style="width: 100%;" multiple="multiple"></select></td>
</tr>
<tr>
<th>Rating</th>
<td>
<input id="collection_userrating_min" type="number" placeholder="0" min="0" max="100">
<input id="collection_userrating_max" type="number" placeholder="100" min="0" max="100">
</td>
</tr>
<tr>
<td colspan="2">
<h3>Options</h3>
</td>
</tr>
<tr>
<th>Maximum ROMs per platform</th>
<td><input id="collection_maxroms" type="number" placeholder="0"></td>
</tr>
<tr>
<th>Maximum size per platform (bytes)</th>
<td><input id="collection_maxplatformsize" type="number" placeholder="0" step="1048576" oninput="DisplayFormattedBytes('collection_maxplatformsize', 'maxplatformsize_label');"><span id="maxplatformsize_label" style="margin-left: 10px;"></span></td>
</tr>
<tr>
<th>Maximum collection size (bytes)</th>
<td><input id="collection_maxcollectionsize" type="number" placeholder="0" step="1048576" oninput="DisplayFormattedBytes('collection_maxcollectionsize', 'maxcollectionsize_label');"><span id="maxcollectionsize_label" style="margin-left: 10px;"></span></td></td>
</tr>
</table>
</div>
<table style="position: absolute; top: 0px; right: 0px; bottom: 0px; width: 60%;">
<tr>
<td>
<h3>Collection</h3>
</td>
</tr>
</table>
<div id="collectionedit_previewbox" style="position: absolute; top: 40px; right: 0px; bottom: 60px; width: 60%; overflow-x: scroll;">
<div id="collectionedit_previewbox_content" style="margin: 5px;">
</div>
</div>
<div id="collectionedit_previewbox_size" style="position: absolute; right: 10px; bottom: 30px; width: 60%; height: 20px; text-align: right;">
</div>
<div id="collectionedit_previewbox_controls" style="position: absolute; right: 10px; bottom: 0px; width: 60%; height: 25px; text-align: right;">
<button id="collectionedit_preview" onclick="GetPreview();">Preview</button>
<button id="collectionedit_cancel" onclick="closeDialog();">Cancel</button>
<button id="collectionedit_ok" onclick="SaveCollection();">Ok</button>
</div>
</div>
</div>
<script type="text/javascript">
var headerToRemove = document.getElementById('modal-heading');
if (headerToRemove) {
headerToRemove.parentNode.removeChild(headerToRemove);
}
var modalContent = document.getElementsByClassName('modal-content');
if (!modalContent[0].classList.contains('collections_modal')) {
modalContent[0].classList.add('collections_modal');
}
// setup dropdowns
$('#collection_platform').select2({
ajax: {
url: '/api/v1/Filter',
processResults: function (data) {
var filter = data['platforms'];
var arr = [];
for (var i = 0; i < filter.length; i++) {
arr.push({
id: filter[i].id,
text: filter[i].name
});
}
return {
results: arr
};
}
}
});
$('#collection_genres').select2({
ajax: {
url: '/api/v1/Filter',
processResults: function (data) {
var filter = data['genres'];
var arr = [];
for (var i = 0; i < filter.length; i++) {
arr.push({
id: filter[i].id,
text: filter[i].name
});
}
return {
results: arr
};
}
}
});
$('#collection_players').select2({
ajax: {
url: '/api/v1/Filter',
processResults: function (data) {
var filter = data['gamemodes'];
var arr = [];
for (var i = 0; i < filter.length; i++) {
arr.push({
id: filter[i].id,
text: filter[i].name
});
}
return {
results: arr
};
}
}
});
$('#collection_playerperspectives').select2({
ajax: {
url: '/api/v1/Filter',
processResults: function (data) {
var filter = data['playerperspectives'];
var arr = [];
for (var i = 0; i < filter.length; i++) {
arr.push({
id: filter[i].id,
text: filter[i].name
});
}
return {
results: arr
};
}
}
});
$('#collection_themes').select2({
ajax: {
url: '/api/v1/Filter',
processResults: function (data) {
var filter = data['themes'];
var arr = [];
for (var i = 0; i < filter.length; i++) {
arr.push({
id: filter[i].id,
text: filter[i].name
});
}
return {
results: arr
};
}
}
});
if (modalVariables) {
// edit mode
ajaxCall(
'/api/v1/Collections/' + modalVariables,
'GET',
function(result) {
if (result.name) { document.getElementById('collection_name').value = result.name; }
if (result.description) { document.getElementById('collection_description').value = result.description; }
if (result.minimumRating != -1) { document.getElementById('collection_userrating_min').value = result.minimumRating; }
if (result.maximumRating != -1) { document.getElementById('collection_userrating_max').value = result.maximumRating; }
if (result.maximumRomsPerPlatform != -1) { document.getElementById('collection_maxroms').value = result.maximumRomsPerPlatform; }
if (result.maximumBytesPerPlatform != -1) { document.getElementById('collection_maxplatformsize').value = result.maximumBytesPerPlatform; }
if (result.maximumCollectionSizeInBytes != -1) { document.getElementById('collection_maxcollectionsize').value = result.maximumCollectionSizeInBytes; }
// fill select2 controls
$.ajax(
{
url: '/api/v1/Filter',
type: 'GET',
indexValue: result,
dataType: 'json',
contentType: 'application/json',
success: function (data) {
// platforms
for (var i = 0; i < data.platforms.length; i++) {
if (this.indexValue.platforms.includes(data.platforms[i].id)) {
var newOption = new Option(data.platforms[i].name, data.platforms[i].id, true, true);
$('#collection_platform').append(newOption).trigger('change');
}
}
// genres
for (var i = 0; i < data.genres.length; i++) {
if (this.indexValue.genres.includes(data.genres[i].id)) {
var newOption = new Option(data.genres[i].name, data.genres[i].id, true, true);
$('#collection_genres').append(newOption).trigger('change');
}
}
// players
for (var i = 0; i < data.gamemodes.length; i++) {
if (this.indexValue.players.includes(data.gamemodes[i].id)) {
var newOption = new Option(data.gamemodes[i].name, data.gamemodes[i].id, true, true);
$('#collection_players').append(newOption).trigger('change');
}
}
// playerperspectives
for (var i = 0; i < data.playerperspectives.length; i++) {
if (this.indexValue.playerPerspectives.includes(data.playerperspectives[i].id)) {
var newOption = new Option(data.playerperspectives[i].name, data.playerperspectives[i].id, true, true);
$('#collection_playerperspectives').append(newOption).trigger('change');
}
}
// themes
for (var i = 0; i < data.themes.length; i++) {
if (this.indexValue.themes.includes(data.themes[i].id)) {
var newOption = new Option(data.themes[i].name, data.themes[i].id, true, true);
$('#collection_themes').append(newOption).trigger('change');
}
}
// generate preview
GetPreview();
},
error: function (error) {
console.log(`Error ${error}`);
}
}
);
}
);
} else {
// new mode
}
function SaveCollection() {
var item = GenerateCollectionItem();
if (modalVariables) {
// existing object - save over the top
item.id = modalVariables;
ajaxCall(
'/api/v1/Collections/' + modalVariables,
'PATCH',
function(result) {
location.reload();
},
function(error) {
alert(error);
},
JSON.stringify(item)
);
} else {
// new object
ajaxCall(
'/api/v1/Collections',
'POST',
function(result) {
location.reload();
},
function(error) {
alert(error);
},
JSON.stringify(item)
);
}
}
function GenerateCollectionItem() {
var platforms = GetDropDownIds('#collection_platform');
var genres = GetDropDownIds('#collection_genres');
var players = GetDropDownIds('#collection_players');
var playerperspectives = GetDropDownIds('#collection_playerperspectives');
var themes = GetDropDownIds('#collection_themes');
var item = {
"name": document.getElementById('collection_name').value,
"description": document.getElementById('collection_description').value,
"platforms": platforms,
"genres": genres,
"players": players,
"playerPerspectives": playerperspectives,
"themes": themes,
"minimumRating": GetNumberFieldValue('collection_userrating_min'),
"maximumRating": GetNumberFieldValue('collection_userrating_max'),
"maximumRomsPerPlatform": GetNumberFieldValue('collection_maxroms'),
"maximumBytesPerPlatform": GetNumberFieldValue('collection_maxplatformsize'),
"maximumCollectionSizeInBytes": GetNumberFieldValue('collection_maxcollectionsize')
}
return item;
}
function GetPreview() {
var item = GenerateCollectionItem();
console.log(JSON.stringify(item));
ajaxCall(
'/api/v1/Collections/Preview',
'POST',
function(result) {
DisplayPreview(result, 'collectionedit_previewbox_content');
},
function(error) {
console.log(JSON.stringify(error));
},
JSON.stringify(item)
);
}
function GetDropDownIds(objectName) {
var obj = $(objectName).select2('data');
if (obj.length > 0) {
var ids = [];
for (var i = 0; i < obj.length; i++) {
ids.push(obj[i].id);
}
return ids;
} else {
return [];
}
}
function GetNumberFieldValue(objectName) {
var obj = document.getElementById(objectName);
var objVal = obj.value;
if (objVal) {
return objVal;
} else {
return -1;
}
}
function DisplayPreview(data, targetDiv) {
var container = document.getElementById(targetDiv);
container.innerHTML = '';
if (data.collection) {
var collectionTable = document.createElement('table');
collectionTable.setAttribute('cellspacing', 0);
collectionTable.style.width = '100%';
// loop the platforms
for (var p = 0; p < data.collection.length; p++) {
var platformItem = data.collection[p];
var platformLabelRow = document.createElement('tr');
var platformLabelCell = document.createElement('th');
platformLabelCell.setAttribute('colspan', 2);
platformLabelCell.className = 'collections_preview_platform_header';
platformLabelCell.innerHTML = '<span style="float: right;">' + formatBytes(platformItem.romSize) + '</span>' + platformItem.name;
platformLabelRow.appendChild(platformLabelCell);
collectionTable.appendChild(platformLabelRow);
var bgaltindex = 0;
// loop the games
for (var g = 0; g < platformItem.games.length; g++) {
var gameItem = platformItem.games[g];
var bgalt = '';
if (bgaltindex == 1) {
bgaltindex = 0;
bgalt = 'bgalt1';
} else {
bgaltindex = 1;
bgalt = 'bgalt0';
}
var gameItemRow = document.createElement('tr');
gameItemRow.className = bgalt;
// game title
var gameTitleCell = document.createElement('th');
gameTitleCell.setAttribute('colspan', 2);
gameTitleCell.className = 'collections_preview_gametitlecell';
gameTitleCell.innerHTML = gameItem.name;
gameItemRow.appendChild(gameTitleCell);
// game cover
var gameDetailRow = document.createElement('tr');
gameDetailRow.className = bgalt;
var gameCoverCell = document.createElement('td');
gameCoverCell.className = 'collections_preview_gamecovercell';
var gameImage = document.createElement('img');
gameImage.className = 'game_tile_image game_tile_image_small';
if (gameItem.cover) {
gameImage.src = '/api/v1/Games/' + gameItem.id + '/cover/image';
} else {
gameImage.src = '/images/unknowngame.png';
gameImage.className = 'game_tile_image game_tile_image_small unknown';
}
gameCoverCell.appendChild(gameImage);
gameDetailRow.appendChild(gameCoverCell);
// game detail
var gameDetailCell = document.createElement('td');
gameDetailCell.className = 'collections_preview_gamedetailcell';
// loop roms
for (var r = 0; r < gameItem.roms.length; r++) {
var romItem = gameItem.roms[r];
var romLabel = document.createElement('p');
romLabel.innerHTML = romItem.name;
gameDetailCell.appendChild(romLabel);
}
gameDetailRow.appendChild(gameDetailCell);
collectionTable.appendChild(gameItemRow);
collectionTable.appendChild(gameDetailRow);
}
}
container.appendChild(collectionTable);
var collectionSize = document.getElementById('collectionedit_previewbox_size');
collectionSize.innerHTML = "Collection size: " + formatBytes(data.collectionProjectedSizeBytes);
}
}
function DisplayFormattedBytes(inputElement, labelElement) {
var src = document.getElementById(inputElement);
var label = document.getElementById(labelElement);
if (src.value) {
label.innerHTML = formatBytes(src.value);
} else {
label.innerHTML = '';
}
}
</script>

View File

@@ -1,15 +1,4 @@
<!-- The Modal -->
<div id="myModalSub" class="modal">
<!-- Modal content -->
<div class="modal-content-sub">
<span id="modal-close-sub" class="close">&times;</span>
<div id="modal-content-sub">Some text in the Modal..</div>
</div>
</div>
<div id="properties_toc">
<div id="properties_toc">
<div id="properties_toc_general" name="properties_toc_item" onclick="SelectTab('general');">General</div>
<div id="properties_toc_match" name="properties_toc_item" onclick="SelectTab('match');">Title Match</div>
<!--<div id="properties_toc_manage" name="properties_toc_item" onclick="SelectTab('manage');">Manage</div>-->
@@ -88,7 +77,7 @@
<script type="text/javascript">
document.getElementById('modal-heading').innerHTML = "Properties";
var gameId = urlParams.get('id');
var gameId = getQueryString('id', 'int');
var romData;
@@ -222,27 +211,6 @@
}
});
function DropDownRenderGameOption(state) {
console.log(JSON.stringify(state));
if (state.loading) {
return state;
}
var response;
if (state.cover) {
response = $(
'<table class="dropdown-div"><tr><td class="dropdown-cover"><img src="https://images.igdb.com/igdb/image/upload/t_cover_small/' + state.cover.value.imageId + '.jpg" /></td><td class="dropdown-label"><span>' + state.text + '</span></td></tr></table>'
);
} else {
response = $(
'<table class="dropdown-div"><tr><td class="dropdown-cover"><img src="/images/unknowngame.png" /></td><td class="dropdown-label"><span>' + state.text + '</span></td></tr></table>'
);
}
return response;
}
function SaveFixedGame() {
var fixplatform = $('#properties_fixplatform').select2('data');
var fixgame = $('#properties_fixgame').select2('data');
@@ -254,48 +222,6 @@
});
}
var subModalVariables;
function showSubDialog(dialogPage, variables) {
// Get the modal
var submodal = document.getElementById("myModalSub");
// Get the modal content
var subModalContent = document.getElementById("modal-content-sub");
// Get the button that opens the modal
var subbtn = document.getElementById("romDelete");
// Get the <span> element that closes the modal
var subspan = document.getElementById("modal-close-sub");
// When the user clicks on the button, open the modal
submodal.style.display = "block";
// When the user clicks on <span> (x), close the modal
subspan.onclick = function () {
submodal.style.display = "none";
subModalContent.innerHTML = "";
subModalVariables = null;
}
subModalVariables = modalVariables;
$('#modal-content-sub').load('/pages/dialogs/' + dialogPage + '.html');
}
function closeSubDialog() {
// Get the modal
var submodal = document.getElementById("myModalSub");
// Get the modal content
var subModalContent = document.getElementById("modal-content-sub");
submodal.style.display = "none";
subModalContent.innerHTML = "";
subModalVariables = null;
}
SelectTab('general');
document.getElementById('romDelete').setAttribute("onclick", "showSubDialog('romdelete', " + modalVariables + ");");

View File

@@ -0,0 +1,10 @@
<p>Are you sure you want to delete the selected ROMs?</p>
<p><strong>Warning:</strong> This cannot be undone!</p>
<div style="width: 100%; text-align: center;">
<div style="display: inline-block; margin-right: 20px;">
<button class="redbutton" value="Delete" onclick="deleteGameRomsCallback(); closeSubDialog();">Delete</button>
</div>
<div style="display: inline-block; margin-left: 20px;">
<button value="Cancel" onclick="closeSubDialog();">Cancel</button>
</div>
</div>

View File

@@ -55,7 +55,7 @@
subModalVariables = modalVariables;
$('#modal-content-sub').load('/pages/dialogs/' + dialogPage + '.html');
$('#modal-content-sub').load('/pages/dialogs/' + dialogPage + '.html?v=' + AppVersion);
}
function closeSubDialog() {

View File

@@ -4,26 +4,15 @@
<div id="emulator"></div>
<div id="emulatorbios">
<table style="width: 100%;">
<tr>
<td>Firmware:</td>
<td><select id="emulatorbiosselector" onchange="loadEmulator();"></select></td>
</tr>
</table>
</div>
<script type="text/javascript">
const urlParams = new URLSearchParams(window.location.search);
var gameId = urlParams.get('gameid');
var platformId = urlParams.get('platformid');
var gameId = getQueryString('gameid', 'int');
var platformId = getQueryString('platformid', 'int');
var gameData;
var artworks = null;
var artworksPosition = 0;
var emuBios = '';
var availableEmuBios = [];
var emuBackground = '';
ajaxCall('/api/v1/Games/' + gameId, 'GET', function (result) {
gameData = result;
@@ -40,36 +29,24 @@
bg.setAttribute('style', 'background-image: url("/api/v1/Games/' + gameId + '/cover/image"); background-position: center; background-repeat: no-repeat; background-size: cover; filter: blur(10px); -webkit-filter: blur(10px);');
}
}
if (result.cover) {
emuBackground = '/api/v1/Games/' + gameId + '/cover/image';
}
});
ajaxCall('/api/v1/Bios/' + platformId, 'GET', function (result) {
var emulatorbiosDiv = document.getElementById('emulatorbios');
availableEmuBios = result;
if (result.length == 0) {
emuBios = '';
emulatorbiosDiv.setAttribute('style', 'display: none;');
} else {
emuBios = '/api/v1/Bios/' + platformId + '/' + availableEmuBios[0].filename;
var emulatorbiosselect = document.getElementById('emulatorbiosselector');
for (var i = 0; i < availableEmuBios.length; i++) {
var biosOption = document.createElement('option');
biosOption.value = availableEmuBios[i].filename;
biosOption.innerHTML = availableEmuBios[i].description + ' (' + availableEmuBios[i].filename + ')';
if (availableEmuBios[i].region == "US") {
emuBios = '/api/v1/Bios/' + platformId + '/' + availableEmuBios[i].filename;
biosOption.setAttribute('selected', 'selected');
}
emulatorbiosselect.appendChild(biosOption);
}
emuBios = '/api/v1/Bios/zip/' + platformId;
}
loadEmulator();
switch (getQueryString('engine', 'string')) {
case 'EmulatorJS':
$('#emulator').load('/pages/EmulatorJS.html?v=' + AppVersion);
break;
}
});
function rotateBackground() {
@@ -82,19 +59,4 @@
bg.setAttribute('style', 'background-image: url("/api/v1/Games/' + gameId + '/artwork/' + artworks[artworksPosition] + '/image"); background-position: center; background-repeat: no-repeat; background-size: cover; filter: blur(10px); -webkit-filter: blur(10px);');
}
}
function loadEmulator() {
if (availableEmuBios.length > 0) {
var emulatorbiosselect = document.getElementById('emulatorbiosselector');
emuBios = '/api/v1/Bios/' + platformId + '/' + emulatorbiosselect.value;
} else {
emuBios = '';
}
switch (urlParams.get('engine')) {
case 'EmulatorJS':
$('#emulator').load('/pages/EmulatorJS.html');
break;
}
}
</script>

View File

@@ -2,13 +2,34 @@
<div id="bgImage_Opacity"></div>
</div>
<!-- The Modal -->
<div id="myModalProgress" class="modal">
<!-- Modal content -->
<div class="modal-content-sub">
<div id="modal-content-sub-progress">
<h1>In Progress...</h1>
<progress style="width: 100%;"></progress>
</div>
</div>
</div>
<div id="gamepage">
<div id="gametitle">
<div id="gametitle_criticrating">
<div>
<span id="gametitle_criticrating_value"></span>
<span id="gametitle_criticrating_label"></span>
</div>
</div>
<h1 id="gametitle_label"></h1>
<p id="gamedeveloper_label"></p>
<p id="gametitle_alts">
<span>Also known as: </span><span id="gametitle_alts_label"></span>
</p>
<p id="gamedeveloper_label"></p>
</div>
@@ -46,15 +67,23 @@
<p id="gamesummarytext_label_button_contract" class="text_link" style="display: none;" onclick="document.querySelector('#gamesummarytext_label').classList.add('line-clamp-4'); document.querySelector('#gamesummarytext_label_button_expand').setAttribute('style', ''); document.querySelector('#gamesummarytext_label_button_contract').setAttribute('style', 'display: none;');">Read less...</p>
</div>
<div id="gamesummaryroms">
<span id="rom_edit" class="romlink" onclick="DisplayROMCheckboxes(true);">Edit</span>
<h3>ROM's/Images</h3>
<div id="rom_edit_panel" style="display: none;">
<div id="rom_edit_panel_center">
<button id="rom_edit_delete" class="redbutton" onclick="deleteGameRoms();">Delete</button>
<select id="rom_edit_fixplatform" style="width: 150px;"></select>
<select id="rom_edit_fixgame" style="width: 300px;"></select>
<button id="rom_edit_update" onclick="remapTitles();">Update</button>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
const urlParams = new URLSearchParams(window.location.search);
var gameId = urlParams.get('id');
var gameId = getQueryString('id', 'int');
var gameData;
var artworks = null;
var artworksPosition = 0;
@@ -69,6 +98,17 @@
var gameTitleLabel = document.getElementById('gametitle_label');
gameTitleLabel.innerHTML = result.name;
// get critic rating
if (gameData.totalRating) {
var criticscoreval = document.getElementById('gametitle_criticrating_value');
criticscoreval.innerHTML = Math.floor(gameData.totalRating) + '%';
if (gameData.totalRatingCount) {
var criticscorelabel = document.getElementById('gametitle_criticrating_label');
criticscorelabel.innerHTML = '<img src="/images/IGDB_logo.svg" style="filter: invert(100%); height: 13px; margin-bottom: -5px;" /><span style="font-size: 10px;"> User Rating<br />' + "based on " + gameData.totalRatingCount + " votes</span>"
}
}
// get alt name
var gameTitleAltLabel = document.getElementById('gametitle_alts');
if (result.alternativeNames) {
@@ -95,7 +135,7 @@
} else {
gameSummaryLabel.innerHTML = result.storyline;
}
if (gameSummaryLabel.offsetHeight < gameSummaryLabel.scrollHeight ||
gameSummaryLabel.offsetWidth < gameSummaryLabel.scrollWidth) {
// your element has overflow and truncated
@@ -115,9 +155,12 @@
artworksPosition = startPos;
rotateBackground();
} else {
var bg = document.getElementById('bgImage');
if (result.cover) {
var bg = document.getElementById('bgImage');
bg.setAttribute('style', 'background-image: url("/api/v1/Games/' + gameId + '/cover/image"); background-position: center; background-repeat: no-repeat; background-size: cover; filter: blur(10px); -webkit-filter: blur(10px);');
} else {
var randomInt = randomIntFromInterval(1, 3);
bg.setAttribute('style', 'background-image: url("/images/gamebg' + randomInt + '.jpg"); background-position: center; background-repeat: no-repeat; background-size: cover; filter: blur(10px); -webkit-filter: blur(10px);');
}
}
@@ -131,7 +174,7 @@
ajaxCall('/api/v1/games/' + gameId + '/companies', 'GET', function (result) {
var lstDevelopers = [];
var lstPublishers = [];
for (var i = 0; i < result.length; i++) {
var companyLabel = document.createElement('span');
companyLabel.className = 'gamegenrelabel';
@@ -222,7 +265,7 @@
var gameScreenshots = document.getElementById('gamescreenshots');
if (result.screenshots || result.videos) {
var gameScreenshots_Main = document.getElementById('gamescreenshots_main');
// load static screenshots
var gameScreenshots_Gallery = document.getElementById('gamescreenshots_gallery_panel');
var imageIndex = 0;
@@ -286,6 +329,8 @@
gameScreenshots_vGallery.appendChild(itemsArr[i]);
}
selectScreenshot(0);
}, function(error) {
selectScreenshot(0);
});
} else {
@@ -296,15 +341,25 @@
}
// load roms
loadRoms();
});
function loadRoms(displayCheckboxes) {
var existingTable = document.getElementById('romtable');
if (existingTable) {
existingTable.remove();
}
var gameRoms = document.getElementById('gamesummaryroms');
ajaxCall('/api/v1/Games/' + gameId + '/roms', 'GET', function (result) {
if (result) {
result.sort((a, b) => a.platform.name.charCodeAt(0) - b.platform.name.charCodeAt(0));
var newTable = document.createElement('table');
newTable.id = 'romtable';
newTable.className = 'romtable';
newTable.setAttribute('cellspacing', 0);
newTable.appendChild(createTableRow(true, ['Name', 'Size', 'Media', '', '', '']));
newTable.appendChild(createTableRow(true, [['<input id="rom_mastercheck" type="checkbox" onclick="selectAllChecks();"/>', 'rom_checkbox_box_hidden', 'rom_edit_checkbox'], 'Name', 'Size', 'Media', '', '', '']));
var lastPlatform = '';
for (var i = 0; i < result.length; i++) {
@@ -320,11 +375,12 @@
var launchButton = '';
if (result[i].emulator) {
launchButton = '<a href="/index.html?page=emulator&engine=' + result[i].emulator.type + '&core=' + result[i].emulator.core + '&platformid=' + result[i].platform.id + '&gameid=' + gameId + '&rompath=' + encodeURIComponent('/api/v1/Games/' + gameId + '/roms/' + result[i].id + '/file') + '" class="romstart">Launch</a>';
launchButton = '<a href="/index.html?page=emulator&engine=' + result[i].emulator.type + '&core=' + result[i].emulator.core + '&platformid=' + result[i].platform.id + '&gameid=' + gameId + '&rompath=' + encodeURIComponent('/api/v1/Games/' + gameId + '/roms/' + result[i].id + '/' + encodeURIComponent(result[i].name)) + '" class="romstart">Launch</a>';
}
var newRow = [
'<a href="/api/v1/Games/' + gameId + '/roms/' + result[i].id + '/file" class="romlink">' + result[i].name + '</a>',
['<input type="checkbox" name="rom_checkbox" data-romid="' + result[i].id + '" />', 'rom_checkbox_box_hidden', 'rom_edit_checkbox'],
'<a href="/api/v1/Games/' + gameId + '/roms/' + result[i].id + '/' + encodeURIComponent(result[i].name) + '" class="romlink">' + result[i].name + '</a>',
formatBytes(result[i].size, 2),
result[i].romTypeMedia,
result[i].mediaLabel,
@@ -335,11 +391,15 @@
}
gameRoms.appendChild(newTable);
if (displayCheckboxes == true) {
DisplayROMCheckboxes(true);
}
} else {
gameRoms.setAttribute('style', 'display: none;');
}
});
});
}
function rotateBackground() {
if (artworks) {
@@ -409,11 +469,201 @@
var gameScreenshots_Items = document.getElementsByName('gamescreenshots_gallery_item');
selectedScreenshot = selectedScreenshot - 1;
if (selectedScreenshot < 0) {
selectedScreenshot = gameScreenshots_Items.length - 1;
}
selectScreenshot(selectedScreenshot);
}
function DisplayROMCheckboxes(visible) {
var checkbox_boxes = document.getElementsByName('rom_edit_checkbox');
for (var i = 0; i < checkbox_boxes.length; i++) {
if (visible == true) {
checkbox_boxes[i].className = 'rom_checkbox_box';
} else {
checkbox_boxes[i].className = 'rom_checkbox_box_hidden';
}
}
var editButton = document.getElementById('rom_edit');
var deleteButton = document.getElementById('rom_edit_panel');
if (visible == true) {
editButton.innerHTML = 'Cancel';
deleteButton.style.display = '';
} else {
editButton.innerHTML = 'Edit';
document.getElementById('rom_mastercheck').checked = false;
deleteButton.style.display = 'none';
selectAllChecks(false);
}
editButton.setAttribute('onclick', 'DisplayROMCheckboxes(' + !visible + ');');
}
function selectAllChecks(value) {
var mastercheckbox = document.getElementById('rom_mastercheck');
var checkboxes = document.getElementsByName('rom_checkbox');
for (var i = 0; i < checkboxes.length; i++) {
if (value) {
checkboxes[i].checked = value;
} else {
checkboxes[i].checked = mastercheckbox.checked;
}
}
}
$('#rom_edit_fixplatform').select2({
minimumInputLength: 3,
placeholder: "Platform",
ajax: {
url: '/api/v1/Search/Platform',
data: function (params) {
var query = {
SearchString: params.term
}
// Query parameters will be ?SearchString=[term]
return query;
},
processResults: function (data) {
var arr = [];
for (var i = 0; i < data.length; i++) {
arr.push({
id: data[i].id,
text: data[i].name
});
}
return {
results: arr
};
}
}
});
$('#rom_edit_fixgame').select2({
minimumInputLength: 3,
templateResult: DropDownRenderGameOption,
placeholder: "Game",
ajax: {
url: '/api/v1/Search/Game',
data: function (params) {
fixplatform = $('#rom_edit_fixplatform').select2('data');
var query = {
PlatformId: fixplatform[0].id,
SearchString: params.term
}
// Query parameters will be ?SearchString=[term]
return query;
},
processResults: function (data) {
var arr = [];
for (var i = 0; i < data.length; i++) {
arr.push({
id: data[i].id,
text: data[i].name,
cover: data[i].cover
});
}
return {
results: arr
};
}
}
});
var remapCallCounter = 0;
var remapCallCounterMax = 0;
function remapTitles() {
var fixplatform = $('#rom_edit_fixplatform').select2('data');
var fixgame = $('#rom_edit_fixgame').select2('data');
if (fixplatform[0] && fixgame[0]) {
var rom_checks = document.getElementsByName('rom_checkbox');
for (var i = 0; i < rom_checks.length; i++) {
if (rom_checks[i].checked == true) {
remapCallCounterMax += 1;
}
}
if (remapCallCounterMax > 0) {
showProgress();
for (var i = 0; i < rom_checks.length; i++) {
if (rom_checks[i].checked == true) {
var romId = rom_checks[i].getAttribute('data-romid');
remapCallCounter += 1;
ajaxCall('/api/v1/Games/' + gameId + '/roms/' + romId + '?NewPlatformId=' + fixplatform[0].id + '&NewGameId=' + fixgame[0].id, 'PATCH', function (result) {
remapTitlesCallback();
}, function (result) {
remapTitlesCallback();
});
}
}
}
}
}
function remapTitlesCallback() {
remapCallCounter = remapCallCounter - 1;
if (remapCallCounter <= 0) {
closeProgress();
loadRoms(true);
remapCallCounter = 0;
remapCallCounterMax = 0;
}
}
function deleteGameRoms() {
var rom_checks = document.getElementsByName('rom_checkbox');
var itemsChecked = false;
for (var i = 0; i < rom_checks.length; i++) {
if (rom_checks[i].checked == true) {
itemsChecked = true;
break;
}
}
if (itemsChecked == true) {
showSubDialog('romsdelete');
}
}
function deleteGameRomsCallback() {
var rom_checks = document.getElementsByName('rom_checkbox');
for (var i = 0; i < rom_checks.length; i++) {
if (rom_checks[i].checked == true) {
var romId = rom_checks[i].getAttribute('data-romid');
remapCallCounter += 1;
ajaxCall('/api/v1/Games/' + gameId + '/roms/' + romId, 'DELETE', function (result) {
remapTitlesCallback();
});
}
}
}
function showProgress() {
// Get the modal
var submodal = document.getElementById("myModalProgress");
// When the user clicks on the button, open the modal
submodal.style.display = "block";
}
function closeProgress() {
// Get the modal
var submodal = document.getElementById("myModalProgress");
submodal.style.display = "none";
}
</script>

View File

@@ -1,4 +1,8 @@
<div id="games_home">
<div id="bgImage" style="background-image: url('/images/librarybg.jpg'); background-position: center; background-repeat: no-repeat; background-size: cover; filter: blur(10px); -webkit-filter: blur(10px);">
<div id="bgImage_Opacity"></div>
</div>
<div id="games_home">
<div id="games_filter"></div>
<div id="games_library"></div>
</div>
@@ -7,10 +11,7 @@
ajaxCall('/api/v1/Filter', 'GET', function (result) {
var filterElement = document.getElementById('games_filter');
formatFilterPanel(filterElement, result);
});
ajaxCall('/api/v1/Games', 'GET', function (result) {
var gameElement = document.getElementById('games_library');
formatGamesPanel(gameElement, result);
executeFilter();
});
</script>

View File

@@ -20,8 +20,7 @@
</div>
<script type="text/javascript">
const urlParams = new URLSearchParams(window.location.search);
var myParam = urlParams.get('sub');
var myParam = getQueryString('sub', 'string');
var selectedTab = '';
@@ -43,6 +42,6 @@
}
}
$('#properties_bodypanel').load('/pages/settings/' + TabName + '.html');
$('#properties_bodypanel').load('/pages/settings/' + TabName + '.html?v=' + AppVersion);
}
</script>

View File

@@ -2,7 +2,7 @@
<h1 id="gametitle_label">About Gaseous</h1>
</div>
<table>
<table style="width: 100%;">
<tr>
<th>Home Page</th>
<td><a href="https://github.com/gaseous-project/gaseous-server" class="romlink">https://github.com/gaseous-project/gaseous-server</a></td>
@@ -11,4 +11,44 @@
<th>Bugs and Feature Requests</th>
<td><a href="https://github.com/gaseous-project/gaseous-server/issues" class="romlink">https://github.com/gaseous-project/gaseous-server/issues</a></td>
</tr>
</table>
<tr>
<th>Server Version</th>
<td id="settings_appversion"></td>
</tr>
<tr>
<td colspan="2">
<h3>Data Sources</h2>
<h4>Game data</h4>
</td>
</tr>
<tr>
<td>
<a href="https://www.igdb.com/" target="_blank"><img src="/images/IGDB_logo.svg" style="filter: invert(100%); height: 36px;" /></a>
</td>
<td>
The Internet Game Database
</td>
</tr>
<tr>
<td colspan="2">
<h4>Signature data sources</h4>
</td>
</tr>
<tr>
<td>
<a href="https://www.tosecdev.org/" target="_blank"><img src="/images/TOSEC_logo.gif" style="height: 36px;" /></a>
</td>
<td>
The Old School Emulation Center
</td>
</tr>
</table>
<script type="text/javascript">
ajaxCall('/api/v1/System/Version', 'GET', function (result) {
if (result) {
var versionBox = document.getElementById('settings_appversion');
versionBox.innerHTML = result;
}
});
</script>

View File

@@ -6,8 +6,19 @@
<div id="system_tasks"></div>
<h3>Usage</h3>
<p><strong>Library</strong></p>
<p><strong>Disk</strong></p>
<div id="system_disks"></div>
<p><strong>Library</strong></p>
<div>
<table cellspacing="0" style="width: 100%;">
<tr>
<td id="system_platforms"></td>
</tr>
<tr>
<td id="system_platforms_legend"></td>
</tr>
</table>
</div>
<p><strong>Database</strong></p>
<div id="system_database"></div>
@@ -40,6 +51,9 @@
case 'LibraryScan':
itemTypeName = "Library scan";
break;
case 'CollectionCompiler':
itemTypeName = "Compress collections";
break;
default:
itemTypeName = result[i].itemType;
break;
@@ -67,7 +81,7 @@
}
var startButton = '';
if (result[i].itemState != "Running") {
if (result[i].allowManualStart == true && result[i].itemState != "Running") {
startButton = "<span id='startProcess' class='romstart' onclick='StartProcess(\"" + result[i].itemType + "\");'>Start</span>";
}
@@ -92,21 +106,24 @@
function SystemLoadSystemStatus() {
ajaxCall('/api/v1/System', 'GET', function (result) {
if (result) {
var totalLibrarySpace = 0;
// disks
var newTable = document.createElement('table');
newTable.className = 'romtable';
newTable.setAttribute('cellspacing', 0);
newTable.appendChild(createTableRow(true, ['Path', 'Library Size <div id="disk_LibSize" style="width: 10px; height: 10px; background-color: green;"></div>', 'Other <div id="disk_OtherSize" style="width: 10px; height: 10px; background-color: lightgreen;"></div>', 'Total Size <div id="disk_FreeSize" style="width: 10px; height: 10px; background-color: lightgray;"></div>']));
for (var i = 0; i < result.Paths.length; i++) {
var spaceUsedByLibrary = result.Paths[i].SpaceUsed;
var spaceUsedByOthers = result.Paths[i].TotalSpace - result.Paths[i].SpaceAvailable;
for (var i = 0; i < result.paths.length; i++) {
var spaceUsedByLibrary = result.paths[i].spaceUsed;
totalLibrarySpace += spaceUsedByLibrary;
var spaceUsedByOthers = result.paths[i].totalSpace - result.paths[i].spaceAvailable;
var newRow = [
result.Paths[i].LibraryPath,
result.paths[i].libraryPath,
formatBytes(spaceUsedByLibrary),
formatBytes(spaceUsedByOthers),
formatBytes(result.Paths[i].TotalSpace)
formatBytes(result.paths[i].totalSpace)
];
newTable.appendChild(createTableRow(false, newRow, 'romrow', 'romcell'));
@@ -114,7 +131,7 @@
var spaceRow = document.createElement('tr');
var spaceCell = document.createElement('td');
spaceCell.setAttribute('colspan', 4);
spaceCell.appendChild(BuildSpaceBar(spaceUsedByLibrary, spaceUsedByOthers, result.Paths[i].TotalSpace));
spaceCell.appendChild(BuildSpaceBar(spaceUsedByLibrary, spaceUsedByOthers, result.paths[i].totalSpace));
spaceRow.appendChild(spaceCell);
newTable.appendChild(spaceRow);
}
@@ -123,11 +140,13 @@
targetDiv.innerHTML = '';
targetDiv.appendChild(newTable);
BuildLibraryStatisticsBar(document.getElementById('system_platforms'), document.getElementById('system_platforms_legend'), result.platformStatistics, totalLibrarySpace);
// database
var newDbTable = document.createElement('table');
newDbTable.className = 'romtable';
newDbTable.setAttribute('cellspacing', 0);
newDbTable.appendChild(createTableRow(false, ['Database Size', formatBytes(result.DatabaseSize)]));
newDbTable.appendChild(createTableRow(false, ['Database Size', formatBytes(result.databaseSize)]));
var targetDbDiv = document.getElementById('system_database');
targetDbDiv.innerHTML = '';
@@ -165,6 +184,40 @@
return newTable;
}
function BuildLibraryStatisticsBar(TargetObject, TargetObjectLegend, LibraryStatistics, LibrarySize) {
var newTable = document.createElement('table');
newTable.setAttribute('cellspacing', 0);
newTable.setAttribute('style', 'width: 100%; height: 10px;');
var newRow = document.createElement('tr');
for (var i = 0; i < LibraryStatistics.length; i++) {
var platformSizePercent = LibraryStatistics[i].totalSize / LibrarySize * 100;
var platformSizeColour = intToRGB(hashCode(LibraryStatistics[i].platform));
var newCell = document.createElement('td');
newCell.setAttribute('style', 'min-width: 2px; width: ' + platformSizePercent + '%; background-color: #' + platformSizeColour);
newRow.appendChild(newCell);
var legend = document.createElement('div');
legend.className = 'legend_box';
var legendColour = document.createElement('div');
legendColour.className = 'legend_colour';
legendColour.setAttribute('style', 'background-color: #' + platformSizeColour + ';');
var legendLabel = document.createElement('div');
legendLabel.className = 'legend_label';
legendLabel.innerHTML = LibraryStatistics[i].platform + '<br />' + formatBytes(LibraryStatistics[i].totalSize);
legend.appendChild(legendColour);
legend.appendChild(legendLabel);
TargetObjectLegend.appendChild(legend);
}
newTable.appendChild(newRow);
TargetObject.appendChild(newTable);
}
function SystemSignaturesStatus() {
ajaxCall('/api/v1/Signatures/Status', 'GET', function (result) {
var newTable = document.createElement('table');

View File

@@ -7,6 +7,10 @@
var containerPanelSearch = document.createElement('div');
containerPanelSearch.className = 'filter_panel_box';
var containerPanelSearchField = document.createElement('input');
var searchCookie = getCookie('filter_panel_search');
if (searchCookie) {
containerPanelSearchField.value = searchCookie;
}
containerPanelSearchField.id = 'filter_panel_search';
containerPanelSearchField.type = 'text';
containerPanelSearchField.placeholder = 'Search';
@@ -15,45 +19,136 @@
panel.appendChild(containerPanelSearch);
panel.appendChild(buildFilterPanelHeader('userrating', 'User Rating'));
var containerPanelUserRating = document.createElement('div');
containerPanelUserRating.className = 'filter_panel_box';
var containerPanelUserRatingMinField = document.createElement('input');
var minRatingCookie = getCookie('filter_panel_userrating_min');
if (minRatingCookie) {
containerPanelUserRatingMinField.value = minRatingCookie;
}
containerPanelUserRatingMinField.id = 'filter_panel_userrating_min';
containerPanelUserRatingMinField.type = 'number';
containerPanelUserRatingMinField.placeholder = '0';
containerPanelUserRatingMinField.setAttribute('onchange', 'executeFilterDelayed();');
containerPanelUserRatingMinField.setAttribute('onkeydown', 'executeFilterDelayed();');
containerPanelUserRatingMinField.setAttribute('min', '0');
containerPanelUserRatingMinField.setAttribute('max', '100');
containerPanelUserRating.appendChild(containerPanelUserRatingMinField);
var containerPanelUserRatingMaxField = document.createElement('input');
var maxRatingCookie = getCookie('filter_panel_userrating_max');
if (maxRatingCookie) {
containerPanelUserRatingMaxField.value = maxRatingCookie;
}
containerPanelUserRatingMaxField.id = 'filter_panel_userrating_max';
containerPanelUserRatingMaxField.type = 'number';
containerPanelUserRatingMaxField.placeholder = '100';
containerPanelUserRatingMaxField.setAttribute('onchange', 'executeFilterDelayed();');
containerPanelUserRatingMaxField.setAttribute('onkeydown', 'executeFilterDelayed();');
containerPanelUserRatingMaxField.setAttribute('min', '0');
containerPanelUserRatingMaxField.setAttribute('max', '100');
containerPanelUserRating.appendChild(containerPanelUserRatingMaxField);
panel.appendChild(containerPanelUserRating);
if (result.platforms) {
panel.appendChild(buildFilterPanelHeader('platforms', 'Platforms'));
var containerPanelPlatform = document.createElement('div');
containerPanelPlatform.className = 'filter_panel_box';
for (var i = 0; i < result.platforms.length; i++) {
containerPanelPlatform.appendChild(buildFilterPanelItem('platforms', result.platforms[i].id, result.platforms[i].name));
}
panel.appendChild(containerPanelPlatform);
targetElement.appendChild(panel);
buildFilterPanel(panel, 'platform', 'Platforms', result.platforms, true, true);
}
if (result.genres) {
panel.appendChild(buildFilterPanelHeader('genres', 'Genres'));
var containerPanelGenres = document.createElement('div');
containerPanelGenres.className = 'filter_panel_box';
for (var i = 0; i < result.genres.length; i++) {
containerPanelGenres.appendChild(buildFilterPanelItem('genres', result.genres[i].id, result.genres[i].name));
}
panel.appendChild(containerPanelGenres);
targetElement.appendChild(panel);
buildFilterPanel(panel, 'genre', 'Genres', result.genres, true, false);
}
if (result.gamemodes) {
buildFilterPanel(panel, 'gamemode', 'Players', result.gamemodes, true, false);
}
if (result.playerperspectives) {
buildFilterPanel(panel, 'playerperspective', 'Player Perspectives', result.playerperspectives, true, false);
}
if (result.themes) {
buildFilterPanel(panel, 'theme', 'Themes', result.themes, true, false);
}
targetElement.appendChild(panel);
}
function buildFilterPanelHeader(headerString, friendlyHeaderString) {
function buildFilterPanel(targetElement, headerString, friendlyHeaderString, valueList, showToggle, initialDisplay) {
if (showToggle == false) { initialDisplay = true; }
var displayCookie = getCookie('filter_panel_box_' + headerString);
if (displayCookie) {
initialDisplay = (displayCookie === 'true');
console.log(displayCookie);
}
targetElement.appendChild(buildFilterPanelHeader(headerString, friendlyHeaderString, showToggle, initialDisplay));
var containerPanel = document.createElement('div');
containerPanel.className = 'filter_panel_box';
containerPanel.id = 'filter_panel_box_' + headerString;
if (initialDisplay == false) {
containerPanel.setAttribute('style', 'display: none;');
}
for (var i = 0; i < valueList.length; i++) {
containerPanel.appendChild(buildFilterPanelItem(headerString, valueList[i].id, valueList[i].name));
}
targetElement.appendChild(containerPanel);
}
function buildFilterPanelHeader(headerString, friendlyHeaderString, showVisibleToggle, toggleInitialValue) {
var headerToggle = document.createElement('div');
headerToggle.setAttribute('style', 'float: right;');
headerToggle.id = 'filter_panel_header_toggle_' + headerString;
if (toggleInitialValue == true) {
headerToggle.innerHTML = '-';
} else {
headerToggle.innerHTML = '+';
}
var headerLabel = document.createElement('span');
headerLabel.innerHTML = friendlyHeaderString;
var header = document.createElement('div');
header.id = 'filter_panel_header_' + headerString;
header.className = 'filter_header';
header.innerHTML = friendlyHeaderString;
if (showVisibleToggle == true) {
header.appendChild(headerToggle);
header.setAttribute('onclick', 'toggleFilterPanel("' + headerString + '");');
header.style.cursor = 'pointer';
}
header.appendChild(headerLabel);
return header;
}
function toggleFilterPanel(panelName) {
var filterPanel = document.getElementById('filter_panel_box_' + panelName);
var filterPanelToggle = document.getElementById('filter_panel_header_toggle_' + panelName);
var cookieVal = '';
if (filterPanel.style.display == 'none') {
filterPanelToggle.innerHTML = '-';
filterPanel.style.display = '';
cookieVal = "true";
} else {
filterPanelToggle.innerHTML = '+';
filterPanel.style.display = 'none';
cookieVal = "false";
}
setCookie("filter_panel_box_" + panelName, cookieVal);
}
function buildFilterPanelItem(filterType, itemString, friendlyItemString) {
var checkCookie = getCookie('filter_panel_item_' + filterType + '_checkbox_' + itemString);
var checkState = false;
if (checkCookie) {
checkState = (checkCookie === 'true');
}
var filterPanelItem = document.createElement('div');
filterPanelItem.id = 'filter_panel_item_' + itemString;
filterPanelItem.className = 'filter_panel_item';
@@ -61,12 +156,15 @@ function buildFilterPanelItem(filterType, itemString, friendlyItemString) {
var filterPanelItemCheckBox = document.createElement('div');
var filterPanelItemCheckBoxItem = document.createElement('input');
filterPanelItemCheckBoxItem.id = 'filter_panel_item_checkbox_' + itemString;
filterPanelItemCheckBoxItem.id = 'filter_panel_item_' + filterType + '_checkbox_' + itemString;
filterPanelItemCheckBoxItem.type = 'checkbox';
filterPanelItemCheckBoxItem.className = 'filter_panel_item_checkbox';
filterPanelItemCheckBoxItem.name = 'filter_' + filterType;
filterPanelItemCheckBoxItem.setAttribute('filter_id', itemString);
filterPanelItemCheckBoxItem.setAttribute('oninput' , 'executeFilter();');
if (checkState == true) {
filterPanelItemCheckBoxItem.checked = true;
}
filterPanelItemCheckBox.appendChild(filterPanelItemCheckBoxItem);
var filterPanelItemLabel = document.createElement('label');
@@ -92,33 +190,79 @@ function executeFilterDelayed() {
function executeFilter() {
// build filter lists
var queries = [];
var platforms = '';
var genres = '';
var searchString = document.getElementById('filter_panel_search').value;
var platformFilters = document.getElementsByName('filter_platforms');
var genreFilters = document.getElementsByName('filter_genres');
var searchString = document.getElementById('filter_panel_search');
if (searchString.value.length > 0) {
queries.push('name=' + searchString.value);
}
setCookie(searchString.id, searchString.value);
for (var i = 0; i < platformFilters.length; i++) {
if (platformFilters[i].checked) {
if (platforms.length > 0) {
platforms += ',';
var minUserRating = 0;
var minUserRatingInput = document.getElementById('filter_panel_userrating_min');
if (minUserRatingInput.value) {
minUserRating = minUserRatingInput.value;
queries.push('minrating=' + minUserRating);
}
setCookie(minUserRatingInput.id, minUserRatingInput.value);
var maxUserRating = 100;
var maxUserRatingInput = document.getElementById('filter_panel_userrating_max');
if (maxUserRatingInput.value) {
maxUserRating = maxUserRatingInput.value;
queries.push('maxrating=' + maxUserRating);
}
setCookie(maxUserRatingInput.id, maxUserRatingInput.value);
queries.push(GetFilterQuery('platform'));
queries.push(GetFilterQuery('genre'));
queries.push(GetFilterQuery('gamemode'));
queries.push(GetFilterQuery('playerperspective'));
queries.push(GetFilterQuery('theme'));
var queryString = '';
for (var i = 0; i < queries.length; i++) {
if (queries[i].length > 0) {
if (queryString.length == 0) {
queryString = '?';
} else {
queryString += '&';
}
platforms += platformFilters[i].getAttribute('filter_id');
queryString += queries[i];
}
}
for (var i = 0; i < genreFilters.length; i++) {
if (genreFilters[i].checked) {
if (genres.length > 0) {
genres += ',';
}
genres += genreFilters[i].getAttribute('filter_id');
}
}
console.log('Query string = ' + queryString);
ajaxCall('/api/v1/Games?name=' + searchString + '&platform=' + platforms + '&genre=' + genres, 'GET', function (result) {
ajaxCall('/api/v1/Games' + queryString, 'GET', function (result) {
var gameElement = document.getElementById('games_library');
formatGamesPanel(gameElement, result);
});
}
function GetFilterQuery(filterName) {
var Filters = document.getElementsByName('filter_' + filterName);
var queryString = '';
for (var i = 0; i < Filters.length; i++) {
if (Filters[i].checked) {
setCookie(Filters[i].id, true);
if (queryString.length > 0) {
queryString += ',';
}
queryString += Filters[i].getAttribute('filter_id');
} else {
setCookie(Filters[i].id, false);
}
}
if (queryString.length > 0) {
queryString = filterName + '=' + queryString;
}
return queryString;
}

View File

@@ -4,6 +4,12 @@
var game = renderGameIcon(result[i], true, false);
targetElement.appendChild(game);
}
$('.lazy').Lazy({
scrollDirection: 'vertical',
effect: 'fadeIn',
visibleOnly: true
});
}
function renderGameIcon(gameObject, showTitle, showRatings) {
@@ -12,9 +18,9 @@ function renderGameIcon(gameObject, showTitle, showRatings) {
gameBox.setAttribute('onclick', 'window.location.href = "/index.html?page=game&id=' + gameObject.id + '";');
var gameImage = document.createElement('img');
gameImage.className = 'game_tile_image';
gameImage.className = 'game_tile_image lazy';
if (gameObject.cover) {
gameImage.src = '/api/v1/Games/' + gameObject.id + '/cover/image';
gameImage.setAttribute('data-src', '/api/v1/Games/' + gameObject.id + '/cover/image');
} else {
gameImage.src = '/images/unknowngame.png';
gameImage.className = 'game_tile_image unknown';

View File

@@ -0,0 +1,2 @@
/*! jQuery & Zepto Lazy v1.7.10 - http://jquery.eisbehr.de/lazy - MIT&GPL-2.0 license - Copyright 2012-2018 Daniel 'Eisbehr' Kern */
!function(t,e){"use strict";function r(r,a,i,u,l){function f(){L=t.devicePixelRatio>1,i=c(i),a.delay>=0&&setTimeout(function(){s(!0)},a.delay),(a.delay<0||a.combined)&&(u.e=v(a.throttle,function(t){"resize"===t.type&&(w=B=-1),s(t.all)}),u.a=function(t){t=c(t),i.push.apply(i,t)},u.g=function(){return i=n(i).filter(function(){return!n(this).data(a.loadedName)})},u.f=function(t){for(var e=0;e<t.length;e++){var r=i.filter(function(){return this===t[e]});r.length&&s(!1,r)}},s(),n(a.appendScroll).on("scroll."+l+" resize."+l,u.e))}function c(t){var i=a.defaultImage,o=a.placeholder,u=a.imageBase,l=a.srcsetAttribute,f=a.loaderAttribute,c=a._f||{};t=n(t).filter(function(){var t=n(this),r=m(this);return!t.data(a.handledName)&&(t.attr(a.attribute)||t.attr(l)||t.attr(f)||c[r]!==e)}).data("plugin_"+a.name,r);for(var s=0,d=t.length;s<d;s++){var A=n(t[s]),g=m(t[s]),h=A.attr(a.imageBaseAttribute)||u;g===N&&h&&A.attr(l)&&A.attr(l,b(A.attr(l),h)),c[g]===e||A.attr(f)||A.attr(f,c[g]),g===N&&i&&!A.attr(E)?A.attr(E,i):g===N||!o||A.css(O)&&"none"!==A.css(O)||A.css(O,"url('"+o+"')")}return t}function s(t,e){if(!i.length)return void(a.autoDestroy&&r.destroy());for(var o=e||i,u=!1,l=a.imageBase||"",f=a.srcsetAttribute,c=a.handledName,s=0;s<o.length;s++)if(t||e||A(o[s])){var g=n(o[s]),h=m(o[s]),b=g.attr(a.attribute),v=g.attr(a.imageBaseAttribute)||l,p=g.attr(a.loaderAttribute);g.data(c)||a.visibleOnly&&!g.is(":visible")||!((b||g.attr(f))&&(h===N&&(v+b!==g.attr(E)||g.attr(f)!==g.attr(F))||h!==N&&v+b!==g.css(O))||p)||(u=!0,g.data(c,!0),d(g,h,v,p))}u&&(i=n(i).filter(function(){return!n(this).data(c)}))}function d(t,e,r,i){++z;var o=function(){y("onError",t),p(),o=n.noop};y("beforeLoad",t);var u=a.attribute,l=a.srcsetAttribute,f=a.sizesAttribute,c=a.retinaAttribute,s=a.removeAttribute,d=a.loadedName,A=t.attr(c);if(i){var g=function(){s&&t.removeAttr(a.loaderAttribute),t.data(d,!0),y(T,t),setTimeout(p,1),g=n.noop};t.off(I).one(I,o).one(D,g),y(i,t,function(e){e?(t.off(D),g()):(t.off(I),o())})||t.trigger(I)}else{var h=n(new Image);h.one(I,o).one(D,function(){t.hide(),e===N?t.attr(C,h.attr(C)).attr(F,h.attr(F)).attr(E,h.attr(E)):t.css(O,"url('"+h.attr(E)+"')"),t[a.effect](a.effectTime),s&&(t.removeAttr(u+" "+l+" "+c+" "+a.imageBaseAttribute),f!==C&&t.removeAttr(f)),t.data(d,!0),y(T,t),h.remove(),p()});var m=(L&&A?A:t.attr(u))||"";h.attr(C,t.attr(f)).attr(F,t.attr(l)).attr(E,m?r+m:null),h.complete&&h.trigger(D)}}function A(t){var e=t.getBoundingClientRect(),r=a.scrollDirection,n=a.threshold,i=h()+n>e.top&&-n<e.bottom,o=g()+n>e.left&&-n<e.right;return"vertical"===r?i:"horizontal"===r?o:i&&o}function g(){return w>=0?w:w=n(t).width()}function h(){return B>=0?B:B=n(t).height()}function m(t){return t.tagName.toLowerCase()}function b(t,e){if(e){var r=t.split(",");t="";for(var a=0,n=r.length;a<n;a++)t+=e+r[a].trim()+(a!==n-1?",":"")}return t}function v(t,e){var n,i=0;return function(o,u){function l(){i=+new Date,e.call(r,o)}var f=+new Date-i;n&&clearTimeout(n),f>t||!a.enableThrottle||u?l():n=setTimeout(l,t-f)}}function p(){--z,i.length||z||y("onFinishedAll")}function y(t,e,n){return!!(t=a[t])&&(t.apply(r,[].slice.call(arguments,1)),!0)}var z=0,w=-1,B=-1,L=!1,T="afterLoad",D="load",I="error",N="img",E="src",F="srcset",C="sizes",O="background-image";"event"===a.bind||o?f():n(t).on(D+"."+l,f)}function a(a,o){var u=this,l=n.extend({},u.config,o),f={},c=l.name+"-"+ ++i;return u.config=function(t,r){return r===e?l[t]:(l[t]=r,u)},u.addItems=function(t){return f.a&&f.a("string"===n.type(t)?n(t):t),u},u.getItems=function(){return f.g?f.g():{}},u.update=function(t){return f.e&&f.e({},!t),u},u.force=function(t){return f.f&&f.f("string"===n.type(t)?n(t):t),u},u.loadAll=function(){return f.e&&f.e({all:!0},!0),u},u.destroy=function(){return n(l.appendScroll).off("."+c,f.e),n(t).off("."+c),f={},e},r(u,l,a,f,c),l.chainable?a:u}var n=t.jQuery||t.Zepto,i=0,o=!1;n.fn.Lazy=n.fn.lazy=function(t){return new a(this,t)},n.Lazy=n.lazy=function(t,r,i){if(n.isFunction(r)&&(i=r,r=[]),n.isFunction(i)){t=n.isArray(t)?t:[t],r=n.isArray(r)?r:[r];for(var o=a.prototype.config,u=o._f||(o._f={}),l=0,f=t.length;l<f;l++)(o[t[l]]===e||n.isFunction(o[t[l]]))&&(o[t[l]]=i);for(var c=0,s=r.length;c<s;c++)u[r[c]]=t[0]}},a.prototype.config={name:"lazy",chainable:!0,autoDestroy:!0,bind:"load",threshold:500,visibleOnly:!1,appendScroll:t,scrollDirection:"both",imageBase:null,defaultImage:"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==",placeholder:null,delay:-1,combined:!1,attribute:"data-src",srcsetAttribute:"data-srcset",sizesAttribute:"data-sizes",retinaAttribute:"data-retina",loaderAttribute:"data-loader",imageBaseAttribute:"data-imagebase",removeAttribute:!0,handledName:"handled",loadedName:"loaded",effect:"show",effectTime:0,enableThrottle:!0,throttle:250,beforeLoad:e,afterLoad:e,onError:e,onFinishedAll:e},n(t).on("load",function(){o=!0})}(window);

View File

@@ -0,0 +1,2 @@
/*! jQuery & Zepto Lazy - All Plugins v1.7.10 - http://jquery.eisbehr.de/lazy - MIT&GPL-2.0 license - Copyright 2012-2018 Daniel 'Eisbehr' Kern */
!function(t){function a(a,e,r,o){o=o?o.toUpperCase():"GET";var i;"POST"!==o&&"PUT"!==o||!a.config("ajaxCreateData")||(i=a.config("ajaxCreateData").apply(a,[e])),t.ajax({url:e.attr("data-src"),type:"POST"===o||"PUT"===o?o:"GET",data:i,dataType:e.attr("data-type")||"html",success:function(t){e.html(t),r(!0),a.config("removeAttribute")&&e.removeAttr("data-src data-method data-type")},error:function(){r(!1)}})}t.lazy("ajax",function(t,e){a(this,t,e,t.attr("data-method"))}),t.lazy("get",function(t,e){a(this,t,e,"GET")}),t.lazy("post",function(t,e){a(this,t,e,"POST")}),t.lazy("put",function(t,e){a(this,t,e,"PUT")})}(window.jQuery||window.Zepto),function(t){t.lazy(["av","audio","video"],["audio","video"],function(a,e){var r=a[0].tagName.toLowerCase();if("audio"===r||"video"===r){var o=a.find("data-src"),i=a.find("data-track"),n=0,c=function(){++n===o.length&&e(!1)},s=function(){var a=t(this),e=a[0].tagName.toLowerCase(),r=a.prop("attributes"),o=t("data-src"===e?"<source>":"<track>");"data-src"===e&&o.one("error",c),t.each(r,function(t,a){o.attr(a.name,a.value)}),a.replaceWith(o)};a.one("loadedmetadata",function(){e(!0)}).off("load error").attr("poster",a.attr("data-poster")),o.length?o.each(s):a.attr("data-src")?(t.each(a.attr("data-src").split(","),function(e,r){var o=r.split("|");a.append(t("<source>").one("error",c).attr({src:o[0].trim(),type:o[1].trim()}))}),this.config("removeAttribute")&&a.removeAttr("data-src")):e(!1),i.length&&i.each(s)}else e(!1)})}(window.jQuery||window.Zepto),function(t){t.lazy(["frame","iframe"],"iframe",function(a,e){var r=this;if("iframe"===a[0].tagName.toLowerCase()){var o=a.attr("data-error-detect");"true"!==o&&"1"!==o?(a.attr("src",a.attr("data-src")),r.config("removeAttribute")&&a.removeAttr("data-src data-error-detect")):t.ajax({url:a.attr("data-src"),dataType:"html",crossDomain:!0,xhrFields:{withCredentials:!0},success:function(t){a.html(t).attr("src",a.attr("data-src")),r.config("removeAttribute")&&a.removeAttr("data-src data-error-detect")},error:function(){e(!1)}})}else e(!1)})}(window.jQuery||window.Zepto),function(t){t.lazy("noop",function(){}),t.lazy("noop-success",function(t,a){a(!0)}),t.lazy("noop-error",function(t,a){a(!1)})}(window.jQuery||window.Zepto),function(t){function a(a,e,i){var n=a.prop("attributes"),c=t("<"+e+">");return t.each(n,function(t,a){"srcset"!==a.name&&a.name!==o||(a.value=r(a.value,i)),c.attr(a.name,a.value)}),a.replaceWith(c),c}function e(a,e,r){var o=t("<img>").one("load",function(){r(!0)}).one("error",function(){r(!1)}).appendTo(a).attr("src",e);o.complete&&o.load()}function r(t,a){if(a){var e=t.split(",");t="";for(var r=0,o=e.length;r<o;r++)t+=a+e[r].trim()+(r!==o-1?",":"")}return t}var o="data-src";t.lazy(["pic","picture"],["picture"],function(i,n){if("picture"===i[0].tagName.toLowerCase()){var c=i.find(o),s=i.find("data-img"),d=this.config("imageBase")||"";c.length?(c.each(function(){a(t(this),"source",d)}),1===s.length?(s=a(s,"img",d),s.on("load",function(){n(!0)}).on("error",function(){n(!1)}),s.attr("src",s.attr(o)),this.config("removeAttribute")&&s.removeAttr(o)):i.attr(o)?(e(i,d+i.attr(o),n),this.config("removeAttribute")&&i.removeAttr(o)):n(!1)):i.attr("data-srcset")?(t("<source>").attr({media:i.attr("data-media"),sizes:i.attr("data-sizes"),type:i.attr("data-type"),srcset:r(i.attr("data-srcset"),d)}).appendTo(i),e(i,d+i.attr(o),n),this.config("removeAttribute")&&i.removeAttr(o+" data-srcset data-media data-sizes data-type")):n(!1)}else n(!1)})}(window.jQuery||window.Zepto),function(t){t.lazy(["js","javascript","script"],"script",function(t,a){"script"===t[0].tagName.toLowerCase()?(t.attr("src",t.attr("data-src")),this.config("removeAttribute")&&t.removeAttr("data-src")):a(!1)})}(window.jQuery||window.Zepto),function(t){t.lazy("vimeo",function(t,a){"iframe"===t[0].tagName.toLowerCase()?(t.attr("src","https://player.vimeo.com/video/"+t.attr("data-src")),this.config("removeAttribute")&&t.removeAttr("data-src")):a(!1)})}(window.jQuery||window.Zepto),function(t){t.lazy(["yt","youtube"],function(t,a){if("iframe"===t[0].tagName.toLowerCase()){var e=/1|true/.test(t.attr("data-nocookie"));t.attr("src","https://www.youtube"+(e?"-nocookie":"")+".com/embed/"+t.attr("data-src")+"?rel=0&amp;showinfo=0"),this.config("removeAttribute")&&t.removeAttr("data-src")}else a(!1)})}(window.jQuery||window.Zepto);

View File

@@ -1,4 +1,4 @@
function ajaxCall(endpoint, method, successFunction) {
function ajaxCall(endpoint, method, successFunction, errorFunction, body) {
$.ajax({
// Our sample url to make request
@@ -8,21 +8,82 @@
// Type of Request
type: method,
// data to send to the server
data: body,
dataType: 'json',
contentType: 'application/json',
// Function to call when to
// request is ok
success: function (data) {
var x = JSON.stringify(data);
console.log(x);
//var x = JSON.stringify(data);
//console.log(x);
successFunction(data);
},
// Error handling
error: function (error) {
console.log(`Error ${error}`);
console.log(`Error ${JSON.stringify(error)}`);
if (errorFunction) {
errorFunction(error);
}
}
});
}
function getQueryString(stringName, type) {
const urlParams = new URLSearchParams(window.location.search);
var myParam = urlParams.get(stringName);
switch (type) {
case "int":
if (typeof (Number(myParam)) == 'number') {
return Number(myParam);
} else {
return null;
}
break;
case "string":
if (typeof (myParam) == 'string') {
return encodeURIComponent(myParam);
} else {
return null;
}
default:
return null;
break;
}
}
function setCookie(cname, cvalue, exdays) {
const d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
if (exdays) {
let expires = "expires="+ d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
} else {
document.cookie = cname + "=" + cvalue + ";path=/";
}
}
function getCookie(cname) {
let name = cname + "=";
let decodedCookie = decodeURIComponent(document.cookie);
let ca = decodedCookie.split(';');
for(let i = 0; i <ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
function formatBytes(bytes, decimals = 2) {
if (!+bytes) return '0 Bytes'
@@ -69,7 +130,61 @@ function showDialog(dialogPage, variables) {
modalVariables = variables;
$('#modal-content').load('/pages/dialogs/' + dialogPage + '.html');
$('#modal-content').load('/pages/dialogs/' + dialogPage + '.html?v=' + AppVersion);
}
function closeDialog() {
// Get the modal
var modal = document.getElementById("myModal");
// Get the modal content
var modalContent = document.getElementById("modal-content");
modal.style.display = "none";
modalContent.innerHTML = "";
modalVariables = null;
}
var subModalVariables;
function showSubDialog(dialogPage, variables) {
// Get the modal
var submodal = document.getElementById("myModalSub");
// Get the modal content
var subModalContent = document.getElementById("modal-content-sub");
// Get the button that opens the modal
var subbtn = document.getElementById("romDelete");
// Get the <span> element that closes the modal
var subspan = document.getElementById("modal-close-sub");
// When the user clicks on the button, open the modal
submodal.style.display = "block";
// When the user clicks on <span> (x), close the modal
subspan.onclick = function () {
submodal.style.display = "none";
subModalContent.innerHTML = "";
subModalVariables = null;
}
subModalVariables = variables;
$('#modal-content-sub').load('/pages/dialogs/' + dialogPage + '.html?v=' + AppVersion);
}
function closeSubDialog() {
// Get the modal
var submodal = document.getElementById("myModalSub");
// Get the modal content
var subModalContent = document.getElementById("modal-content-sub");
submodal.style.display = "none";
subModalContent.innerHTML = "";
subModalVariables = null;
}
function randomIntFromInterval(min, max) { // min and max included
@@ -90,13 +205,58 @@ function createTableRow(isHeader, row, rowClass, cellClass) {
var newCell = document.createElement(cellType);
if (typeof(row[i]) != "object") {
newCell.innerHTML = row[i];
newCell.className = cellClass;
} else {
newCell.appendChild(row[i]);
if (Array.isArray(row[i])) {
newCell.innerHTML = row[i][0];
if (row[i][1]) { newCell.className = row[i][1]; }
if (row[i][2]) { newCell.setAttribute('name', row[i][2]); }
} else {
newCell.appendChild(row[i]);
newCell.className = cellClass;
}
}
newCell.className = cellClass;
newRow.appendChild(newCell);
}
return newRow;
}
function hashCode(str) {
var hash = 0;
for (var i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
return hash;
}
function intToRGB(i) {
var c = (i & 0x00FFFFFF)
.toString(16)
.toUpperCase();
return "00000".substring(0, 6 - c.length) + c;
}
function DropDownRenderGameOption(state) {
console.log(JSON.stringify(state));
if (state.loading) {
return state;
}
var response;
if (state.cover) {
response = $(
'<table class="dropdown-div"><tr><td class="dropdown-cover"><img src="https://images.igdb.com/igdb/image/upload/t_cover_small/' + state.cover.value.imageId + '.jpg" /></td><td class="dropdown-label"><span>' + state.text + '</span></td></tr></table>'
);
} else {
response = $(
'<table class="dropdown-div"><tr><td class="dropdown-cover"><img src="/images/unknowngame.png" /></td><td class="dropdown-label"><span>' + state.text + '</span></td></tr></table>'
);
}
return response;
}

View File

@@ -13,6 +13,10 @@
src: url('/fonts/Commodore Pixelized v1.2.ttf');
}
h1 {
margin-bottom: unset;
}
h3 {
border-bottom-style: solid;
/*border-bottom-color: #916b01;*/
@@ -66,6 +70,9 @@ h3 {
border-image: linear-gradient(to right, rgba(255,0,0,1) 0%, rgba(251,255,0,1) 16%, rgba(0,255,250,1) 30%, rgba(0,16,255,1) 46%, rgba(250,0,255,1) 62%, rgba(255,0,0,1) 78%, rgba(255,237,0,1) 90%, rgba(20,255,0,1) 100%) 5;
}
#modal-content {
height: 100%;
}
/* The Close Button */
.close {
@@ -105,11 +112,10 @@ h3 {
height: 30px;
}
#banner_upload {
background-color: white;
position: fixed;
.banner_button {
background-color: transparent;
float: right;
top: 0px;
right: 41px;
height: 40px;
align-items: center;
justify-content: center;
@@ -117,44 +123,24 @@ h3 {
padding-right: 10px;
padding-top: 0px;
padding-bottom: 0px;
margin: 0px;
margin: 0px 0px 0px 0px;
display: flex;
color: black;
color: white;
border-left-color: black;
border-left-width: 1px;
border-left-style: solid;
}
#banner_upload:hover {
.banner_button:hover {
cursor: pointer;
background-color: lightgrey;
background-color: black;
}
#banner_cog {
background-color: white;
position: fixed;
top: 0px;
right: 0px;
width: 40px;
height: 40px;
align-items: center;
justify-content: center;
padding: 0px;
margin: 0px;
display: flex;
}
#banner_cog:hover {
cursor: pointer;
background-color: lightgrey;
}
#banner_system_image {
height: 20px;
width: 20px;
}
#banner_upload_image {
.banner_button_image {
height: 20px;
width: 20px;
margin-right: 5px;
filter: invert(100%);
}
#banner_header {
@@ -167,12 +153,11 @@ h3 {
height: 40px;
right: 0px;
align-items: center;
display: flex;
}
#banner_header_label {
font-family: Commodore64;
display: inline;
display: inline-block;
padding: 10px;
font-size: 16pt;
vertical-align: top;
@@ -180,6 +165,10 @@ h3 {
color: #7c70da;
}
#banner_header_label {
cursor: pointer;
}
#content {
margin-top: 35px;
padding-top: 5px;
@@ -206,7 +195,7 @@ h3 {
padding: 10px;
}
input[type='text'] {
input[type='text'], input[type='number'] {
background-color: #2b2b2b;
color: white;
padding: 5px;
@@ -225,6 +214,15 @@ input[id='filter_panel_search'] {
width: 160px;
}
input[id='filter_panel_userrating_min'] {
width: 50px;
margin-right: 5px;
}
input[id='filter_panel_userrating_max'] {
width: 50px;
}
/* width */
::-webkit-scrollbar {
width: 10px;
@@ -286,23 +284,39 @@ input[id='filter_panel_search'] {
text-align: center;
vertical-align: top;
margin-bottom: 10px;
border-radius: 10px 10px 10px 10px;
-webkit-border-radius: 10px 10px 10px 10px;
-moz-border-radius: 10px 10px 10px 10px;
border: 1px solid transparent;
}
.game_tile:hover {
cursor: pointer;
text-decoration: underline;
background-color: #2b2b2b;
border-radius: 10px 10px 10px 10px;
-webkit-border-radius: 10px 10px 10px 10px;
-moz-border-radius: 10px 10px 10px 10px;
border: 1px solid #2b2b2b;
}
.game_tile_image {
max-width: 200px;
max-height: 200px;
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
-webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
-moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
}
.game_tile_image, .unknown {
background-color: white;
}
.game_tile_image_small {
max-width: 100px;
max-height: 100px;
}
#bgImage {
position: fixed;
top: -30px;
@@ -354,6 +368,9 @@ input[id='filter_panel_search'] {
max-width: 250px;
max-height: 350px;
width: 100%;
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
-webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
-moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
}
.gamegenrelabel {
@@ -367,6 +384,9 @@ input[id='filter_panel_search'] {
max-height: 64px;
margin-right: 20px;
margin-bottom: 20px;
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
-webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
-moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
}
.rating_image_mini {
@@ -381,6 +401,9 @@ input[id='filter_panel_search'] {
padding: 10px;
/*height: 350px;*/
margin-bottom: 20px;
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
-webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
-moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
}
#gamescreenshots_main {
@@ -538,24 +561,27 @@ th {
border-radius: 15px;
font-size: 13px;
font-weight: bold;
color: blue;
color: white;
line-height: 17px;
text-align: center;
border-color: blue;
border-width: 2px;
border-style: solid;
background-color: white;
outline-color: white;
outline-width: 1px;
outline-style: solid;
border-color: white;
background-color: blue;
outline-color: blue;
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
-webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
-moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
}
.properties_button:hover {
cursor: pointer;
color: white;
border-color: white;
background-color: blue;
outline-color: blue;
color: blue;
border-color: blue;
background-color: white;
outline-color: white;
}
#gamedev_logo {
@@ -604,6 +630,11 @@ div[name="properties_toc_item"]:hover {
margin-top: 20px;
}
.select2-container--default .select2-selection--multiple {
background-color: #2b2b2b !important;
color: white !important;
}
.select2-container--open .select2-dropdown--below,
.select2-container--open .select2-dropdown--above {
background: #2b2b2b;
@@ -613,6 +644,29 @@ div[name="properties_toc_item"]:hover {
border: 1px solid #2b2b2b;
}
.select2-selection__choice {
background-color: #2b2b2b !important;
}
.select2-selection__choice__display {
color: white;
}
.select2-results__option--selected {
background-color: black !important;
}
.select2-selection--single .select2-selection__rendered {
background-color: #2b2b2b;
color: white !important;
border: 1px solid #2b2b2b;
}
.select2-search__field {
background-color: #2b2b2b;
color: white;
}
.dropdown-div {
width: 100%;
}
@@ -632,7 +686,7 @@ div[name="properties_toc_item"]:hover {
}
button {
background-color: #888;
background-color: #555;
color: white;
border-width: 1px;
border-color: #555;
@@ -641,10 +695,12 @@ button {
padding-bottom: 5px;
padding-left: 10px;
padding-right: 10px;
margin-right: 3px;
margin-bottom: 3px;
}
button:hover {
background-color: #555;
background-color: #888;
cursor: pointer;
}
@@ -705,4 +761,120 @@ button:disabled {
height: 310px;
margin-top: 10px;
overflow: auto;
}
.legend_box {
display: inline-block;
width: 145px;
height: 50px;
vertical-align: top;
margin-right: 5px;
padding-right: 5px;
margin-top: 10px;
}
.legend_colour {
float: left;
width: 10px;
height: 10px;
margin-top: 3px;
margin-bottom: 10px;
}
.legend_label {
margin-left: 15px;
}
.rom_checkbox_box {
}
.rom_checkbox_box_hidden {
display: none;
}
#rom_edit, #rom_edit_delete {
float: right;
}
#rom_edit_panel {
background-color: rgba(56, 56, 56, 0.3);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
padding: 15px;
margin-bottom: 10px;
}
#game {
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
-webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
-moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
}
#gametitle_criticrating {
float: right;
}
#gametitle_alts {
margin-top: -2px;
}
#gametitle_criticrating_value {
display: inline-block;
font-family: Commodore64;
font-size: 36px;
text-align: center;
margin-right: 2px;
}
#gametitle_criticrating_label {
display: inline-block;
text-align: left;
}
#rom_edit_progressbar {
width: 100%;
height: 10px;
background-color: lightgray;
margin-top: 10px;
}
#rom_edit_progressbar_progress {
height: 10px;
background-color: cyan;
}
.collections_modal {
width: 1000px;
height: 90%;
margin: 25px auto;
/* overflow-x: scroll;*/
position: relative;
}
.collections_preview_platform_header {
background-color: #2b2b2b;
}
.collections_preview_gamecovercell {
text-align: center;
width: 110px;
vertical-align: top;
padding-bottom: 10px;
}
.collections_preview_gametitlecell {
font-weight: bold;
}
.collections_preview_gamedetailcell {
vertical-align: top;
}
.bgalt0 {
background-color: #414040;
}
.bgalt1 {
background-color: transparent;;
}

View File

@@ -14,7 +14,7 @@
<ProjectReference Include="..\gaseous-tools\gaseous-tools.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MySql.Data" Version="8.0.33" />
<PackageReference Include="MySql.Data" Version="8.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>

View File

@@ -13,7 +13,7 @@ namespace gaseous_tools
/// <returns></returns>
static public object ReturnValueIfNull(object? ObjectToCheck, object IfNullValue)
{
if (ObjectToCheck == null)
if (ObjectToCheck == null || ObjectToCheck == System.DBNull.Value)
{
return IfNullValue;
} else

View File

@@ -359,6 +359,22 @@ namespace gaseous_tools
}
}
public string LibraryTempDirectory
{
get
{
return Path.Combine(LibraryRootDirectory, "Temp");
}
}
public string LibraryCollectionsDirectory
{
get
{
return Path.Combine(LibraryRootDirectory, "Collections");
}
}
public string LibraryMetadataDirectory_Platform(Platform platform)
{
string MetadataPath = Path.Combine(LibraryMetadataDirectory, "Platforms", platform.Slug);
@@ -404,6 +420,8 @@ namespace gaseous_tools
if (!Directory.Exists(LibraryBIOSDirectory)) { Directory.CreateDirectory(LibraryBIOSDirectory); }
if (!Directory.Exists(LibraryUploadDirectory)) { Directory.CreateDirectory(LibraryUploadDirectory); }
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_TOSEC)) { Directory.CreateDirectory(LibrarySignatureImportDirectory_TOSEC); }
}

View File

@@ -0,0 +1,86 @@
DROP TABLE IF EXISTS `GameMode`;
CREATE TABLE `gaseous`.`GameMode` (
`Id` BIGINT NOT NULL,
`CreatedAt` DATETIME NULL,
`Checksum` VARCHAR(45) NULL,
`Name` VARCHAR(100) NULL,
`Slug` VARCHAR(100) NULL,
`UpdatedAt` DATETIME NULL,
`Url` VARCHAR(255) NULL,
`dateAdded` DATETIME NULL,
`lastUpdated` DATETIME NULL,
PRIMARY KEY (`Id`)
);
DROP TABLE IF EXISTS `MultiplayerMode`;
CREATE TABLE `MultiplayerMode` (
`Id` bigint NOT NULL,
`CreatedAt` datetime DEFAULT NULL,
`Checksum` varchar(45) DEFAULT NULL,
`CampaignCoop` boolean DEFAULT NULL,
`DropIn` boolean DEFAULT NULL,
`Game` bigint DEFAULT NULL,
`LanCoop` boolean DEFAULT NULL,
`OfflineCoop` boolean DEFAULT NULL,
`OfflineCoopMax` int DEFAULT NULL,
`OfflineMax` int DEFAULT NULL,
`OnlineCoop` boolean DEFAULT NULL,
`OnlineCoopMax` int DEFAULT NULL,
`OnlineMax` int DEFAULT NULL,
`Platform` bigint DEFAULT NULL,
`SplitScreen` boolean DEFAULT NULL,
`SplitScreenOnline` boolean DEFAULT NULL,
`UpdatedAt` datetime DEFAULT NULL,
`dateAdded` datetime DEFAULT NULL,
`lastUpdated` datetime DEFAULT NULL,
PRIMARY KEY (`Id`)
);
DROP TABLE IF EXISTS `PlayerPerspective`;
CREATE TABLE `PlayerPerspective` (
`Id` bigint NOT NULL,
`CreatedAt` datetime DEFAULT NULL,
`Checksum` varchar(45) DEFAULT NULL,
`Name` varchar(100) DEFAULT NULL,
`Slug` varchar(45) DEFAULT NULL,
`UpdatedAt` datetime DEFAULT NULL,
`Url` varchar(255) DEFAULT NULL,
`dateAdded` datetime DEFAULT NULL,
`lastUpdated` datetime DEFAULT NULL,
PRIMARY KEY (`Id`)
);
DROP TABLE IF EXISTS `Theme`;
CREATE TABLE `Theme` (
`Id` bigint NOT NULL,
`CreatedAt` datetime DEFAULT NULL,
`Checksum` varchar(45) DEFAULT NULL,
`Name` varchar(100) DEFAULT NULL,
`Slug` varchar(45) DEFAULT NULL,
`UpdatedAt` datetime DEFAULT NULL,
`Url` varchar(255) DEFAULT NULL,
`dateAdded` datetime DEFAULT NULL,
`lastUpdated` datetime DEFAULT NULL,
PRIMARY KEY (`Id`)
);
DROP TABLE IF EXISTS `RomCollections`;
CREATE TABLE `RomCollections` (
`Id` BIGINT NOT NULL AUTO_INCREMENT,
`Name` VARCHAR(255) NULL,
`Description` LONGTEXT NULL,
`Platforms` JSON NULL,
`Genres` JSON NULL,
`Players` JSON NULL,
`PlayerPerspectives` JSON NULL,
`Themes` JSON NULL,
`MinimumRating` INT NULL,
`MaximumRating` INT NULL,
`MaximumRomsPerPlatform` INT NULL,
`MaximumBytesPerPlatform` BIGINT NULL,
`MaximumCollectionSizeInBytes` BIGINT NULL,
`BuiltStatus` INT NULL,
PRIMARY KEY (`Id`));
ALTER TABLE `gaseous`.`Signatures_Sources`
CHANGE COLUMN `Author` `Author` LONGTEXT NULL DEFAULT NULL ;

View File

@@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MySql.Data" Version="8.0.33" />
<PackageReference Include="MySql.Data" Version="8.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="IGDB" Version="2.3.2" />
</ItemGroup>
@@ -16,6 +16,7 @@
<None Remove="Database\" />
<None Remove="Database\MySQL\" />
<None Remove="Database\MySQL\gaseous-1000.sql" />
<None Remove="Database\MySQL\gaseous-1001.sql" />
</ItemGroup>
<ItemGroup>
<Folder Include="Database\" />
@@ -23,5 +24,6 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Database\MySQL\gaseous-1000.sql" />
<EmbeddedResource Include="Database\MySQL\gaseous-1001.sql" />
</ItemGroup>
</Project>

BIN
screenshots/Emulator.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 MiB

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 MiB

After

Width:  |  Height:  |  Size: 7.4 MiB