diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..e163a90 Binary files /dev/null and b/.DS_Store differ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..507340c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env +WORKDIR /App +EXPOSE 80 + +# Copy everything +COPY . ./ +# Restore as distinct layers +RUN dotnet restore "gaseous-server/gaseous-server.csproj" +# Build and publish a release +RUN dotnet publish "gaseous-server/gaseous-server.csproj" -c Release -o out + +# Build runtime image +FROM mcr.microsoft.com/dotnet/aspnet:7.0 +WORKDIR /App +COPY --from=build-env /App/out . +ENTRYPOINT ["dotnet", "gaseous-server.dll"] diff --git a/Gaseous.sln b/Gaseous.sln index f7adb01..4dd6c98 100644 --- a/Gaseous.sln +++ b/Gaseous.sln @@ -15,7 +15,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-tools", "gaseous-to EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-server", "gaseous-server\gaseous-server.csproj", "{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{B07A4655-A003-416B-A790-ADAA5B548E1A}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{17FA6F12-8532-420C-9489-CB8FDE42137C}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + Dockerfile = Dockerfile + README.MD = README.MD + LICENSE = LICENSE + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..5b4b4e3 --- /dev/null +++ b/README.MD @@ -0,0 +1,97 @@ +# Gaseous Server + +This is the server for the Gaseous system. All your games and metadata are stored within. + +## Requirements +* MySQL Server 8+ +* Internet Game Database API Key. See: https://api-docs.igdb.com/#account-creation + +## Third Party Projects +The following projects are used by Gaseous +* https://dotnet.microsoft.com/en-us/apps/aspnet +* https://github.com/JamesNK/Newtonsoft.Json +* https://www.nuget.org/packages/MySql.Data/8.0.32.1 +* https://github.com/kamranayub/igdb-dotnet + +## Configuration File +When Gaseous-Server is started for the first time, it creates a configuration file at ~/.gaseous-server/config.json if it doesn't exist. Some values can be filled in using environment variables (such as in the case of using docker). + +### DatabaseConfiguration +| Attribute | Environment Variable | +| --------- | -------------------- | +| HostName | dbhost | +| UserName | dbuser | +| Password | dbpass | + +### IGDBConfiguration +| Attribute | Environment Variable | +| --------- | -------------------- | +| ClientId | igdbclientid | +| Secret. | igdbclientsecret | + +### config.json +```json +{ + "DatabaseConfiguration": { + "HostName": "localhost", + "UserName": "gaseous", + "Password": "gaseous", + "DatabaseName": "gaseous", + "Port": 3306 + }, + "IGDBConfiguration": { + "ClientId": "", + "Secret": "" + }, + "LoggingConfiguration": { + "DebugLogging": false, + "LogFormat": "text" + } +} + +``` + +## Deploy with Docker +Dockerfile and docker-compose.yml files have been provided to make deployment of the server as easy as possible. +1. Clone the repo with "git clone https://github.com/gaseous-project/gaseous-server.git" +2. Change into the gaseous-server directory +3. Open the docker-compose.yml file and edit the igdbclientid and igdbclientsecret to the values retrieved from your IGDB account +4. Run the command "docker-compose up -d" +5. Connect to the host on port 5198 + +## Adding Content +While games can be added to the server without them, it is recommended adding some signature DAT files beforehand to allow for better matching of ROM to game. + +These signature DAT files contain a list of titles with hashes for many of the ROM images that have been found by the community. + +Currently only TOSEC is supported, though more will be added. + +### Adding signature DAT files +1. Download the DAT files from the source website. For example; from https://www.tosecdev.org/downloads/category/56-2023-01-23 +2. Extract the archive +3. Copy the DAT files to ~/.gaseous-server/Data/Signatures/TOSEC/ + +### Adding game image files +1. Ensure your game image file is unzipped, and clearly named. Attempting a search for the game name on https://www.igdb.com can help with file naming. If a hash search is unsuccessful, Gaseous will fall back to attempting to search by the file name. +2. Copy the file to ~/.gaseous-server/Data/Import + +Image to game matching follows the following order of operations, stopping the process at the first match: +### Get the file signature +1. Attempt a hash search +2. Attempt to search the signature database for a rom matching the file name - sometimes the hash can not be matched as a highscore table for example was saved to the image +3. Attempt to parse the file name - clues such as the extension being used to define which platform the file belongs to are used to create a search criteria + +### Create a list of search candidates +Before beginning, remove any version numbers. +1. Add the full name of the image +2. Add the name of the image with any " - " replaced by ": " +3. Add the name of the image with text after a " - " removed +4. Add the name of the image with text after a ": " removed + +### Search IGDB for a game match +Loop through each of the search candidates searching using: +1. "where" - exact match as the search candidate +2. "wherefuzzy" - partial match using wildcards +3. "search" - uses a more flexible search method + +Note: that if more than one result is found, the image will be set as "Unknown" as there is no way for Gaseous to know which title is the correct one. \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0c1445d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,39 @@ +version: '2' +services: + gaseous-server: + container_name: gaseous-server + build: + context: ./ + restart: unless-stopped + networks: + - gaseous + depends_on: + - gsdb + ports: + - 5198:80 + volumes: + - gs:/root/.gaseous-server + environment: + - dbhost=gsdb + - dbuser=root + - dbpass=gaseous + - igdbclientid= + - igdbclientsecret= + gsdb: + container_name: gsdb + image: mysql:8 + restart: unless-stopped + networks: + - gaseous + volumes: + - gsdb:/var/lib/mysql + environment: + - MYSQL_ROOT_PASSWORD=gaseous + - MYSQL_USER=gaseous + - MYSQL_PASSWORD=gaseous +networks: + gaseous: + driver: bridge +volumes: + gs: + gsdb: diff --git a/gaseous-romsignatureobject/RomSignatureObject.cs b/gaseous-romsignatureobject/RomSignatureObject.cs index 5c04359..20021d2 100644 --- a/gaseous-romsignatureobject/RomSignatureObject.cs +++ b/gaseous-romsignatureobject/RomSignatureObject.cs @@ -72,6 +72,14 @@ namespace gaseous_romsignatureobject public string? RomTypeMedia { get; set; } public string? MediaLabel { get; set; } + public SignatureSourceType SignatureSource { get; set; } + + public enum SignatureSourceType + { + None = 0, + TOSEC = 1 + } + public enum RomTypes { /// diff --git a/gaseous-server/.DS_Store b/gaseous-server/.DS_Store new file mode 100644 index 0000000..f0d37de Binary files /dev/null and b/gaseous-server/.DS_Store differ diff --git a/gaseous-server/Assets/.DS_Store b/gaseous-server/Assets/.DS_Store new file mode 100644 index 0000000..95b3a7b Binary files /dev/null and b/gaseous-server/Assets/.DS_Store differ diff --git a/gaseous-server/Assets/Ratings/.DS_Store b/gaseous-server/Assets/Ratings/.DS_Store new file mode 100644 index 0000000..fad90d5 Binary files /dev/null and b/gaseous-server/Assets/Ratings/.DS_Store differ diff --git a/gaseous-server/Assets/Ratings/ACB/ACB_G.svg b/gaseous-server/Assets/Ratings/ACB/ACB_G.svg new file mode 100644 index 0000000..85a81e3 --- /dev/null +++ b/gaseous-server/Assets/Ratings/ACB/ACB_G.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gaseous-server/Assets/Ratings/ACB/ACB_M.svg b/gaseous-server/Assets/Ratings/ACB/ACB_M.svg new file mode 100644 index 0000000..744b4b3 --- /dev/null +++ b/gaseous-server/Assets/Ratings/ACB/ACB_M.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/gaseous-server/Assets/Ratings/ACB/ACB_MA15.svg b/gaseous-server/Assets/Ratings/ACB/ACB_MA15.svg new file mode 100644 index 0000000..829d9bc --- /dev/null +++ b/gaseous-server/Assets/Ratings/ACB/ACB_MA15.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gaseous-server/Assets/Ratings/ACB/ACB_PG.svg b/gaseous-server/Assets/Ratings/ACB/ACB_PG.svg new file mode 100644 index 0000000..1b3a8ce --- /dev/null +++ b/gaseous-server/Assets/Ratings/ACB/ACB_PG.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/gaseous-server/Assets/Ratings/ACB/ACB_R18.svg b/gaseous-server/Assets/Ratings/ACB/ACB_R18.svg new file mode 100644 index 0000000..a65c5dd --- /dev/null +++ b/gaseous-server/Assets/Ratings/ACB/ACB_R18.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gaseous-server/Assets/Ratings/ACB/ACB_RC.svg b/gaseous-server/Assets/Ratings/ACB/ACB_RC.svg new file mode 100644 index 0000000..8707732 --- /dev/null +++ b/gaseous-server/Assets/Ratings/ACB/ACB_RC.svg @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/CERO/CERO_A.svg b/gaseous-server/Assets/Ratings/CERO/CERO_A.svg new file mode 100644 index 0000000..fff2608 --- /dev/null +++ b/gaseous-server/Assets/Ratings/CERO/CERO_A.svg @@ -0,0 +1,496 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/CERO/CERO_B.svg b/gaseous-server/Assets/Ratings/CERO/CERO_B.svg new file mode 100644 index 0000000..e4a844a --- /dev/null +++ b/gaseous-server/Assets/Ratings/CERO/CERO_B.svg @@ -0,0 +1,557 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/CERO/CERO_C.svg b/gaseous-server/Assets/Ratings/CERO/CERO_C.svg new file mode 100644 index 0000000..e6896d0 --- /dev/null +++ b/gaseous-server/Assets/Ratings/CERO/CERO_C.svg @@ -0,0 +1,558 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/CERO/CERO_D.svg b/gaseous-server/Assets/Ratings/CERO/CERO_D.svg new file mode 100644 index 0000000..14b1a3e --- /dev/null +++ b/gaseous-server/Assets/Ratings/CERO/CERO_D.svg @@ -0,0 +1,290 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/CERO/CERO_Z.svg b/gaseous-server/Assets/Ratings/CERO/CERO_Z.svg new file mode 100644 index 0000000..a3cc19a --- /dev/null +++ b/gaseous-server/Assets/Ratings/CERO/CERO_Z.svg @@ -0,0 +1,619 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_Eighteen.svg b/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_Eighteen.svg new file mode 100644 index 0000000..6e86f63 --- /dev/null +++ b/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_Eighteen.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_Fourteen.svg b/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_Fourteen.svg new file mode 100644 index 0000000..4f7a6c9 --- /dev/null +++ b/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_Fourteen.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_L.svg b/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_L.svg new file mode 100644 index 0000000..1a85899 --- /dev/null +++ b/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_L.svg @@ -0,0 +1,119 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_Sixteen.svg b/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_Sixteen.svg new file mode 100644 index 0000000..c5beafb --- /dev/null +++ b/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_Sixteen.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_Ten.svg b/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_Ten.svg new file mode 100644 index 0000000..506cfc3 --- /dev/null +++ b/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_Ten.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_Twelve.svg b/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_Twelve.svg new file mode 100644 index 0000000..618a536 --- /dev/null +++ b/gaseous-server/Assets/Ratings/CLASS_IND/CLASS_IND_Twelve.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/ESRB/AO.svg b/gaseous-server/Assets/Ratings/ESRB/AO.svg new file mode 100644 index 0000000..c88be19 --- /dev/null +++ b/gaseous-server/Assets/Ratings/ESRB/AO.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gaseous-server/Assets/Ratings/ESRB/E.svg b/gaseous-server/Assets/Ratings/ESRB/E.svg new file mode 100644 index 0000000..e6f7695 --- /dev/null +++ b/gaseous-server/Assets/Ratings/ESRB/E.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gaseous-server/Assets/Ratings/ESRB/E10.svg b/gaseous-server/Assets/Ratings/ESRB/E10.svg new file mode 100644 index 0000000..664135f --- /dev/null +++ b/gaseous-server/Assets/Ratings/ESRB/E10.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gaseous-server/Assets/Ratings/ESRB/M.svg b/gaseous-server/Assets/Ratings/ESRB/M.svg new file mode 100644 index 0000000..3ae12f7 --- /dev/null +++ b/gaseous-server/Assets/Ratings/ESRB/M.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gaseous-server/Assets/Ratings/ESRB/RP-LM17-English.svg b/gaseous-server/Assets/Ratings/ESRB/RP-LM17-English.svg new file mode 100644 index 0000000..39deca5 --- /dev/null +++ b/gaseous-server/Assets/Ratings/ESRB/RP-LM17-English.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gaseous-server/Assets/Ratings/ESRB/RP.svg b/gaseous-server/Assets/Ratings/ESRB/RP.svg new file mode 100644 index 0000000..1cd0cc7 --- /dev/null +++ b/gaseous-server/Assets/Ratings/ESRB/RP.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gaseous-server/Assets/Ratings/ESRB/T.svg b/gaseous-server/Assets/Ratings/ESRB/T.svg new file mode 100644 index 0000000..31039e6 --- /dev/null +++ b/gaseous-server/Assets/Ratings/ESRB/T.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gaseous-server/Assets/Ratings/GRAC/GRAC_All.svg b/gaseous-server/Assets/Ratings/GRAC/GRAC_All.svg new file mode 100644 index 0000000..a09ab19 --- /dev/null +++ b/gaseous-server/Assets/Ratings/GRAC/GRAC_All.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/GRAC/GRAC_Eighteen.svg b/gaseous-server/Assets/Ratings/GRAC/GRAC_Eighteen.svg new file mode 100644 index 0000000..f296c58 --- /dev/null +++ b/gaseous-server/Assets/Ratings/GRAC/GRAC_Eighteen.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/GRAC/GRAC_Fifteen.svg b/gaseous-server/Assets/Ratings/GRAC/GRAC_Fifteen.svg new file mode 100644 index 0000000..6e997df --- /dev/null +++ b/gaseous-server/Assets/Ratings/GRAC/GRAC_Fifteen.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/GRAC/GRAC_Testing.svg b/gaseous-server/Assets/Ratings/GRAC/GRAC_Testing.svg new file mode 100644 index 0000000..72cbd9b --- /dev/null +++ b/gaseous-server/Assets/Ratings/GRAC/GRAC_Testing.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/GRAC/GRAC_Twelve.svg b/gaseous-server/Assets/Ratings/GRAC/GRAC_Twelve.svg new file mode 100644 index 0000000..2ed7295 --- /dev/null +++ b/gaseous-server/Assets/Ratings/GRAC/GRAC_Twelve.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/PEGI/Eighteen.svg b/gaseous-server/Assets/Ratings/PEGI/Eighteen.svg new file mode 100644 index 0000000..204aa19 --- /dev/null +++ b/gaseous-server/Assets/Ratings/PEGI/Eighteen.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/PEGI/Seven.svg b/gaseous-server/Assets/Ratings/PEGI/Seven.svg new file mode 100644 index 0000000..b2f94d9 --- /dev/null +++ b/gaseous-server/Assets/Ratings/PEGI/Seven.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/PEGI/Sixteen.svg b/gaseous-server/Assets/Ratings/PEGI/Sixteen.svg new file mode 100644 index 0000000..b2eaac9 --- /dev/null +++ b/gaseous-server/Assets/Ratings/PEGI/Sixteen.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/PEGI/Three.svg b/gaseous-server/Assets/Ratings/PEGI/Three.svg new file mode 100644 index 0000000..b627a5d --- /dev/null +++ b/gaseous-server/Assets/Ratings/PEGI/Three.svg @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/PEGI/Twelve.svg b/gaseous-server/Assets/Ratings/PEGI/Twelve.svg new file mode 100644 index 0000000..24aa8d7 --- /dev/null +++ b/gaseous-server/Assets/Ratings/PEGI/Twelve.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/Assets/Ratings/USK/USK_0.svg b/gaseous-server/Assets/Ratings/USK/USK_0.svg new file mode 100644 index 0000000..90f38a6 --- /dev/null +++ b/gaseous-server/Assets/Ratings/USK/USK_0.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gaseous-server/Assets/Ratings/USK/USK_12.svg b/gaseous-server/Assets/Ratings/USK/USK_12.svg new file mode 100644 index 0000000..89b7ce3 --- /dev/null +++ b/gaseous-server/Assets/Ratings/USK/USK_12.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gaseous-server/Assets/Ratings/USK/USK_16.svg b/gaseous-server/Assets/Ratings/USK/USK_16.svg new file mode 100644 index 0000000..9f1b4d7 --- /dev/null +++ b/gaseous-server/Assets/Ratings/USK/USK_16.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gaseous-server/Assets/Ratings/USK/USK_18.svg b/gaseous-server/Assets/Ratings/USK/USK_18.svg new file mode 100644 index 0000000..9b2bd7a --- /dev/null +++ b/gaseous-server/Assets/Ratings/USK/USK_18.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/gaseous-server/Assets/Ratings/USK/USK_6.svg b/gaseous-server/Assets/Ratings/USK/USK_6.svg new file mode 100644 index 0000000..cc8fee8 --- /dev/null +++ b/gaseous-server/Assets/Ratings/USK/USK_6.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gaseous-server/Classes/ImportGames.cs b/gaseous-server/Classes/ImportGames.cs new file mode 100644 index 0000000..cad8337 --- /dev/null +++ b/gaseous-server/Classes/ImportGames.cs @@ -0,0 +1,619 @@ +using System; +using System.Data; +using System.Security.Policy; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using gaseous_tools; +using MySqlX.XDevAPI; +using Org.BouncyCastle.Utilities.IO.Pem; +using static gaseous_server.Classes.Metadata.Games; + +namespace gaseous_server.Classes +{ + public class ImportGames + { + public ImportGames(string ImportPath) + { + if (Directory.Exists(ImportPath)) + { + string[] importContents_Files = Directory.GetFiles(ImportPath); + string[] importContents_Directories = Directory.GetDirectories(ImportPath); + + // import files first + foreach (string importContent in importContents_Files) { + ImportGame.ImportGameFile(importContent); + } + } + else + { + Logging.Log(Logging.LogType.Critical, "Import Games", "The import directory " + ImportPath + " does not exist."); + throw new DirectoryNotFoundException("Invalid path: " + ImportPath); + } + } + + + } + + public class ImportGame + { + public static void ImportGameFile(string GameFileImportPath, bool IsDirectory = false, bool ForceImport = false) + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = ""; + Dictionary dbDict = new Dictionary(); + + string[] SkippableFiles = { + ".DS_STORE", + "desktop.ini" + }; + if (SkippableFiles.Contains(Path.GetFileName(GameFileImportPath), StringComparer.OrdinalIgnoreCase)) + { + Logging.Log(Logging.LogType.Debug, "Import Game", "Skipping item " + GameFileImportPath); + } + else + { + //Logging.Log(Logging.LogType.Information, "Import Game", "Processing item " + GameFileImportPath); + if (IsDirectory == false) + { + FileInfo fi = new FileInfo(GameFileImportPath); + Common.hashObject hash = new Common.hashObject(GameFileImportPath); + + // check to make sure we don't already have this file imported + sql = "SELECT COUNT(Id) AS count FROM Games_Roms WHERE MD5=@md5 AND SHA1=@sha1"; + dbDict.Add("md5", hash.md5hash); + dbDict.Add("sha1", hash.sha1hash); + DataTable importDB = db.ExecuteCMD(sql, dbDict); + if ((Int64)importDB.Rows[0]["count"] > 0) + { + if (!GameFileImportPath.StartsWith(Config.LibraryConfiguration.LibraryImportDirectory)) + { + Logging.Log(Logging.LogType.Warning, "Import Game", " " + GameFileImportPath + " already in database - skipping"); + } + } + else + { + Logging.Log(Logging.LogType.Information, "Import Game", " " + GameFileImportPath + " not in database - processing"); + + // process as a single file + Models.Signatures_Games discoveredSignature = GetFileSignature(hash, fi, GameFileImportPath); + + // get discovered platform + IGDB.Models.Platform determinedPlatform = Metadata.Platforms.GetPlatform(discoveredSignature.Flags.IGDBPlatformId); + if (determinedPlatform == null) + { + determinedPlatform = new IGDB.Models.Platform(); + } + + IGDB.Models.Game determinedGame = SearchForGame(discoveredSignature.Game.Name, discoveredSignature.Flags.IGDBPlatformId); + + // add to database + StoreROM(hash, determinedGame, determinedPlatform, discoveredSignature, GameFileImportPath); + } + } + } + } + + public 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(); + List signatures = sc.GetSignature(hash.md5hash); + if (signatures.Count == 0) + { + // no md5 signature found - try sha1 + signatures = sc.GetSignature("", hash.sha1hash); + } + + Models.Signatures_Games discoveredSignature = new Models.Signatures_Games(); + if (signatures.Count == 1) + { + // only 1 signature found! + discoveredSignature = signatures.ElementAt(0); + gaseous_server.Models.PlatformMapping.GetIGDBPlatformMapping(ref discoveredSignature, fi, false); + } + else if (signatures.Count > 1) + { + // more than one signature found - find one with highest score + foreach (Models.Signatures_Games Sig in signatures) + { + if (Sig.Score > discoveredSignature.Score) + { + discoveredSignature = Sig; + gaseous_server.Models.PlatformMapping.GetIGDBPlatformMapping(ref discoveredSignature, fi, false); + } + } + } + else + { + // no signature match found - try name search + signatures = sc.GetByTosecName(fi.Name); + + if (signatures.Count == 1) + { + // only 1 signature found! + discoveredSignature = signatures.ElementAt(0); + gaseous_server.Models.PlatformMapping.GetIGDBPlatformMapping(ref discoveredSignature, fi, false); + } + else if (signatures.Count > 1) + { + // more than one signature found - find one with highest score + foreach (Models.Signatures_Games Sig in signatures) + { + if (Sig.Score > discoveredSignature.Score) + { + discoveredSignature = Sig; + gaseous_server.Models.PlatformMapping.GetIGDBPlatformMapping(ref discoveredSignature, fi, false); + } + } + } + else + { + // still no search - try alternate method + Models.Signatures_Games.GameItem gi = new Models.Signatures_Games.GameItem(); + Models.Signatures_Games.RomItem ri = new Models.Signatures_Games.RomItem(); + + discoveredSignature.Game = gi; + discoveredSignature.Rom = ri; + + // game title is the file name without the extension or path + gi.Name = Path.GetFileNameWithoutExtension(GameFileImportPath); + + // remove everything after brackets - leaving (hopefully) only the name + if (gi.Name.Contains("(")) + { + gi.Name = gi.Name.Substring(0, gi.Name.IndexOf("(")); + } + + // remove special characters like dashes + gi.Name = gi.Name.Replace("-", ""); + + // guess platform + gaseous_server.Models.PlatformMapping.GetIGDBPlatformMapping(ref discoveredSignature, fi, true); + + // get rom data + ri.Name = Path.GetFileName(GameFileImportPath); + ri.Md5 = hash.md5hash; + ri.Sha1 = hash.sha1hash; + ri.Size = fi.Length; + ri.SignatureSource = Models.Signatures_Games.RomItem.SignatureSourceType.None; + } + } + + Logging.Log(Logging.LogType.Information, "Import Game", " Determined import file as: " + discoveredSignature.Game.Name + " (" + discoveredSignature.Game.Year + ") " + discoveredSignature.Game.System); + + return discoveredSignature; + } + + public static IGDB.Models.Game SearchForGame(string GameName, long PlatformId) + { + // search discovered game - case insensitive exact match first + IGDB.Models.Game determinedGame = new IGDB.Models.Game(); + + List SearchCandidates = GetSearchCandidates(GameName); + + foreach (string SearchCandidate in SearchCandidates) + { + bool GameFound = false; + + Logging.Log(Logging.LogType.Information, "Import Game", " Searching for title: " + SearchCandidate); + + foreach (Metadata.Games.SearchType searchType in Enum.GetValues(typeof(Metadata.Games.SearchType))) + { + Logging.Log(Logging.LogType.Information, "Import Game", " Search type: " + searchType.ToString()); + IGDB.Models.Game[] games = Metadata.Games.SearchForGame(SearchCandidate, PlatformId, searchType); + if (games.Length == 1) + { + // exact match! + determinedGame = Metadata.Games.GetGame((long)games[0].Id, false, false); + Logging.Log(Logging.LogType.Information, "Import Game", " IGDB game: " + determinedGame.Name); + GameFound = true; + break; + } + else if (games.Length > 0) + { + Logging.Log(Logging.LogType.Information, "Import Game", " " + games.Length + " search results found"); + } + else + { + Logging.Log(Logging.LogType.Information, "Import Game", " No search results found"); + } + } + if (GameFound == true) { break; } + } + if (determinedGame == null) + { + determinedGame = new IGDB.Models.Game(); + } + + string destSlug = ""; + if (determinedGame.Id == null) + { + Logging.Log(Logging.LogType.Information, "Import Game", " Unable to determine game"); + } + + return determinedGame; + } + + public static List SearchForGame_GetAll(string GameName, long PlatformId) + { + List searchResults = new List(); + + List SearchCandidates = GetSearchCandidates(GameName); + + foreach (string SearchCandidate in SearchCandidates) + { + foreach (Metadata.Games.SearchType searchType in Enum.GetValues(typeof(Metadata.Games.SearchType))) + { + if ((PlatformId == 0 && searchType == SearchType.searchNoPlatform) || (PlatformId != 0 && searchType != SearchType.searchNoPlatform)) + { + IGDB.Models.Game[] games = Metadata.Games.SearchForGame(SearchCandidate, PlatformId, searchType); + foreach (IGDB.Models.Game foundGame in games) + { + bool gameInResults = false; + foreach (IGDB.Models.Game searchResult in searchResults) + { + if (searchResult.Id == foundGame.Id) + { + gameInResults = true; + } + } + + if (gameInResults == false) + { + searchResults.Add(foundGame); + } + } + } + } + } + + return searchResults; + + } + + private static List GetSearchCandidates(string GameName) + { + // remove version numbers from name + GameName = Regex.Replace(GameName, @"v(\d+\.)?(\d+\.)?(\*|\d+)$", "").Trim(); + GameName = Regex.Replace(GameName, @"Rev (\d+\.)?(\d+\.)?(\*|\d+)$", "").Trim(); + + List SearchCandidates = new List(); + SearchCandidates.Add(GameName); + if (GameName.Contains(" - ")) + { + SearchCandidates.Add(GameName.Replace(" - ", ": ")); + SearchCandidates.Add(GameName.Substring(0, GameName.IndexOf(" - ")).Trim()); + } + if (GameName.Contains(": ")) + { + SearchCandidates.Add(GameName.Substring(0, GameName.IndexOf(": ")).Trim()); + } + + Logging.Log(Logging.LogType.Information, "Import Game", " Search candidates: " + String.Join(", ", SearchCandidates)); + + return SearchCandidates; + } + + public static long StoreROM(Common.hashObject hash, IGDB.Models.Game determinedGame, IGDB.Models.Platform determinedPlatform, Models.Signatures_Games discoveredSignature, string GameFileImportPath, long UpdateId = 0) + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + + string sql = ""; + + Dictionary dbDict = new Dictionary(); + + if (UpdateId == 0) + { + sql = "INSERT INTO Games_Roms (PlatformId, GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Flags, RomType, RomTypeMedia, MediaLabel, Path, MetadataSource) VALUES (@platformid, @gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @flags, @romtype, @romtypemedia, @medialabel, @path, @metadatasource); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + } else + { + sql = "UPDATE Games_Roms SET PlatformId=platformid, GameId=@gameid, Name=@name, Size=@size, DevelopmentStatus=@developmentstatus, Flags=@flags, RomType=@romtype, RomTypeMedia=@romtypemedia, MediaLabel=@medialabel, MetadataSource=@metadatasource WHERE Id=@id;"; + dbDict.Add("id", UpdateId); + } + 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("size", Common.ReturnValueIfNull(discoveredSignature.Rom.Size, 0)); + dbDict.Add("md5", hash.md5hash); + dbDict.Add("sha1", hash.sha1hash); + dbDict.Add("crc", Common.ReturnValueIfNull(discoveredSignature.Rom.Crc, "")); + dbDict.Add("developmentstatus", Common.ReturnValueIfNull(discoveredSignature.Rom.DevelopmentStatus, "")); + dbDict.Add("metadatasource", discoveredSignature.Rom.SignatureSource); + + if (discoveredSignature.Rom.flags != null) + { + if (discoveredSignature.Rom.flags.Count > 0) + { + dbDict.Add("flags", Newtonsoft.Json.JsonConvert.SerializeObject(discoveredSignature.Rom.flags)); + } + else + { + dbDict.Add("flags", "[ ]"); + } + } + else + { + dbDict.Add("flags", "[ ]"); + } + dbDict.Add("romtype", (int)discoveredSignature.Rom.RomType); + dbDict.Add("romtypemedia", Common.ReturnValueIfNull(discoveredSignature.Rom.RomTypeMedia, "")); + dbDict.Add("medialabel", Common.ReturnValueIfNull(discoveredSignature.Rom.MediaLabel, "")); + dbDict.Add("path", GameFileImportPath); + + DataTable romInsert = db.ExecuteCMD(sql, dbDict); + long romId = 0; + if (UpdateId == 0) + { + romId = (long)romInsert.Rows[0][0]; + } else + { + romId = UpdateId; + } + + // move to destination + MoveGameFile(romId); + + return romId; + } + + public static string ComputeROMPath(long RomId) + { + Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId); + + // get metadata + IGDB.Models.Platform platform = gaseous_server.Classes.Metadata.Platforms.GetPlatform(rom.PlatformId); + IGDB.Models.Game game = gaseous_server.Classes.Metadata.Games.GetGame(rom.GameId, false, false); + + // build path + string platformSlug = "Unknown Platform"; + if (platform != null) + { + platformSlug = platform.Slug; + } + string gameSlug = "Unknown Title"; + if (game != null) + { + gameSlug = game.Slug; + } + string DestinationPath = Path.Combine(Config.LibraryConfiguration.LibraryDataDirectory, gameSlug, platformSlug); + if (!Directory.Exists(DestinationPath)) + { + Directory.CreateDirectory(DestinationPath); + } + + string DestinationPathName = Path.Combine(DestinationPath, rom.Name); + + return DestinationPathName; + } + + public static bool MoveGameFile(long RomId) + { + Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId); + string romPath = rom.Path; + + if (File.Exists(romPath)) + { + string DestinationPath = ComputeROMPath(RomId); + + if (romPath == DestinationPath) + { + Logging.Log(Logging.LogType.Debug, "Move Game ROM", "Destination path is the same as the current path - aborting"); + return true; + } + else + { + Logging.Log(Logging.LogType.Information, "Move Game ROM", "Moving " + romPath + " to " + DestinationPath); + if (File.Exists(DestinationPath)) + { + Logging.Log(Logging.LogType.Information, "Move Game ROM", "A file with the same name exists at the destination - aborting"); + return false; + } + else + { + File.Move(romPath, DestinationPath); + + // update the db + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "UPDATE Games_Roms SET Path=@path WHERE Id=@id"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", RomId); + dbDict.Add("path", DestinationPath); + db.ExecuteCMD(sql, dbDict); + + return true; + } + } + } + else + { + Logging.Log(Logging.LogType.Warning, "Move Game ROM", "File " + romPath + " appears to be missing!"); + return false; + } + } + + public static void OrganiseLibrary() + { + Logging.Log(Logging.LogType.Information, "Organise Library", "Starting library organisation"); + + // move rom files to their new location + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT * FROM Games_Roms"; + DataTable romDT = db.ExecuteCMD(sql); + + if (romDT.Rows.Count > 0) + { + foreach (DataRow dr in romDT.Rows) + { + Logging.Log(Logging.LogType.Information, "Organise Library", "Processing ROM " + dr["name"]); + long RomId = (long)dr["id"]; + MoveGameFile(RomId); + } + } + + // clean up empty directories + DeleteOrphanedDirectories(Config.LibraryConfiguration.LibraryDataDirectory); + + Logging.Log(Logging.LogType.Information, "Organise Library", "Finsihed library organisation"); + } + + private static void DeleteOrphanedDirectories(string startLocation) + { + foreach (var directory in Directory.GetDirectories(startLocation)) + { + DeleteOrphanedDirectories(directory); + if (Directory.GetFiles(directory).Length == 0 && + Directory.GetDirectories(directory).Length == 0) + { + Directory.Delete(directory, false); + } + } + } + + public static void LibraryScan() + { + Logging.Log(Logging.LogType.Information, "Library Scan", "Starting library scan"); + + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + + Logging.Log(Logging.LogType.Information, "Library Scan", "Looking for duplicate library files to clean up"); + string duplicateSql = "DELETE r1 FROM Games_Roms r1 INNER JOIN Games_Roms r2 WHERE r1.Id > r2.Id AND r1.MD5 = r2.MD5;"; + db.ExecuteCMD(duplicateSql); + + string sql = "SELECT * FROM Games_Roms ORDER BY `name`"; + DataTable dtRoms = db.ExecuteCMD(sql); + + // clean out database entries in the import folder + if (dtRoms.Rows.Count > 0) + { + for (var i = 0; i < dtRoms.Rows.Count; i++) + { + long romId = (long)dtRoms.Rows[i]["Id"]; + string romPath = (string)dtRoms.Rows[i]["Path"]; + + if (!romPath.StartsWith(Config.LibraryConfiguration.LibraryDataDirectory)) + { + Logging.Log(Logging.LogType.Information, "Library Scan", " Deleting database entry for files with incorrect directory " + romPath); + string deleteSql = "DELETE FROM Games_Roms WHERE Id=@id"; + Dictionary deleteDict = new Dictionary(); + deleteDict.Add("Id", romId); + db.ExecuteCMD(deleteSql, deleteDict); + } + } + } + + sql = "SELECT * FROM Games_Roms ORDER BY `name`"; + dtRoms = db.ExecuteCMD(sql); + + // search for files in the library that aren't in the database + Logging.Log(Logging.LogType.Information, "Library Scan", "Looking for orphaned library files to add"); + string[] LibraryFiles = Directory.GetFiles(Config.LibraryConfiguration.LibraryDataDirectory, "*.*", SearchOption.AllDirectories); + foreach (string LibraryFile in LibraryFiles) + { + Common.hashObject LibraryFileHash = new Common.hashObject(LibraryFile); + + // check if file is in database + bool romFound = false; + for (var i = 0; i < dtRoms.Rows.Count; i++) + { + long romId = (long)dtRoms.Rows[i]["Id"]; + string romPath = (string)dtRoms.Rows[i]["Path"]; + string romMd5 = (string)dtRoms.Rows[i]["MD5"]; + + if ((LibraryFile == romPath) || (LibraryFileHash.md5hash == romMd5)) + { + romFound = true; + break; + } + } + + if (romFound == false) + { + // file is not in database - process it + Common.hashObject hash = new Common.hashObject(LibraryFile); + FileInfo fi = new FileInfo(LibraryFile); + + Models.Signatures_Games sig = GetFileSignature(hash, fi, LibraryFile); + + Logging.Log(Logging.LogType.Information, "Library Scan", " Orphaned file found in library: " + LibraryFile); + + // get discovered platform + IGDB.Models.Platform determinedPlatform = Metadata.Platforms.GetPlatform(sig.Flags.IGDBPlatformId); + if (determinedPlatform == null) + { + determinedPlatform = new IGDB.Models.Platform(); + } + + IGDB.Models.Game determinedGame = SearchForGame(sig.Game.Name, sig.Flags.IGDBPlatformId); + + StoreROM(hash, determinedGame, determinedPlatform, sig, LibraryFile); + } + } + + sql = "SELECT * FROM Games_Roms ORDER BY `name`"; + dtRoms = db.ExecuteCMD(sql); + + // check all roms to see if their local file still exists + Logging.Log(Logging.LogType.Information, "Library Scan", "Checking library files exist on disk"); + if (dtRoms.Rows.Count > 0) + { + for (var i = 0; i < dtRoms.Rows.Count; i++) + { + long romId = (long)dtRoms.Rows[i]["Id"]; + string romPath = (string)dtRoms.Rows[i]["Path"]; + Classes.Roms.GameRomItem.SourceType romMetadataSource = (Classes.Roms.GameRomItem.SourceType)(int)dtRoms.Rows[i]["MetadataSource"]; + + Logging.Log(Logging.LogType.Information, "Library Scan", " Processing ROM at path " + romPath); + + if (File.Exists(romPath)) + { + // file exists, so lets check to make sure the signature was matched, and update if a signature can be found + if (romMetadataSource == Roms.GameRomItem.SourceType.None) + { + Common.hashObject hash = new Common.hashObject + { + md5hash = "", + sha1hash = "" + }; + FileInfo fi = new FileInfo(romPath); + + Models.Signatures_Games sig = GetFileSignature(hash, fi, romPath); + if (sig.Rom.SignatureSource != Models.Signatures_Games.RomItem.SignatureSourceType.None) + { + Logging.Log(Logging.LogType.Information, "Library Scan", " Update signature found for " + romPath); + + // get discovered platform + IGDB.Models.Platform determinedPlatform = Metadata.Platforms.GetPlatform(sig.Flags.IGDBPlatformId); + if (determinedPlatform == null) + { + determinedPlatform = new IGDB.Models.Platform(); + } + + IGDB.Models.Game determinedGame = SearchForGame(sig.Game.Name, sig.Flags.IGDBPlatformId); + + StoreROM(hash, determinedGame, determinedPlatform, sig, romPath, romId); + } + } + + if (romPath != ComputeROMPath(romId)) + { + MoveGameFile(romId); + } + } + else + { + // file doesn't exist where it's supposed to be! delete it from the db + Logging.Log(Logging.LogType.Warning, "Library Scan", " Deleting orphaned database entry for " + romPath); + + string deleteSql = "DELETE FROM Games_Roms WHERE Id = @id"; + Dictionary deleteDict = new Dictionary(); + deleteDict.Add("id", romId); + db.ExecuteCMD(deleteSql, deleteDict); + } + } + } + + Logging.Log(Logging.LogType.Information, "Library Scan", "Library scan completed"); + } + } +} + diff --git a/gaseous-server/Classes/Metadata/AgeRating.cs b/gaseous-server/Classes/Metadata/AgeRating.cs new file mode 100644 index 0000000..47bd6df --- /dev/null +++ b/gaseous-server/Classes/Metadata/AgeRating.cs @@ -0,0 +1,152 @@ +using System; +using System.Reflection; +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 AgeRatings + { + const string fieldList = "fields category,checksum,content_descriptions,rating,rating_cover_url,synopsis;"; + + public AgeRatings() + { + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static AgeRating? GetAgeRatings(long? Id) + { + if ((Id == 0) || (Id == null)) + { + return null; + } + else + { + Task RetVal = _GetAgeRatings(SearchUsing.id, Id); + return RetVal.Result; + } + } + + public static AgeRating GetAgeRatings(string Slug) + { + Task RetVal = _GetAgeRatings(SearchUsing.slug, Slug); + return RetVal.Result; + } + + private static async Task _GetAgeRatings(SearchUsing searchUsing, object searchValue) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("AgeRating", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("AgeRating", (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"); + } + + AgeRating returnValue = new AgeRating(); + switch (cacheStatus) + { + case Storage.CacheStatus.NotPresent: + returnValue = await GetObjectFromServer(WhereClause); + Storage.NewCacheValue(returnValue); + UpdateSubClasses(returnValue); + break; + case Storage.CacheStatus.Expired: + returnValue = await GetObjectFromServer(WhereClause); + Storage.NewCacheValue(returnValue, true); + UpdateSubClasses(returnValue); + break; + case Storage.CacheStatus.Current: + returnValue = Storage.GetCacheValue(returnValue, "id", (long)searchValue); + break; + default: + throw new Exception("How did you get here?"); + } + + return returnValue; + } + + private static void UpdateSubClasses(AgeRating ageRating) + { + if (ageRating.ContentDescriptions != null) + { + foreach (long AgeRatingContentDescriptionId in ageRating.ContentDescriptions.Ids) + { + AgeRatingContentDescription ageRatingContentDescription = AgeRatingContentDescriptions.GetAgeRatingContentDescriptions(AgeRatingContentDescriptionId); + } + } + } + + private enum SearchUsing + { + id, + slug + } + + private static async Task GetObjectFromServer(string WhereClause) + { + // get AgeRatings metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.AgeRating, query: fieldList + " " + WhereClause + ";"); + var result = results.First(); + + return result; + } + + public static GameAgeRating GetConsolidatedAgeRating(long RatingId) + { + GameAgeRating gameAgeRating = new GameAgeRating(); + + AgeRating ageRating = GetAgeRatings(RatingId); + gameAgeRating.Id = (long)ageRating.Id; + gameAgeRating.RatingBoard = (AgeRatingCategory)ageRating.Category; + gameAgeRating.RatingTitle = (AgeRatingTitle)ageRating.Rating; + + List descriptions = new List(); + if (ageRating.ContentDescriptions != null) + { + foreach (long ContentId in ageRating.ContentDescriptions.Ids) + { + AgeRatingContentDescription ageRatingContentDescription = AgeRatingContentDescriptions.GetAgeRatingContentDescriptions(ContentId); + descriptions.Add(ageRatingContentDescription.Description); + } + } + gameAgeRating.Descriptions = descriptions.ToArray(); + + return gameAgeRating; + } + + public class GameAgeRating + { + public long Id { get; set; } + public AgeRatingCategory RatingBoard { get; set; } + public AgeRatingTitle RatingTitle { get; set; } + public string[] Descriptions { get; set; } + } + } +} + diff --git a/gaseous-server/Classes/Metadata/AgeRatingContentDescriptions.cs b/gaseous-server/Classes/Metadata/AgeRatingContentDescriptions.cs new file mode 100644 index 0000000..eeec99c --- /dev/null +++ b/gaseous-server/Classes/Metadata/AgeRatingContentDescriptions.cs @@ -0,0 +1,107 @@ +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 AgeRatingContentDescriptions + { + const string fieldList = "fields category,checksum,description;"; + + public AgeRatingContentDescriptions() + { + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static AgeRatingContentDescription? GetAgeRatingContentDescriptions(long? Id) + { + if ((Id == 0) || (Id == null)) + { + return null; + } + else + { + Task RetVal = _GetAgeRatingContentDescriptions(SearchUsing.id, Id); + return RetVal.Result; + } + } + + public static AgeRatingContentDescription GetAgeRatingContentDescriptions(string Slug) + { + Task RetVal = _GetAgeRatingContentDescriptions(SearchUsing.slug, Slug); + return RetVal.Result; + } + + private static async Task _GetAgeRatingContentDescriptions(SearchUsing searchUsing, object searchValue) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("AgeRatingContentDescription", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("AgeRatingContentDescription", (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"); + } + + AgeRatingContentDescription returnValue = new AgeRatingContentDescription(); + switch (cacheStatus) + { + case Storage.CacheStatus.NotPresent: + returnValue = await GetObjectFromServer(WhereClause); + Storage.NewCacheValue(returnValue); + break; + case Storage.CacheStatus.Expired: + returnValue = await GetObjectFromServer(WhereClause); + Storage.NewCacheValue(returnValue, true); + break; + case Storage.CacheStatus.Current: + returnValue = Storage.GetCacheValue(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 GetObjectFromServer(string WhereClause) + { + // get AgeRatingContentDescriptionContentDescriptions metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.AgeRatingContentDescriptions, query: fieldList + " " + WhereClause + ";"); + var result = results.First(); + + return result; + } + } +} + diff --git a/gaseous-server/Classes/Metadata/AlternativeNames.cs b/gaseous-server/Classes/Metadata/AlternativeNames.cs new file mode 100644 index 0000000..3a5fe91 --- /dev/null +++ b/gaseous-server/Classes/Metadata/AlternativeNames.cs @@ -0,0 +1,107 @@ +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 AlternativeNames + { + const string fieldList = "fields checksum,comment,game,name;"; + + public AlternativeNames() + { + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static AlternativeName? GetAlternativeNames(long? Id) + { + if ((Id == 0) || (Id == null)) + { + return null; + } + else + { + Task RetVal = _GetAlternativeNames(SearchUsing.id, Id); + return RetVal.Result; + } + } + + public static AlternativeName GetAlternativeNames(string Slug) + { + Task RetVal = _GetAlternativeNames(SearchUsing.slug, Slug); + return RetVal.Result; + } + + private static async Task _GetAlternativeNames(SearchUsing searchUsing, object searchValue) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("AlternativeName", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("AlternativeName", (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"); + } + + AlternativeName returnValue = new AlternativeName(); + switch (cacheStatus) + { + case Storage.CacheStatus.NotPresent: + returnValue = await GetObjectFromServer(WhereClause); + Storage.NewCacheValue(returnValue); + break; + case Storage.CacheStatus.Expired: + returnValue = await GetObjectFromServer(WhereClause); + Storage.NewCacheValue(returnValue, true); + break; + case Storage.CacheStatus.Current: + returnValue = Storage.GetCacheValue(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 GetObjectFromServer(string WhereClause) + { + // get AlternativeNames metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.AlternativeNames, query: fieldList + " " + WhereClause + ";"); + var result = results.First(); + + return result; + } + } +} + diff --git a/gaseous-server/Classes/Metadata/Artworks.cs b/gaseous-server/Classes/Metadata/Artworks.cs new file mode 100644 index 0000000..af0e713 --- /dev/null +++ b/gaseous-server/Classes/Metadata/Artworks.cs @@ -0,0 +1,168 @@ +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 Artworks + { + const string fieldList = "fields alpha_channel,animated,checksum,game,height,image_id,url,width;"; + + public Artworks() + { + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static Artwork? GetArtwork(long? Id, string LogoPath) + { + if ((Id == 0) || (Id == null)) + { + return null; + } + else + { + Task RetVal = _GetArtwork(SearchUsing.id, Id, LogoPath); + return RetVal.Result; + } + } + + public static Artwork GetArtwork(string Slug, string LogoPath) + { + Task RetVal = _GetArtwork(SearchUsing.slug, Slug, LogoPath); + return RetVal.Result; + } + + private static async Task _GetArtwork(SearchUsing searchUsing, object searchValue, string LogoPath) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("Artwork", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("Artwork", (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"); + } + + Artwork returnValue = new Artwork(); + bool forceImageDownload = false; + LogoPath = Path.Combine(LogoPath, "Artwork"); + switch (cacheStatus) + { + case Storage.CacheStatus.NotPresent: + returnValue = await GetObjectFromServer(WhereClause, LogoPath); + Storage.NewCacheValue(returnValue); + forceImageDownload = true; + break; + case Storage.CacheStatus.Expired: + returnValue = await GetObjectFromServer(WhereClause, LogoPath); + Storage.NewCacheValue(returnValue, true); + forceImageDownload = true; + break; + case Storage.CacheStatus.Current: + returnValue = Storage.GetCacheValue(returnValue, "id", (long)searchValue); + break; + default: + throw new Exception("How did you get here?"); + } + + if ((!File.Exists(Path.Combine(LogoPath, returnValue.ImageId + ".jpg"))) || forceImageDownload == true) + { + //GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_thumb, returnValue.ImageId); + //GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_logo_med, returnValue.ImageId); + GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_original, returnValue.ImageId); + } + + return returnValue; + } + + private enum SearchUsing + { + id, + slug + } + + private static async Task GetObjectFromServer(string WhereClause, string LogoPath) + { + // get Artwork metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.Artworks, query: fieldList + " " + WhereClause + ";"); + var result = results.First(); + + //GetImageFromServer(result.Url, LogoPath, LogoSize.t_thumb, result.ImageId); + //GetImageFromServer(result.Url, LogoPath, LogoSize.t_logo_med, result.ImageId); + GetImageFromServer(result.Url, LogoPath, LogoSize.t_original, result.ImageId); + + return result; + } + + private static void GetImageFromServer(string Url, string LogoPath, LogoSize logoSize, string ImageId) + { + using (var client = new HttpClient()) + { + string fileName = "Artwork.jpg"; + string extension = "jpg"; + switch (logoSize) + { + case LogoSize.t_thumb: + fileName = "_Thumb"; + extension = "jpg"; + break; + case LogoSize.t_logo_med: + fileName = "_Medium"; + extension = "png"; + break; + case LogoSize.t_original: + fileName = ""; + extension = "png"; + break; + default: + fileName = "Artwork"; + extension = "jpg"; + break; + } + fileName = ImageId + fileName; + string imageUrl = Url.Replace(LogoSize.t_thumb.ToString(), logoSize.ToString()).Replace("jpg", extension); + + using (var s = client.GetStreamAsync("https:" + imageUrl)) + { + if (!Directory.Exists(LogoPath)) { Directory.CreateDirectory(LogoPath); } + using (var fs = new FileStream(Path.Combine(LogoPath, fileName + "." + extension), FileMode.OpenOrCreate)) + { + s.Result.CopyTo(fs); + } + } + } + } + + private enum LogoSize + { + t_thumb, + t_logo_med, + t_original + } + } +} + diff --git a/gaseous-server/Classes/Metadata/Collections.cs b/gaseous-server/Classes/Metadata/Collections.cs new file mode 100644 index 0000000..956ddf6 --- /dev/null +++ b/gaseous-server/Classes/Metadata/Collections.cs @@ -0,0 +1,107 @@ +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 Collections + { + const string fieldList = "fields checksum,created_at,games,name,slug,updated_at,url;"; + + public Collections() + { + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static Collection? GetCollections(long? Id) + { + if ((Id == 0) || (Id == null)) + { + return null; + } + else + { + Task RetVal = _GetCollections(SearchUsing.id, Id); + return RetVal.Result; + } + } + + public static Collection GetCollections(string Slug) + { + Task RetVal = _GetCollections(SearchUsing.slug, Slug); + return RetVal.Result; + } + + private static async Task _GetCollections(SearchUsing searchUsing, object searchValue) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("Collection", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("Collection", (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"); + } + + Collection returnValue = new Collection(); + switch (cacheStatus) + { + case Storage.CacheStatus.NotPresent: + returnValue = await GetObjectFromServer(WhereClause); + Storage.NewCacheValue(returnValue); + break; + case Storage.CacheStatus.Expired: + returnValue = await GetObjectFromServer(WhereClause); + Storage.NewCacheValue(returnValue, true); + break; + case Storage.CacheStatus.Current: + returnValue = Storage.GetCacheValue(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 GetObjectFromServer(string WhereClause) + { + // get Collections metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.Collections, query: fieldList + " " + WhereClause + ";"); + var result = results.First(); + + return result; + } + } +} + diff --git a/gaseous-server/Classes/Metadata/Company.cs b/gaseous-server/Classes/Metadata/Company.cs new file mode 100644 index 0000000..a00e183 --- /dev/null +++ b/gaseous-server/Classes/Metadata/Company.cs @@ -0,0 +1,123 @@ +using System; +using gaseous_tools; +using IGDB; +using IGDB.Models; + +namespace gaseous_server.Classes.Metadata +{ + public class Companies + { + const string fieldList = "fields change_date,change_date_category,changed_company_id,checksum,country,created_at,description,developed,logo,name,parent,published,slug,start_date,start_date_category,updated_at,url,websites;"; + + public Companies() + { + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static Company? GetCompanies(long? Id) + { + if ((Id == 0) || (Id == null)) + { + return null; + } + else + { + Task RetVal = _GetCompanies(SearchUsing.id, Id); + return RetVal.Result; + } + } + + public static Company GetCompanies(string Slug) + { + Task RetVal = _GetCompanies(SearchUsing.slug, Slug); + return RetVal.Result; + } + + private static async Task _GetCompanies(SearchUsing searchUsing, object searchValue) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("Company", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("Company", (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"); + } + + Company returnValue = new Company(); + switch (cacheStatus) + { + case Storage.CacheStatus.NotPresent: + returnValue = await GetObjectFromServer(WhereClause); + if (returnValue != null) { Storage.NewCacheValue(returnValue); } + UpdateSubClasses(returnValue); + break; + case Storage.CacheStatus.Expired: + returnValue = await GetObjectFromServer(WhereClause); + if (returnValue != null) { Storage.NewCacheValue(returnValue, true); } + UpdateSubClasses(returnValue); + break; + case Storage.CacheStatus.Current: + returnValue = Storage.GetCacheValue(returnValue, "id", (long)searchValue); + break; + default: + throw new Exception("How did you get here?"); + } + + return returnValue; + } + + private static void UpdateSubClasses(Company company) + { + if (company.Logo != null) + { + CompanyLogo companyLogo = CompanyLogos.GetCompanyLogo(company.Logo.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Company(company)); + } + } + + private enum SearchUsing + { + id, + slug + } + + private static async Task GetObjectFromServer(string WhereClause) + { + // get Companies metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.Companies, query: fieldList + " " + WhereClause + ";"); + if (results.Length > 0) + { + var result = results.First(); + + return result; + } + else + { + return null; + } + + } + } +} + diff --git a/gaseous-server/Classes/Metadata/CompanyLogos.cs b/gaseous-server/Classes/Metadata/CompanyLogos.cs new file mode 100644 index 0000000..88ec456 --- /dev/null +++ b/gaseous-server/Classes/Metadata/CompanyLogos.cs @@ -0,0 +1,175 @@ +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 CompanyLogos + { + const string fieldList = "fields alpha_channel,animated,checksum,height,image_id,url,width;"; + + public CompanyLogos() + { + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static CompanyLogo? GetCompanyLogo(long? Id, string LogoPath) + { + if ((Id == 0) || (Id == null)) + { + return null; + } + else + { + Task RetVal = _GetCompanyLogo(SearchUsing.id, Id, LogoPath); + return RetVal.Result; + } + } + + public static CompanyLogo GetCompanyLogo(string Slug, string LogoPath) + { + Task RetVal = _GetCompanyLogo(SearchUsing.slug, Slug, LogoPath); + return RetVal.Result; + } + + private static async Task _GetCompanyLogo(SearchUsing searchUsing, object searchValue, string LogoPath) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("CompanyLogo", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("CompanyLogo", (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"); + } + + CompanyLogo returnValue = new CompanyLogo(); + bool forceImageDownload = false; + switch (cacheStatus) + { + case Storage.CacheStatus.NotPresent: + returnValue = await GetObjectFromServer(WhereClause, LogoPath); + if (returnValue != null) + { + Storage.NewCacheValue(returnValue); + forceImageDownload = true; + } + break; + case Storage.CacheStatus.Expired: + returnValue = await GetObjectFromServer(WhereClause, LogoPath); + if (returnValue != null) + { + Storage.NewCacheValue(returnValue, true); + forceImageDownload = true; + } + break; + case Storage.CacheStatus.Current: + returnValue = Storage.GetCacheValue(returnValue, "id", (long)searchValue); + break; + default: + throw new Exception("How did you get here?"); + } + + if (returnValue != null) + { + if ((!File.Exists(Path.Combine(LogoPath, "Logo.jpg"))) || forceImageDownload == true) + { + GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_thumb); + GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_logo_med); + } + } + + return returnValue; + } + + private enum SearchUsing + { + id, + slug + } + + private static async Task GetObjectFromServer(string WhereClause, string LogoPath) + { + // get CompanyLogo metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.CompanyLogos, query: fieldList + " " + WhereClause + ";"); + if (results.Length > 0) + { + var result = results.First(); + + GetImageFromServer(result.Url, LogoPath, LogoSize.t_thumb); + GetImageFromServer(result.Url, LogoPath, LogoSize.t_logo_med); + + return result; + } + else + { + return null; + } + } + + private static void GetImageFromServer(string Url, string LogoPath, LogoSize logoSize) + { + using (var client = new HttpClient()) + { + string fileName = "Logo.jpg"; + string extension = "jpg"; + switch (logoSize) + { + case LogoSize.t_thumb: + fileName = "Logo_Thumb"; + extension = "jpg"; + break; + case LogoSize.t_logo_med: + fileName = "Logo_Medium"; + extension = "png"; + break; + default: + fileName = "Logo"; + extension = "jpg"; + break; + } + string imageUrl = Url.Replace(LogoSize.t_thumb.ToString(), logoSize.ToString()).Replace("jpg", extension); + + using (var s = client.GetStreamAsync("https:" + imageUrl)) + { + if (!Directory.Exists(LogoPath)) { Directory.CreateDirectory(LogoPath); } + using (var fs = new FileStream(Path.Combine(LogoPath, fileName + "." + extension), FileMode.OpenOrCreate)) + { + s.Result.CopyTo(fs); + } + } + } + } + + private enum LogoSize + { + t_thumb, + t_logo_med + } + } +} + diff --git a/gaseous-server/Classes/Metadata/Covers.cs b/gaseous-server/Classes/Metadata/Covers.cs new file mode 100644 index 0000000..e1f63a2 --- /dev/null +++ b/gaseous-server/Classes/Metadata/Covers.cs @@ -0,0 +1,166 @@ +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 Covers + { + const string fieldList = "fields alpha_channel,animated,checksum,game,height,image_id,url,width;"; + + public Covers() + { + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static Cover? GetCover(long? Id, string LogoPath) + { + if ((Id == 0) || (Id == null)) + { + return null; + } + else + { + Task RetVal = _GetCover(SearchUsing.id, Id, LogoPath); + return RetVal.Result; + } + } + + public static Cover GetCover(string Slug, string LogoPath) + { + Task RetVal = _GetCover(SearchUsing.slug, Slug, LogoPath); + return RetVal.Result; + } + + private static async Task _GetCover(SearchUsing searchUsing, object searchValue, string LogoPath) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("Cover", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("Cover", (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"); + } + + Cover returnValue = new Cover(); + bool forceImageDownload = false; + switch (cacheStatus) + { + case Storage.CacheStatus.NotPresent: + returnValue = await GetObjectFromServer(WhereClause, LogoPath); + Storage.NewCacheValue(returnValue); + forceImageDownload = true; + break; + case Storage.CacheStatus.Expired: + returnValue = await GetObjectFromServer(WhereClause, LogoPath); + Storage.NewCacheValue(returnValue, true); + forceImageDownload = true; + break; + case Storage.CacheStatus.Current: + returnValue = Storage.GetCacheValue(returnValue, "id", (long)searchValue); + break; + default: + throw new Exception("How did you get here?"); + } + + if ((!File.Exists(Path.Combine(LogoPath, "Cover.jpg"))) || forceImageDownload == true) + { + //GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_thumb, returnValue.ImageId); + //GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_logo_med, returnValue.ImageId); + GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_original, returnValue.ImageId); + } + + return returnValue; + } + + private enum SearchUsing + { + id, + slug + } + + private static async Task GetObjectFromServer(string WhereClause, string LogoPath) + { + // get Cover metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.Covers, query: fieldList + " " + WhereClause + ";"); + var result = results.First(); + + //GetImageFromServer(result.Url, LogoPath, LogoSize.t_thumb, result.ImageId); + //GetImageFromServer(result.Url, LogoPath, LogoSize.t_logo_med, result.ImageId); + GetImageFromServer(result.Url, LogoPath, LogoSize.t_original, result.ImageId); + + return result; + } + + private static void GetImageFromServer(string Url, string LogoPath, LogoSize logoSize, string ImageId) + { + using (var client = new HttpClient()) + { + string fileName = "Cover.jpg"; + string extension = "jpg"; + switch (logoSize) + { + case LogoSize.t_thumb: + fileName = "Cover_Thumb"; + extension = "jpg"; + break; + case LogoSize.t_logo_med: + fileName = "Cover_Medium"; + extension = "png"; + break; + case LogoSize.t_original: + fileName = "Cover"; + extension = "png"; + break; + default: + fileName = "Cover"; + extension = "jpg"; + break; + } + string imageUrl = Url.Replace(LogoSize.t_thumb.ToString(), logoSize.ToString()).Replace("jpg", extension); + + using (var s = client.GetStreamAsync("https:" + imageUrl)) + { + if (!Directory.Exists(LogoPath)) { Directory.CreateDirectory(LogoPath); } + using (var fs = new FileStream(Path.Combine(LogoPath, fileName + "." + extension), FileMode.OpenOrCreate)) + { + s.Result.CopyTo(fs); + } + } + } + } + + private enum LogoSize + { + t_thumb, + t_logo_med, + t_original + } + } +} + diff --git a/gaseous-server/Classes/Metadata/ExternalGames.cs b/gaseous-server/Classes/Metadata/ExternalGames.cs new file mode 100644 index 0000000..348365d --- /dev/null +++ b/gaseous-server/Classes/Metadata/ExternalGames.cs @@ -0,0 +1,120 @@ +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 ExternalGames + { + const string fieldList = "fields category,checksum,countries,created_at,game,media,name,platform,uid,updated_at,url,year;"; + + public ExternalGames() + { + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static ExternalGame? GetExternalGames(long? Id) + { + if ((Id == 0) || (Id == null)) + { + return null; + } + else + { + Task RetVal = _GetExternalGames(SearchUsing.id, Id); + return RetVal.Result; + } + } + + public static ExternalGame GetExternalGames(string Slug) + { + Task RetVal = _GetExternalGames(SearchUsing.slug, Slug); + return RetVal.Result; + } + + private static async Task _GetExternalGames(SearchUsing searchUsing, object searchValue) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("ExternalGame", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("ExternalGame", (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"); + } + + ExternalGame returnValue = new ExternalGame(); + switch (cacheStatus) + { + case Storage.CacheStatus.NotPresent: + returnValue = await GetObjectFromServer(WhereClause); + if (returnValue != null) + { + Storage.NewCacheValue(returnValue); + } + break; + case Storage.CacheStatus.Expired: + returnValue = await GetObjectFromServer(WhereClause); + if (returnValue != null) + { + Storage.NewCacheValue(returnValue, true); + } + break; + case Storage.CacheStatus.Current: + returnValue = Storage.GetCacheValue(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 GetObjectFromServer(string WhereClause) + { + // get ExternalGames metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.ExternalGames, query: fieldList + " " + WhereClause + ";"); + if (results.Length > 0) + { + var result = results.First(); + + return result; + } + else + { + return null; + } + } + } +} + diff --git a/gaseous-server/Classes/Metadata/Franchises.cs b/gaseous-server/Classes/Metadata/Franchises.cs new file mode 100644 index 0000000..f688ceb --- /dev/null +++ b/gaseous-server/Classes/Metadata/Franchises.cs @@ -0,0 +1,107 @@ +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 Franchises + { + const string fieldList = "fields checksum,created_at,games,name,slug,updated_at,url;"; + + public Franchises() + { + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static Franchise? GetFranchises(long? Id) + { + if ((Id == 0) || (Id == null)) + { + return null; + } + else + { + Task RetVal = _GetFranchises(SearchUsing.id, Id); + return RetVal.Result; + } + } + + public static Franchise GetFranchises(string Slug) + { + Task RetVal = _GetFranchises(SearchUsing.slug, Slug); + return RetVal.Result; + } + + private static async Task _GetFranchises(SearchUsing searchUsing, object searchValue) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("Franchise", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("Franchise", (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"); + } + + Franchise returnValue = new Franchise(); + switch (cacheStatus) + { + case Storage.CacheStatus.NotPresent: + returnValue = await GetObjectFromServer(WhereClause); + Storage.NewCacheValue(returnValue); + break; + case Storage.CacheStatus.Expired: + returnValue = await GetObjectFromServer(WhereClause); + Storage.NewCacheValue(returnValue, true); + break; + case Storage.CacheStatus.Current: + returnValue = Storage.GetCacheValue(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 GetObjectFromServer(string WhereClause) + { + // get FranchiseContentDescriptions metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.Franchies, query: fieldList + " " + WhereClause + ";"); + var result = results.First(); + + return result; + } + } +} + diff --git a/gaseous-server/Classes/Metadata/GameVideos.cs b/gaseous-server/Classes/Metadata/GameVideos.cs new file mode 100644 index 0000000..63c8f21 --- /dev/null +++ b/gaseous-server/Classes/Metadata/GameVideos.cs @@ -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 GamesVideos + { + const string fieldList = "fields checksum,game,name,video_id;"; + + public GamesVideos() + { + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static GameVideo? GetGame_Videos(long? Id) + { + if ((Id == 0) || (Id == null)) + { + return null; + } + else + { + Task RetVal = _GetGame_Videos(SearchUsing.id, Id); + return RetVal.Result; + } + } + + public static GameVideo GetGame_Videos(string Slug) + { + Task RetVal = _GetGame_Videos(SearchUsing.slug, Slug); + return RetVal.Result; + } + + private static async Task _GetGame_Videos(SearchUsing searchUsing, object searchValue) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("GameVideo", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("GameVideo", (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"); + } + + GameVideo returnValue = new GameVideo(); + 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(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 GetObjectFromServer(string WhereClause) + { + // get Game_Videos metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.GameVideos, query: fieldList + " " + WhereClause + ";"); + var result = results.First(); + + return result; + } + } +} + diff --git a/gaseous-server/Classes/Metadata/Games.cs b/gaseous-server/Classes/Metadata/Games.cs new file mode 100644 index 0000000..9a11be1 --- /dev/null +++ b/gaseous-server/Classes/Metadata/Games.cs @@ -0,0 +1,288 @@ +using System; +using System.Data; +using gaseous_tools; +using IGDB; +using IGDB.Models; + +namespace gaseous_server.Classes.Metadata +{ + public class Games + { + const string fieldList = "fields age_ratings,aggregated_rating,aggregated_rating_count,alternative_names,artworks,bundles,category,checksum,collection,cover,created_at,dlcs,expanded_games,expansions,external_games,first_release_date,follows,forks,franchise,franchises,game_engines,game_localizations,game_modes,genres,hypes,involved_companies,keywords,language_supports,multiplayer_modes,name,parent_game,platforms,player_perspectives,ports,rating,rating_count,release_dates,remakes,remasters,screenshots,similar_games,slug,standalone_expansions,status,storyline,summary,tags,themes,total_rating,total_rating_count,updated_at,url,version_parent,version_title,videos,websites;"; + + public Games() + { + + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static Game? GetGame(long Id, bool followSubGames, bool forceRefresh) + { + if (Id == 0) + { + Game returnValue = new Game(); + if (Storage.GetCacheStatus("Game", 0) == Storage.CacheStatus.NotPresent) + { + returnValue = new Game + { + Id = 0, + Name = "Unknown Title", + Slug = "Unknown" + }; + Storage.NewCacheValue(returnValue); + + return returnValue; + } + else + { + return Storage.GetCacheValue(returnValue, "id", 0); + } + } + else + { + Task RetVal = _GetGame(SearchUsing.id, Id, followSubGames, forceRefresh); + return RetVal.Result; + } + } + + public static Game GetGame(string Slug, bool followSubGames, bool forceRefresh) + { + Task RetVal = _GetGame(SearchUsing.slug, Slug, followSubGames, forceRefresh); + return RetVal.Result; + } + + public static Game GetGame(DataRow dataRow) + { + return Storage.BuildCacheObject(new Game(), dataRow); + } + + private static async Task _GetGame(SearchUsing searchUsing, object searchValue, bool followSubGames = false, bool forceRefresh = false) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("Game", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("Game", (string)searchValue); + } + + if (forceRefresh == true) + { + if (cacheStatus == Storage.CacheStatus.Current) { cacheStatus = Storage.CacheStatus.Expired; } + } + + // 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"); + } + + Game returnValue = new Game(); + switch (cacheStatus) + { + case Storage.CacheStatus.NotPresent: + returnValue = await GetObjectFromServer(WhereClause); + Storage.NewCacheValue(returnValue); + UpdateSubClasses(returnValue, followSubGames); + return returnValue; + case Storage.CacheStatus.Expired: + returnValue = await GetObjectFromServer(WhereClause); + Storage.NewCacheValue(returnValue, true); + UpdateSubClasses(returnValue, followSubGames); + return returnValue; + case Storage.CacheStatus.Current: + return Storage.GetCacheValue(returnValue, "id", (long)searchValue); + default: + throw new Exception("How did you get here?"); + } + } + + private static void UpdateSubClasses(Game Game, 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 gamesToFetch = new List(); + 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) + { + foreach (long ExternalGameId in Game.ExternalGames.Ids) + { + ExternalGame GameExternalGame = ExternalGames.GetExternalGames(ExternalGameId); + } + } + + if (Game.Franchise != null) + { + Franchise GameFranchise = Franchises.GetFranchises(Game.Franchise.Id); + } + + if (Game.Franchises != null) + { + foreach (long FranchiseId in Game.Franchises.Ids) + { + Franchise GameFranchise = Franchises.GetFranchises(FranchiseId); + } + } + + if (Game.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.Platforms != null) + { + foreach (long PlatformId in Game.Platforms.Ids) + { + Platform GamePlatform = Platforms.GetPlatform(PlatformId); + } + } + + if (Game.Screenshots != null) + { + foreach (long ScreenshotId in Game.Screenshots.Ids) + { + Screenshot GameScreenshot = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game)); + } + } + + if (Game.Videos != null) + { + foreach (long GameVideoId in Game.Videos.Ids) + { + GameVideo gameVideo = GamesVideos.GetGame_Videos(GameVideoId); + } + } + } + + private enum SearchUsing + { + id, + slug + } + + private static async Task GetObjectFromServer(string WhereClause) + { + // get Game metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.Games, query: fieldList + " " + WhereClause + ";"); + var result = results.First(); + + return result; + } + + public static Game[] SearchForGame(string SearchString, long PlatformId, SearchType searchType) + { + Task games = _SearchForGame(SearchString, PlatformId, searchType); + return games.Result; + } + + private static async Task _SearchForGame(string SearchString, long PlatformId, SearchType searchType) + { + string searchBody = ""; + searchBody += "fields id,name,slug,platforms,summary; "; + switch (searchType) + { + case SearchType.searchNoPlatform: + searchBody += "search \"" + SearchString + "\"; "; + break; + case SearchType.search: + searchBody += "search \"" + SearchString + "\"; "; + searchBody += "where platforms = (" + PlatformId + ");"; + break; + case SearchType.wherefuzzy: + searchBody += "where platforms = (" + PlatformId + ") & name ~ *\"" + SearchString + "\"*;"; + break; + case SearchType.where: + searchBody += "where platforms = (" + PlatformId + ") & name ~ \"" + SearchString + "\";"; + break; + } + + + // get Game metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.Games, query: searchBody); + + return results; + } + + public enum SearchType + { + where = 0, + wherefuzzy = 1, + search = 2, + searchNoPlatform = 3 + } + } +} \ No newline at end of file diff --git a/gaseous-server/Classes/Metadata/Genres.cs b/gaseous-server/Classes/Metadata/Genres.cs new file mode 100644 index 0000000..5995e48 --- /dev/null +++ b/gaseous-server/Classes/Metadata/Genres.cs @@ -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 Genres + { + const string fieldList = "fields checksum,created_at,name,slug,updated_at,url;"; + + public Genres() + { + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static Genre? GetGenres(long? Id) + { + if ((Id == 0) || (Id == null)) + { + return null; + } + else + { + Task RetVal = _GetGenres(SearchUsing.id, Id); + return RetVal.Result; + } + } + + public static Genre GetGenres(string Slug) + { + Task RetVal = _GetGenres(SearchUsing.slug, Slug); + return RetVal.Result; + } + + private static async Task _GetGenres(SearchUsing searchUsing, object searchValue) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("Genre", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("Genre", (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"); + } + + Genre returnValue = new Genre(); + 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(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 GetObjectFromServer(string WhereClause) + { + // get Genres metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.Genres, query: fieldList + " " + WhereClause + ";"); + var result = results.First(); + + return result; + } + } +} + diff --git a/gaseous-server/Classes/Metadata/InvolvedCompany.cs b/gaseous-server/Classes/Metadata/InvolvedCompany.cs new file mode 100644 index 0000000..0d6c3f9 --- /dev/null +++ b/gaseous-server/Classes/Metadata/InvolvedCompany.cs @@ -0,0 +1,126 @@ +using System; +using gaseous_tools; +using IGDB; +using IGDB.Models; + +namespace gaseous_server.Classes.Metadata +{ + public class InvolvedCompanies + { + const string fieldList = "fields *;"; + + public InvolvedCompanies() + { + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static InvolvedCompany? GetInvolvedCompanies(long? Id) + { + if ((Id == 0) || (Id == null)) + { + return null; + } + else + { + Task RetVal = _GetInvolvedCompanies(SearchUsing.id, Id); + return RetVal.Result; + } + } + + public static InvolvedCompany GetInvolvedCompanies(string Slug) + { + Task RetVal = _GetInvolvedCompanies(SearchUsing.slug, Slug); + return RetVal.Result; + } + + private static async Task _GetInvolvedCompanies(SearchUsing searchUsing, object searchValue) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("InvolvedCompany", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("InvolvedCompany", (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"); + } + + InvolvedCompany returnValue = new InvolvedCompany(); + switch (cacheStatus) + { + case Storage.CacheStatus.NotPresent: + returnValue = await GetObjectFromServer(WhereClause); + Storage.NewCacheValue(returnValue); + UpdateSubClasses(returnValue); + break; + case Storage.CacheStatus.Expired: + returnValue = await GetObjectFromServer(WhereClause); + Storage.NewCacheValue(returnValue, true); + UpdateSubClasses(returnValue); + break; + case Storage.CacheStatus.Current: + returnValue = Storage.GetCacheValue(returnValue, "id", (long)searchValue); + break; + default: + throw new Exception("How did you get here?"); + } + + return returnValue; + } + + private static void UpdateSubClasses(InvolvedCompany involvedCompany) + { + if (involvedCompany.Company != null) + { + Company company = Companies.GetCompanies(involvedCompany.Company.Id); + } + } + + private enum SearchUsing + { + id, + slug + } + + private static async Task GetObjectFromServer(string WhereClause) + { + // get InvolvedCompanies metadata + try + { + var results = await igdb.QueryAsync(IGDBClient.Endpoints.InvolvedCompanies, query: fieldList + " " + WhereClause + ";"); + var result = results.First(); + + return result; + } + catch (Exception ex) + { + Logging.Log(Logging.LogType.Critical, "Involved Companies", "Failure when requesting involved companies."); + Logging.Log(Logging.LogType.Critical, "Involved Companies", "Field list: " + fieldList); + Logging.Log(Logging.LogType.Critical, "Involved Companies", "Where clause: " + WhereClause); + Logging.Log(Logging.LogType.Critical, "Involved Companies", "Error", ex); + throw; + } + } + } +} + diff --git a/gaseous-server/Classes/Metadata/PlatformLogos.cs b/gaseous-server/Classes/Metadata/PlatformLogos.cs new file mode 100644 index 0000000..8bf419c --- /dev/null +++ b/gaseous-server/Classes/Metadata/PlatformLogos.cs @@ -0,0 +1,175 @@ +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 PlatformLogos + { + const string fieldList = "fields alpha_channel,animated,checksum,height,image_id,url,width;"; + + public PlatformLogos() + { + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static PlatformLogo? GetPlatformLogo(long? Id, string LogoPath) + { + if ((Id == 0) || (Id == null)) + { + return null; + } + else + { + Task RetVal = _GetPlatformLogo(SearchUsing.id, Id, LogoPath); + return RetVal.Result; + } + } + + public static PlatformLogo GetPlatformLogo(string Slug, string LogoPath) + { + Task RetVal = _GetPlatformLogo(SearchUsing.slug, Slug, LogoPath); + return RetVal.Result; + } + + private static async Task _GetPlatformLogo(SearchUsing searchUsing, object searchValue, string LogoPath) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("PlatformLogo", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("PlatformLogo", (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"); + } + + PlatformLogo returnValue = new PlatformLogo(); + bool forceImageDownload = false; + switch (cacheStatus) + { + case Storage.CacheStatus.NotPresent: + returnValue = await GetObjectFromServer(WhereClause, LogoPath); + if (returnValue != null) + { + Storage.NewCacheValue(returnValue); + forceImageDownload = true; + } + break; + case Storage.CacheStatus.Expired: + returnValue = await GetObjectFromServer(WhereClause, LogoPath); + if (returnValue != null) + { + Storage.NewCacheValue(returnValue, true); + forceImageDownload = true; + } + break; + case Storage.CacheStatus.Current: + returnValue = Storage.GetCacheValue(returnValue, "id", (long)searchValue); + break; + default: + throw new Exception("How did you get here?"); + } + + if (returnValue != null) + { + if ((!File.Exists(Path.Combine(LogoPath, "Logo.jpg"))) || forceImageDownload == true) + { + GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_thumb); + GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_logo_med); + } + } + + return returnValue; + } + + private enum SearchUsing + { + id, + slug + } + + private static async Task GetObjectFromServer(string WhereClause, string LogoPath) + { + // get PlatformLogo metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.PlatformLogos, query: fieldList + " " + WhereClause + ";"); + if (results.Length > 0) + { + var result = results.First(); + + GetImageFromServer(result.Url, LogoPath, LogoSize.t_thumb); + GetImageFromServer(result.Url, LogoPath, LogoSize.t_logo_med); + + return result; + } + else + { + return null; + } + } + + private static void GetImageFromServer(string Url, string LogoPath, LogoSize logoSize) + { + using (var client = new HttpClient()) + { + string fileName = "Logo.jpg"; + string extension = "jpg"; + switch (logoSize) + { + case LogoSize.t_thumb: + fileName = "Logo_Thumb"; + extension = "jpg"; + break; + case LogoSize.t_logo_med: + fileName = "Logo_Medium"; + extension = "png"; + break; + default: + fileName = "Logo"; + extension = "jpg"; + break; + } + string imageUrl = Url.Replace(LogoSize.t_thumb.ToString(), logoSize.ToString()).Replace("jpg", extension); + + using (var s = client.GetStreamAsync("https:" + imageUrl)) + { + if (!Directory.Exists(LogoPath)) { Directory.CreateDirectory(LogoPath); } + using (var fs = new FileStream(Path.Combine(LogoPath, fileName + "." + extension), FileMode.OpenOrCreate)) + { + s.Result.CopyTo(fs); + } + } + } + } + + private enum LogoSize + { + t_thumb, + t_logo_med + } + } +} + diff --git a/gaseous-server/Classes/Metadata/PlatformVersions.cs b/gaseous-server/Classes/Metadata/PlatformVersions.cs new file mode 100644 index 0000000..39b98ff --- /dev/null +++ b/gaseous-server/Classes/Metadata/PlatformVersions.cs @@ -0,0 +1,126 @@ +using System; +using System.Data; +using gaseous_tools; +using IGDB; +using IGDB.Models; + +namespace gaseous_server.Classes.Metadata +{ + public class PlatformVersions + { + const string fieldList = "fields checksum,companies,connectivity,cpu,graphics,main_manufacturer,media,memory,name,online,os,output,platform_logo,platform_version_release_dates,resolutions,slug,sound,storage,summary,url;"; + + public PlatformVersions() + { + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static PlatformVersion? GetPlatformVersion(long Id, Platform ParentPlatform) + { + if (Id == 0) + { + return null; + } + else + { + Task RetVal = _GetPlatformVersion(SearchUsing.id, Id, ParentPlatform); + return RetVal.Result; + } + } + + public static PlatformVersion GetPlatformVersion(string Slug, Platform ParentPlatform) + { + Task RetVal = _GetPlatformVersion(SearchUsing.slug, Slug, ParentPlatform); + return RetVal.Result; + } + + private static async Task _GetPlatformVersion(SearchUsing searchUsing, object searchValue, Platform ParentPlatform) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("PlatformVersion", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("PlatformVersion", (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"); + } + + PlatformVersion returnValue = new PlatformVersion(); + switch (cacheStatus) + { + case Storage.CacheStatus.NotPresent: + returnValue = await GetObjectFromServer(WhereClause); + if (returnValue != null) + { + Storage.NewCacheValue(returnValue); + UpdateSubClasses(ParentPlatform, returnValue); + } + return returnValue; + case Storage.CacheStatus.Expired: + returnValue = await GetObjectFromServer(WhereClause); + if (returnValue != null) + { + Storage.NewCacheValue(returnValue, true); + UpdateSubClasses(ParentPlatform, returnValue); + } + return returnValue; + case Storage.CacheStatus.Current: + return Storage.GetCacheValue(returnValue, "id", (long)searchValue); + default: + throw new Exception("How did you get here?"); + } + } + + private static void UpdateSubClasses(Platform ParentPlatform, PlatformVersion platformVersion) + { + if (platformVersion.PlatformLogo != null) + { + PlatformLogo platformLogo = PlatformLogos.GetPlatformLogo(platformVersion.PlatformLogo.Id, Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(ParentPlatform), "Versions", platformVersion.Slug)); + } + } + + private enum SearchUsing + { + id, + slug + } + + private static async Task GetObjectFromServer(string WhereClause) + { + // get PlatformVersion metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.PlatformVersions, query: fieldList + " " + WhereClause + ";"); + if (results.Length > 0) + { + var result = results.First(); + + return result; + } + else + { + return null; + } + } + } +} + diff --git a/gaseous-server/Classes/Metadata/Platforms.cs b/gaseous-server/Classes/Metadata/Platforms.cs new file mode 100644 index 0000000..b61969b --- /dev/null +++ b/gaseous-server/Classes/Metadata/Platforms.cs @@ -0,0 +1,139 @@ +using System; +using System.Data; +using System.Net; +using gaseous_tools; +using IGDB; +using IGDB.Models; + +namespace gaseous_server.Classes.Metadata +{ + public class Platforms + { + const string fieldList = "fields abbreviation,alternative_name,category,checksum,created_at,generation,name,platform_family,platform_logo,slug,summary,updated_at,url,versions,websites;"; + + public Platforms() + { + + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static Platform? GetPlatform(long Id) + { + if (Id == 0) + { + Platform returnValue = new Platform(); + if (Storage.GetCacheStatus("Platform", 0) == Storage.CacheStatus.NotPresent) + { + returnValue = new Platform + { + Id = 0, + Name = "Unknown Platform", + Slug = "Unknown" + }; + Storage.NewCacheValue(returnValue); + + return returnValue; + } + else + { + return Storage.GetCacheValue(returnValue, "id", 0); + } + } + else + { + Task RetVal = _GetPlatform(SearchUsing.id, Id); + return RetVal.Result; + } + } + + public static Platform GetPlatform(string Slug) + { + Task RetVal = _GetPlatform(SearchUsing.slug, Slug); + return RetVal.Result; + } + + private static async Task _GetPlatform(SearchUsing searchUsing, object searchValue) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("Platform", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("Platform", (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"); + } + + Platform returnValue = new Platform(); + switch (cacheStatus) + { + case Storage.CacheStatus.NotPresent: + returnValue = await GetObjectFromServer(WhereClause); + Storage.NewCacheValue(returnValue); + UpdateSubClasses(returnValue); + return returnValue; + case Storage.CacheStatus.Expired: + returnValue = await GetObjectFromServer(WhereClause); + Storage.NewCacheValue(returnValue, true); + UpdateSubClasses(returnValue); + return returnValue; + case Storage.CacheStatus.Current: + return Storage.GetCacheValue(returnValue, "id", (long)searchValue); + default: + throw new Exception("How did you get here?"); + } + } + + private static void UpdateSubClasses(Platform platform) + { + if (platform.Versions != null) + { + foreach (long PlatformVersionId in platform.Versions.Ids) + { + PlatformVersion platformVersion = PlatformVersions.GetPlatformVersion(PlatformVersionId, platform); + } + } + + if (platform.PlatformLogo != null) + { + PlatformLogo platformLogo = PlatformLogos.GetPlatformLogo(platform.PlatformLogo.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(platform)); + } + } + + private enum SearchUsing + { + id, + slug + } + + private static async Task GetObjectFromServer(string WhereClause) + { + // get platform metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.Platforms, query: fieldList + " " + WhereClause + ";"); + var result = results.First(); + + return result; + } + } +} + diff --git a/gaseous-server/Classes/Metadata/Screenshots.cs b/gaseous-server/Classes/Metadata/Screenshots.cs new file mode 100644 index 0000000..6165353 --- /dev/null +++ b/gaseous-server/Classes/Metadata/Screenshots.cs @@ -0,0 +1,168 @@ +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 Screenshots + { + const string fieldList = "fields alpha_channel,animated,checksum,game,height,image_id,url,width;"; + + public Screenshots() + { + } + + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + public static Screenshot? GetScreenshot(long? Id, string LogoPath) + { + if ((Id == 0) || (Id == null)) + { + return null; + } + else + { + Task RetVal = _GetScreenshot(SearchUsing.id, Id, LogoPath); + return RetVal.Result; + } + } + + public static Screenshot GetScreenshot(string Slug, string LogoPath) + { + Task RetVal = _GetScreenshot(SearchUsing.slug, Slug, LogoPath); + return RetVal.Result; + } + + private static async Task _GetScreenshot(SearchUsing searchUsing, object searchValue, string LogoPath) + { + // check database first + Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); + if (searchUsing == SearchUsing.id) + { + cacheStatus = Storage.GetCacheStatus("Screenshot", (long)searchValue); + } + else + { + cacheStatus = Storage.GetCacheStatus("Screenshot", (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"); + } + + Screenshot returnValue = new Screenshot(); + bool forceImageDownload = false; + LogoPath = Path.Combine(LogoPath, "Screenshots"); + switch (cacheStatus) + { + case Storage.CacheStatus.NotPresent: + returnValue = await GetObjectFromServer(WhereClause, LogoPath); + Storage.NewCacheValue(returnValue); + forceImageDownload = true; + break; + case Storage.CacheStatus.Expired: + returnValue = await GetObjectFromServer(WhereClause, LogoPath); + Storage.NewCacheValue(returnValue, true); + forceImageDownload = true; + break; + case Storage.CacheStatus.Current: + returnValue = Storage.GetCacheValue(returnValue, "id", (long)searchValue); + break; + default: + throw new Exception("How did you get here?"); + } + + if ((!File.Exists(Path.Combine(LogoPath, "Screenshot.jpg"))) || forceImageDownload == true) + { + //GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_thumb, returnValue.ImageId); + //GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_logo_med, returnValue.ImageId); + GetImageFromServer(returnValue.Url, LogoPath, LogoSize.t_original, returnValue.ImageId); + } + + return returnValue; + } + + private enum SearchUsing + { + id, + slug + } + + private static async Task GetObjectFromServer(string WhereClause, string LogoPath) + { + // get Screenshot metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.Screenshots, query: fieldList + " " + WhereClause + ";"); + var result = results.First(); + + //GetImageFromServer(result.Url, LogoPath, LogoSize.t_thumb, result.ImageId); + //GetImageFromServer(result.Url, LogoPath, LogoSize.t_logo_med, result.ImageId); + GetImageFromServer(result.Url, LogoPath, LogoSize.t_original, result.ImageId); + + return result; + } + + private static void GetImageFromServer(string Url, string LogoPath, LogoSize logoSize, string ImageId) + { + using (var client = new HttpClient()) + { + string fileName = "Artwork.jpg"; + string extension = "jpg"; + switch (logoSize) + { + case LogoSize.t_thumb: + fileName = "_Thumb"; + extension = "jpg"; + break; + case LogoSize.t_logo_med: + fileName = "_Medium"; + extension = "png"; + break; + case LogoSize.t_original: + fileName = ""; + extension = "png"; + break; + default: + fileName = "Artwork"; + extension = "jpg"; + break; + } + fileName = ImageId + fileName; + string imageUrl = Url.Replace(LogoSize.t_thumb.ToString(), logoSize.ToString()).Replace("jpg", extension); + + using (var s = client.GetStreamAsync("https:" + imageUrl)) + { + if (!Directory.Exists(LogoPath)) { Directory.CreateDirectory(LogoPath); } + using (var fs = new FileStream(Path.Combine(LogoPath, fileName + "." + extension), FileMode.OpenOrCreate)) + { + s.Result.CopyTo(fs); + } + } + } + } + + private enum LogoSize + { + t_thumb, + t_logo_med, + t_original + } + } +} + diff --git a/gaseous-server/Classes/Metadata/Storage.cs b/gaseous-server/Classes/Metadata/Storage.cs new file mode 100644 index 0000000..1f10712 --- /dev/null +++ b/gaseous-server/Classes/Metadata/Storage.cs @@ -0,0 +1,385 @@ +using System; +using System.Data; +using System.Reflection; +using gaseous_tools; +using IGDB; +using IGDB.Models; + +namespace gaseous_server.Classes.Metadata +{ + public class Storage + { + public enum CacheStatus + { + NotPresent, + Current, + Expired + } + + public static CacheStatus GetCacheStatus(string Endpoint, string Slug) + { + return _GetCacheStatus(Endpoint, "slug", Slug); + } + + public static CacheStatus GetCacheStatus(string Endpoint, long Id) + { + return _GetCacheStatus(Endpoint, "id", Id); + } + + public static CacheStatus GetCacheStatus(DataRow Row) + { + if (Row.Table.Columns.Contains("lastUpdated")) + { + DateTime CacheExpiryTime = DateTime.UtcNow.AddHours(-168); + if ((DateTime)Row["lastUpdated"] < CacheExpiryTime) + { + return CacheStatus.Expired; + } + else + { + return CacheStatus.Current; + } + } + else + { + throw new Exception("No lastUpdated column!"); + } + } + + private static CacheStatus _GetCacheStatus(string Endpoint, string SearchField, object SearchValue) + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + + string sql = "SELECT lastUpdated FROM " + Endpoint + " WHERE " + SearchField + " = @" + SearchField; + + Dictionary dbDict = new Dictionary(); + dbDict.Add("Endpoint", Endpoint); + dbDict.Add(SearchField, SearchValue); + + DataTable dt = db.ExecuteCMD(sql, dbDict); + if (dt.Rows.Count == 0) + { + // no data stored for this item, or lastUpdated + return CacheStatus.NotPresent; + } + else + { + DateTime CacheExpiryTime = DateTime.UtcNow.AddHours(-168); + if ((DateTime)dt.Rows[0]["lastUpdated"] < CacheExpiryTime) + { + return CacheStatus.Expired; + } + else + { + return CacheStatus.Current; + } + } + } + + public static void NewCacheValue(object ObjectToCache, bool UpdateRecord = false) + { + // get the object type name + string ObjectTypeName = ObjectToCache.GetType().Name; + + // build dictionary + string objectJson = Newtonsoft.Json.JsonConvert.SerializeObject(ObjectToCache); + Dictionary objectDict = Newtonsoft.Json.JsonConvert.DeserializeObject>(objectJson); + objectDict.Add("dateAdded", DateTime.UtcNow); + objectDict.Add("lastUpdated", DateTime.UtcNow); + + // generate sql + string fieldList = ""; + string valueList = ""; + string updateFieldValueList = ""; + foreach (KeyValuePair key in objectDict) + { + if (fieldList.Length > 0) + { + fieldList = fieldList + ", "; + valueList = valueList + ", "; + } + fieldList = fieldList + key.Key; + valueList = valueList + "@" + key.Key; + if ((key.Key != "id") && (key.Key != "dateAdded")) + { + if (updateFieldValueList.Length > 0) + { + updateFieldValueList = updateFieldValueList + ", "; + } + updateFieldValueList += key.Key + " = @" + key.Key; + } + + // check property type + Type objectType = ObjectToCache.GetType(); + if (objectType != null) + { + PropertyInfo objectProperty = objectType.GetProperty(key.Key); + if (objectProperty != null) + { + string compareName = objectProperty.PropertyType.Name.ToLower().Split("`")[0]; + var objectValue = objectProperty.GetValue(ObjectToCache); + if (objectValue != null) + { + string newObjectValue; + Dictionary newDict; + switch (compareName) + { + case "identityorvalue": + newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectValue); + newDict = Newtonsoft.Json.JsonConvert.DeserializeObject>(newObjectValue); + objectDict[key.Key] = newDict["Id"]; + break; + case "identitiesorvalues": + newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectValue); + newDict = Newtonsoft.Json.JsonConvert.DeserializeObject>(newObjectValue); + newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(newDict["Ids"]); + objectDict[key.Key] = newObjectValue; + break; + case "int32[]": + newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectValue); + objectDict[key.Key] = newObjectValue; + break; + } + } + } + } + } + + string sql = ""; + if (UpdateRecord == false) + { + sql = "INSERT INTO " + ObjectTypeName + " (" + fieldList + ") VALUES (" + valueList + ")"; + } + else + { + sql = "UPDATE " + ObjectTypeName + " SET " + updateFieldValueList + " WHERE Id = @Id"; + } + + // execute sql + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + db.ExecuteCMD(sql, objectDict); + } + + public static T GetCacheValue(T EndpointType, string SearchField, object SearchValue) + { + string Endpoint = EndpointType.GetType().Name; + + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + + string sql = "SELECT * FROM " + Endpoint + " WHERE " + SearchField + " = @" + SearchField; + + Dictionary dbDict = new Dictionary(); + dbDict.Add("Endpoint", Endpoint); + dbDict.Add(SearchField, SearchValue); + + DataTable dt = db.ExecuteCMD(sql, dbDict); + if (dt.Rows.Count == 0) + { + // no data stored for this item + throw new Exception("No record found that matches endpoint " + Endpoint + " with search value " + SearchValue); + } + else + { + DataRow dataRow = dt.Rows[0]; + return BuildCacheObject(EndpointType, dataRow); + } + } + + public static T BuildCacheObject(T EndpointType, DataRow dataRow) + { + foreach (PropertyInfo property in EndpointType.GetType().GetProperties()) + { + if (dataRow.Table.Columns.Contains(property.Name)) + { + if (dataRow[property.Name] != DBNull.Value) + { + string objectTypeName = property.PropertyType.Name.ToLower().Split("`")[0]; + string subObjectTypeName = ""; + object? objectToStore = null; + if (objectTypeName == "nullable") + { + objectTypeName = property.PropertyType.UnderlyingSystemType.ToString().Split("`1")[1].Replace("[System.", "").Replace("]", "").ToLower(); + } + try + { + switch (objectTypeName) + { + //case "boolean": + // Boolean storedBool = Convert.ToBoolean((int)dataRow[property.Name]); + // property.SetValue(EndpointType, storedBool); + // break; + case "datetimeoffset": + DateTimeOffset? storedDate = (DateTime?)dataRow[property.Name]; + property.SetValue(EndpointType, storedDate); + break; + //case "nullable": + // Console.WriteLine("Nullable: " + property.PropertyType.UnderlyingSystemType); + // break; + case "identityorvalue": + subObjectTypeName = property.PropertyType.UnderlyingSystemType.ToString().Split("`1")[1].Replace("[IGDB.Models.", "").Replace("]", "").ToLower(); + + switch (subObjectTypeName) + { + case "collection": + objectToStore = new IdentityOrValue(id: (long)dataRow[property.Name]); + break; + case "company": + objectToStore = new IdentityOrValue(id: (long)dataRow[property.Name]); + break; + case "cover": + objectToStore = new IdentityOrValue(id: (long)dataRow[property.Name]); + break; + case "franchise": + objectToStore = new IdentityOrValue(id: (long)dataRow[property.Name]); + break; + case "game": + objectToStore = new IdentityOrValue(id: (long)dataRow[property.Name]); + break; + case "platformfamily": + objectToStore = new IdentityOrValue(id: (long)dataRow[property.Name]); + break; + case "platformlogo": + objectToStore = new IdentityOrValue(id: (long)dataRow[property.Name]); + break; + case "platformversioncompany": + objectToStore = new IdentityOrValue(id: (long)dataRow[property.Name]); + break; + } + + if (objectToStore != null) + { + property.SetValue(EndpointType, objectToStore); + } + + break; + case "identitiesorvalues": + subObjectTypeName = property.PropertyType.UnderlyingSystemType.ToString().Split("`1")[1].Replace("[IGDB.Models.", "").Replace("]", "").ToLower(); + + long[] fromJsonObject = Newtonsoft.Json.JsonConvert.DeserializeObject((string)dataRow[property.Name]); + + switch (subObjectTypeName) + { + case "agerating": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "alternativename": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "artwork": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "ageratingcontentdescription": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "game": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "externalgame": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "franchise": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "gameengine": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "gamemode": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "gamevideo": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "genre": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "involvedcompany": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "multiplayermode": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "platform": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "platformversion": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "platformwebsite": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "platformversioncompany": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "platformversionreleasedate": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "playerperspective": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "releasedate": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "screenshot": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "theme": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + case "website": + objectToStore = new IdentitiesOrValues(ids: fromJsonObject); + break; + } + + if (objectToStore != null) + { + property.SetValue(EndpointType, objectToStore); + } + + break; + case "int32[]": + Int32[] fromJsonObject_int32Array = Newtonsoft.Json.JsonConvert.DeserializeObject((string)dataRow[property.Name]); + if (fromJsonObject_int32Array != null) + { + property.SetValue(EndpointType, fromJsonObject_int32Array); + } + break; + case "[igdb.models.category": + property.SetValue(EndpointType, (Category)dataRow[property.Name]); + break; + case "[igdb.models.gamestatus": + property.SetValue(EndpointType, (GameStatus)dataRow[property.Name]); + break; + case "[igdb.models.ageratingcategory": + property.SetValue(EndpointType, (AgeRatingCategory)dataRow[property.Name]); + break; + case "[igdb.models.ageratingcontentdescriptioncategory": + property.SetValue(EndpointType, (AgeRatingContentDescriptionCategory)dataRow[property.Name]); + break; + case "[igdb.models.ageratingtitle": + property.SetValue(EndpointType, (AgeRatingTitle)dataRow[property.Name]); + break; + case "[igdb.models.externalcategory": + property.SetValue(EndpointType, (ExternalCategory)dataRow[property.Name]); + break; + case "[igdb.models.startdatecategory": + property.SetValue(EndpointType, (StartDateCategory)dataRow[property.Name]); + break; + default: + property.SetValue(EndpointType, dataRow[property.Name]); + break; + } + } + catch (Exception ex) + { + Console.WriteLine("Error occurred in column " + property.Name); + Console.WriteLine(ex.ToString()); + } + } + } + } + + return EndpointType; + } + } +} + diff --git a/gaseous-server/Classes/MetadataManagement.cs b/gaseous-server/Classes/MetadataManagement.cs new file mode 100644 index 0000000..62e66f7 --- /dev/null +++ b/gaseous-server/Classes/MetadataManagement.cs @@ -0,0 +1,30 @@ +using System; +using System.Data; +using gaseous_tools; + +namespace gaseous_server.Classes +{ + public class MetadataManagement + { + public static void RefreshMetadata(bool forceRefresh = false) + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT Id, `Name` FROM Game;"; + DataTable dt = db.ExecuteCMD(sql); + + foreach (DataRow dr in dt.Rows) + { + try + { + Logging.Log(Logging.LogType.Information, "Metadata Refresh", "Refreshing metadata for game " + dr["name"] + " (" + dr["id"] + ")"); + Metadata.Games.GetGame((long)dr["id"], true, forceRefresh); + } + catch (Exception ex) + { + Logging.Log(Logging.LogType.Critical, "Metadata Refresh", "An error occurred while refreshing metadata for " + dr["name"], ex); + } + } + } + } +} + diff --git a/gaseous-server/Classes/Roms.cs b/gaseous-server/Classes/Roms.cs new file mode 100644 index 0000000..a22b3e4 --- /dev/null +++ b/gaseous-server/Classes/Roms.cs @@ -0,0 +1,140 @@ +using System; +using System.Data; +using gaseous_tools; + +namespace gaseous_server.Classes +{ + public class Roms + { + public static List GetRoms(long GameId) + { + 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 dbDict = new Dictionary(); + dbDict.Add("id", GameId); + DataTable romDT = db.ExecuteCMD(sql, dbDict); + + if (romDT.Rows.Count > 0) + { + List romItems = new List(); + foreach (DataRow romDR in romDT.Rows) + { + romItems.Add(BuildRom(romDR)); + } + + return romItems; + } + else + { + throw new Exception("Unknown Game Id"); + } + } + + public static GameRomItem GetRom(long RomId) + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT * FROM Games_Roms WHERE Id = @id"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", RomId); + DataTable romDT = db.ExecuteCMD(sql, dbDict); + + if (romDT.Rows.Count > 0) + { + DataRow romDR = romDT.Rows[0]; + GameRomItem romItem = BuildRom(romDR); + return romItem; + } + else + { + throw new Exception("Unknown ROM Id"); + } + } + + public static GameRomItem UpdateRom(long RomId, long PlatformId, long GameId) + { + // ensure metadata for platformid is present + 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); + + 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"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", RomId); + dbDict.Add("platformid", PlatformId); + dbDict.Add("gameid", GameId); + db.ExecuteCMD(sql, dbDict); + + GameRomItem rom = GetRom(RomId); + + return rom; + } + + public static void DeleteRom(long RomId) + { + GameRomItem rom = GetRom(RomId); + if (File.Exists(rom.Path)) + { + File.Delete(rom.Path); + } + + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "DELETE FROM Games_Roms WHERE Id = @id"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", RomId); + db.ExecuteCMD(sql, dbDict); + } + + private static GameRomItem BuildRom(DataRow romDR) + { + GameRomItem romItem = new GameRomItem + { + Id = (long)romDR["id"], + PlatformId = (long)romDR["platformid"], + Platform = Classes.Metadata.Platforms.GetPlatform((long)romDR["platformid"]), + GameId = (long)romDR["gameid"], + Name = (string)romDR["name"], + Size = (long)romDR["size"], + CRC = (string)romDR["crc"], + MD5 = (string)romDR["md5"], + SHA1 = (string)romDR["sha1"], + DevelopmentStatus = (string)romDR["developmentstatus"], + Flags = Newtonsoft.Json.JsonConvert.DeserializeObject((string)romDR["flags"]), + RomType = (int)romDR["romtype"], + RomTypeMedia = (string)romDR["romtypemedia"], + MediaLabel = (string)romDR["medialabel"], + Path = (string)romDR["path"], + Source = (GameRomItem.SourceType)(Int32)romDR["metadatasource"] + }; + return romItem; + } + + public class GameRomItem + { + public long Id { get; set; } + public long PlatformId { get; set; } + public IGDB.Models.Platform Platform { get; set; } + public long GameId { get; set; } + public string? Name { get; set; } + public long Size { get; set; } + public string? CRC { get; set; } + public string? MD5 { get; set; } + public string? SHA1 { get; set; } + public string? DevelopmentStatus { get; set; } + public string[]? Flags { get; set; } + public int RomType { get; set; } + public string? RomTypeMedia { get; set; } + public string? MediaLabel { get; set; } + public string? Path { get; set; } + public SourceType Source { get; set; } + + public enum SourceType + { + None = 0, + TOSEC = 1 + } + } + } +} + diff --git a/gaseous-server/Classes/SignatureIngestors/TOSEC.cs b/gaseous-server/Classes/SignatureIngestors/TOSEC.cs new file mode 100644 index 0000000..abb93ed --- /dev/null +++ b/gaseous-server/Classes/SignatureIngestors/TOSEC.cs @@ -0,0 +1,234 @@ +using System; +using System.IO; +using MySql.Data.MySqlClient; +using gaseous_romsignatureobject; +using gaseous_signature_parser.parsers; +using gaseous_tools; +using MySqlX.XDevAPI; + +namespace gaseous_server.SignatureIngestors.TOSEC +{ + public class TOSECIngestor + { + public void Import(string SearchPath) + { + // connect to database + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + + // process provided files + Logging.Log(Logging.LogType.Information, "Signature Ingestor - TOSEC", "Importing from " + SearchPath); + if (Directory.Exists(Config.LibraryConfiguration.LibrarySignatureImportDirectory_TOSEC)) + { + string[] tosecPathContents = Directory.GetFiles(SearchPath); + Array.Sort(tosecPathContents); + + string sql = ""; + Dictionary dbDict = new Dictionary(); + System.Data.DataTable sigDB; + + for (UInt16 i = 0; i < tosecPathContents.Length; ++i) + { + string tosecXMLFile = tosecPathContents[i]; + + // check tosec file md5 + Common.hashObject hashObject = new Common.hashObject(tosecXMLFile); + sql = "SELECT * FROM Signatures_Sources WHERE SourceMD5=@sourcemd5"; + dbDict = new Dictionary(); + dbDict.Add("sourcemd5", hashObject.md5hash); + sigDB = db.ExecuteCMD(sql, dbDict); + + if (sigDB.Rows.Count == 0) + { + try + { + Logging.Log(Logging.LogType.Information, "Signature Ingestor - TOSEC", "Importing file: " + tosecXMLFile); + + // start parsing file + TosecParser tosecParser = new TosecParser(); + RomSignatureObject tosecObject = tosecParser.Parse(tosecXMLFile); + + // store in database + + // store source object + bool processGames = false; + if (tosecObject.SourceMd5 != null) + { + sql = "SELECT * FROM Signatures_Sources WHERE SourceMD5=@sourcemd5"; + dbDict = new Dictionary(); + dbDict.Add("name", Common.ReturnValueIfNull(tosecObject.Name, "")); + dbDict.Add("description", Common.ReturnValueIfNull(tosecObject.Description, "")); + dbDict.Add("category", Common.ReturnValueIfNull(tosecObject.Category, "")); + dbDict.Add("version", Common.ReturnValueIfNull(tosecObject.Version, "")); + dbDict.Add("author", Common.ReturnValueIfNull(tosecObject.Author, "")); + dbDict.Add("email", Common.ReturnValueIfNull(tosecObject.Email, "")); + dbDict.Add("homepage", Common.ReturnValueIfNull(tosecObject.Homepage, "")); + dbDict.Add("uri", Common.ReturnValueIfNull(tosecObject.Url, "")); + dbDict.Add("sourcetype", Common.ReturnValueIfNull(tosecObject.SourceType, "")); + dbDict.Add("sourcemd5", tosecObject.SourceMd5); + dbDict.Add("sourcesha1", tosecObject.SourceSHA1); + + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + // entry not present, insert it + sql = "INSERT INTO Signatures_Sources (Name, Description, Category, Version, Author, Email, Homepage, Url, SourceType, SourceMD5, SourceSHA1) VALUES (@name, @description, @category, @version, @author, @email, @homepage, @uri, @sourcetype, @sourcemd5, @sourcesha1)"; + + db.ExecuteCMD(sql, dbDict); + + processGames = true; + } + + if (processGames == true) + { + for (int x = 0; x < tosecObject.Games.Count; ++x) + { + RomSignatureObject.Game gameObject = tosecObject.Games[x]; + + // set up game dictionary + dbDict = new Dictionary(); + dbDict.Add("name", Common.ReturnValueIfNull(gameObject.Name, "")); + dbDict.Add("description", Common.ReturnValueIfNull(gameObject.Description, "")); + dbDict.Add("year", Common.ReturnValueIfNull(gameObject.Year, "")); + dbDict.Add("publisher", Common.ReturnValueIfNull(gameObject.Publisher, "")); + dbDict.Add("demo", (int)gameObject.Demo); + dbDict.Add("system", Common.ReturnValueIfNull(gameObject.System, "")); + dbDict.Add("platform", Common.ReturnValueIfNull(gameObject.System, "")); + dbDict.Add("systemvariant", Common.ReturnValueIfNull(gameObject.SystemVariant, "")); + dbDict.Add("video", Common.ReturnValueIfNull(gameObject.Video, "")); + dbDict.Add("country", Common.ReturnValueIfNull(gameObject.Country, "")); + dbDict.Add("language", Common.ReturnValueIfNull(gameObject.Language, "")); + dbDict.Add("copyright", Common.ReturnValueIfNull(gameObject.Copyright, "")); + + // store platform + int gameSystem = 0; + if (gameObject.System != null) + { + sql = "SELECT Id FROM Signatures_Platforms WHERE Platform=@platform"; + + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + // entry not present, insert it + sql = "INSERT INTO Signatures_Platforms (Platform) VALUES (@platform); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + sigDB = db.ExecuteCMD(sql, dbDict); + + gameSystem = Convert.ToInt32(sigDB.Rows[0][0]); + } + else + { + gameSystem = (int)sigDB.Rows[0][0]; + } + } + dbDict.Add("systemid", gameSystem); + + // store publisher + int gamePublisher = 0; + if (gameObject.Publisher != null) + { + sql = "SELECT * FROM Signatures_Publishers WHERE Publisher=@publisher"; + + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + // entry not present, insert it + sql = "INSERT INTO Signatures_Publishers (Publisher) VALUES (@publisher); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + sigDB = db.ExecuteCMD(sql, dbDict); + gamePublisher = Convert.ToInt32(sigDB.Rows[0][0]); + } + else + { + gamePublisher = (int)sigDB.Rows[0][0]; + } + } + dbDict.Add("publisherid", gamePublisher); + + // store game + int gameId = 0; + sql = "SELECT * FROM Signatures_Games WHERE Name=@name AND Year=@year AND Publisherid=@publisher AND Systemid=@systemid AND Country=@country AND Language=@language"; + + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + // entry not present, insert it + sql = "INSERT INTO Signatures_Games " + + "(Name, Description, Year, PublisherId, Demo, SystemId, SystemVariant, Video, Country, Language, Copyright) VALUES " + + "(@name, @description, @year, @publisherid, @demo, @systemid, @systemvariant, @video, @country, @language, @copyright); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + sigDB = db.ExecuteCMD(sql, dbDict); + + gameId = Convert.ToInt32(sigDB.Rows[0][0]); + } + else + { + gameId = (int)sigDB.Rows[0][0]; + } + + // store rom + foreach (RomSignatureObject.Game.Rom romObject in gameObject.Roms) + { + if (romObject.Md5 != null) + { + int romId = 0; + sql = "SELECT * FROM Signatures_Roms WHERE GameId=@gameid AND MD5=@md5"; + dbDict = new Dictionary(); + dbDict.Add("gameid", gameId); + dbDict.Add("name", Common.ReturnValueIfNull(romObject.Name, "")); + dbDict.Add("size", Common.ReturnValueIfNull(romObject.Size, "")); + dbDict.Add("crc", Common.ReturnValueIfNull(romObject.Crc, "")); + dbDict.Add("md5", romObject.Md5); + dbDict.Add("sha1", Common.ReturnValueIfNull(romObject.Sha1, "")); + dbDict.Add("developmentstatus", Common.ReturnValueIfNull(romObject.DevelopmentStatus, "")); + + if (romObject.flags != null) + { + if (romObject.flags.Count > 0) + { + dbDict.Add("flags", Newtonsoft.Json.JsonConvert.SerializeObject(romObject.flags)); + } + else + { + dbDict.Add("flags", "[ ]"); + } + } + else + { + dbDict.Add("flags", "[ ]"); + } + dbDict.Add("romtype", (int)romObject.RomType); + dbDict.Add("romtypemedia", Common.ReturnValueIfNull(romObject.RomTypeMedia, "")); + dbDict.Add("medialabel", Common.ReturnValueIfNull(romObject.MediaLabel, "")); + dbDict.Add("metadatasource", Classes.Roms.GameRomItem.SourceType.TOSEC); + + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + // entry not present, insert it + sql = "INSERT INTO Signatures_Roms (GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Flags, RomType, RomTypeMedia, MediaLabel, MetadataSource) VALUES (@gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @flags, @romtype, @romtypemedia, @medialabel, @metadatasource); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + sigDB = db.ExecuteCMD(sql, dbDict); + + + romId = Convert.ToInt32(sigDB.Rows[0][0]); + } + else + { + romId = (int)sigDB.Rows[0][0]; + } + } + } + } + } + } + } + catch (Exception ex) + { + Logging.Log(Logging.LogType.Warning, "Signature Ingestor - TOSEC", "Invalid import file: " + tosecXMLFile, ex); + } + } + else + { + Logging.Log(Logging.LogType.Debug, "Signature Ingestor - TOSEC", "Rejecting already imported file: " + tosecXMLFile); + } + } + } + } + } +} \ No newline at end of file diff --git a/gaseous-server/Controllers/BackgroundTasksController.cs b/gaseous-server/Controllers/BackgroundTasksController.cs new file mode 100644 index 0000000..062ffad --- /dev/null +++ b/gaseous-server/Controllers/BackgroundTasksController.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace gaseous_server.Controllers +{ + [ApiController] + [Route("api/v1/[controller]")] + public class BackgroundTasksController : Controller + { + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public List GetQueue() + { + return ProcessQueue.QueueItems; + } + + [HttpGet] + [Route("{TaskType}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult ForceRun(ProcessQueue.QueueItemType TaskType, Boolean ForceRun) + { + foreach (ProcessQueue.QueueItem qi in ProcessQueue.QueueItems) + { + if (TaskType == qi.ItemType) + { + if (ForceRun == true) + { + qi.ForceExecute(); + } + return qi; + } + } + + return NotFound(); + } + } +} \ No newline at end of file diff --git a/gaseous-server/Controllers/FilterController.cs b/gaseous-server/Controllers/FilterController.cs new file mode 100644 index 0000000..e8af867 --- /dev/null +++ b/gaseous-server/Controllers/FilterController.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using gaseous_tools; +using IGDB.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace gaseous_server.Controllers +{ + [Route("api/v1/[controller]")] + [ApiController] + public class FilterController : ControllerBase + { + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + //[ResponseCache(CacheProfileName = "5Minute")] + public Dictionary Filter() + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + + Dictionary FilterSet = new Dictionary(); + + // platforms + List platforms = new List(); + string sql = "SELECT Platform.Id, Platform.Abbreviation, Platform.AlternativeName, Platform.`Name`, Platform.PlatformLogo, (SELECT COUNT(Games_Roms.Id) AS RomCount FROM Games_Roms WHERE Games_Roms.PlatformId = Platform.Id) AS RomCount FROM Platform HAVING RomCount > 0 ORDER BY `Name`"; + DataTable dbResponse = db.ExecuteCMD(sql); + + foreach (DataRow dr in dbResponse.Rows) + { + platforms.Add(Classes.Metadata.Platforms.GetPlatform((long)dr["id"])); + } + FilterSet.Add("platforms", platforms); + + // genres + List genres = new List(); + sql = "SELECT DISTINCT t1.Id, t1.`Name` FROM Genre AS t1 JOIN (SELECT * FROM Game WHERE (SELECT COUNT(Id) FROM Games_Roms WHERE GameId = Game.Id) > 0) AS t2 ON JSON_CONTAINS(t2.Genres, CAST(t1.Id AS char), '$') ORDER BY t1.`Name`"; + dbResponse = db.ExecuteCMD(sql); + + foreach (DataRow dr in dbResponse.Rows) + { + genres.Add(Classes.Metadata.Genres.GetGenres((long)dr["id"])); + } + FilterSet.Add("genres", genres); + + return FilterSet; + } + } +} \ No newline at end of file diff --git a/gaseous-server/Controllers/GamesController.cs b/gaseous-server/Controllers/GamesController.cs new file mode 100644 index 0000000..0dce880 --- /dev/null +++ b/gaseous-server/Controllers/GamesController.cs @@ -0,0 +1,966 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using gaseous_server.Classes.Metadata; +using gaseous_tools; +using IGDB.Models; +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), StatusCodes.Status200OK)] + public ActionResult Game(string name = "", string platform = "", string genre = "", bool sortdescending = false) + { + string whereClause = ""; + string havingClause = ""; + Dictionary whereParams = new Dictionary(); + + List whereClauses = new List(); + List havingClauses = new List(); + + string tempVal = ""; + + if (name.Length > 0) + { + tempVal = "`Name` LIKE @Name"; + whereParams.Add("@Name", "%" + name + "%"); + havingClauses.Add(tempVal); + } + + if (platform.Length > 0) + { + tempVal = "Games_Roms.PlatformId IN ("; + string[] platformClauseItems = platform.Split(","); + for (int i = 0; i < platformClauseItems.Length; i++) + { + if (i > 0) + { + tempVal += ", "; + } + string platformLabel = "@Platform" + i; + tempVal += platformLabel; + whereParams.Add(platformLabel, platformClauseItems[i]); + } + tempVal += ")"; + whereClauses.Add(tempVal); + } + + if (genre.Length > 0) + { + tempVal = "("; + string[] genreClauseItems = genre.Split(","); + for (int i = 0; i < genreClauseItems.Length; i++) + { + if (i > 0) + { + tempVal += " AND "; + } + string genreLabel = "@Genre" + i; + tempVal += "JSON_CONTAINS(Game.Genres, " + genreLabel + ", '$')"; + whereParams.Add(genreLabel, genreClauseItems[i]); + } + tempVal += ")"; + whereClauses.Add(tempVal); + } + + // build where clause + if (whereClauses.Count > 0) + { + whereClause = "WHERE "; + for (int i = 0; i < whereClauses.Count; i++) + { + if (i > 0) + { + whereClause += " AND "; + } + whereClause += whereClauses[i]; + } + } + + // build having clause + if (havingClauses.Count > 0) + { + havingClause = "HAVING "; + for (int i = 0; i < havingClauses.Count; i++) + { + if (i > 0) + { + havingClause += " AND "; + } + havingClause += havingClauses[i]; + } + } + + // order by clause + string orderByClause = "ORDER BY `Name` ASC"; + if (sortdescending == true) + { + orderByClause = "ORDER BY `Name` DESC"; + } + + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT DISTINCT Games_Roms.GameId AS ROMGameId, Game.Id, Game.AgeRatings, Game.AggregatedRating, Game.AggregatedRatingCount, Game.AlternativeNames, Game.Artworks, Game.Bundles, Game.Category, Game.Collection, Game.Cover, Game.Dlcs, Game.Expansions, Game.ExternalGames, Game.FirstReleaseDate, Game.`Follows`, Game.Franchise, Game.Franchises, Game.GameEngines, Game.GameModes, Game.Genres, Game.Hypes, Game.InvolvedCompanies, Game.Keywords, Game.MultiplayerModes, Game.`Name`, Game.ParentGame, Game.Platforms, Game.PlayerPerspectives, Game.Rating, Game.RatingCount, Game.ReleaseDates, Game.Screenshots, Game.SimilarGames, Game.Slug, Game.StandaloneExpansions, Game.`Status`, Game.StoryLine, Game.Summary, Game.Tags, Game.Themes, Game.TotalRating, Game.TotalRatingCount, Game.VersionParent, Game.VersionTitle, Game.Videos, Game.Websites FROM gaseous.Games_Roms LEFT JOIN Game ON Game.Id = Games_Roms.GameId " + whereClause + " " + havingClause + " " + orderByClause; + + List RetVal = new List(); + + DataTable dbResponse = db.ExecuteCMD(sql, whereParams); + foreach (DataRow dr in dbResponse.Rows) + { + //RetVal.Add(Classes.Metadata.Games.GetGame((long)dr["ROMGameId"], false, false)); + RetVal.Add(Classes.Metadata.Games.GetGame(dr)); + } + + return Ok(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); + + if (gameObject != null) + { + return Ok(gameObject); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("{GameId}/alternativename")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ResponseCache(CacheProfileName = "7Days")] + public ActionResult GameAlternativeNames(long GameId) + { + try + { + Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); + + if (gameObject.AlternativeNames != null) + { + List altNames = new List(); + foreach (long altNameId in gameObject.AlternativeNames.Ids) + { + altNames.Add(AlternativeNames.GetAlternativeNames(altNameId)); + } + return Ok(altNames); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("{GameId}/agerating")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ResponseCache(CacheProfileName = "7Days")] + public ActionResult GameAgeClassification(long GameId) + { + try + { + Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); + + if (gameObject.AgeRatings != null) + { + List ageRatings = new List(); + foreach (long ageRatingId in gameObject.AgeRatings.Ids) + { + ageRatings.Add(AgeRatings.GetConsolidatedAgeRating(ageRatingId)); + } + return Ok(ageRatings); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("{GameId}/agerating/{RatingId}/image")] + [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GameAgeClassification(long GameId, long RatingId) + { + try + { + GameAgeRating gameAgeRating = GetConsolidatedAgeRating(RatingId); + + string fileExtension = ""; + string fileType = ""; + switch (gameAgeRating.RatingBoard) + { + case AgeRatingCategory.ESRB: + fileExtension = "svg"; + fileType = "image/svg+xml"; + break; + case AgeRatingCategory.PEGI: + fileExtension = "svg"; + fileType = "image/svg+xml"; + break; + case AgeRatingCategory.ACB: + fileExtension = "svg"; + fileType = "image/svg+xml"; + break; + case AgeRatingCategory.CERO: + fileExtension = "svg"; + fileType = "image/svg+xml"; + break; + case AgeRatingCategory.USK: + fileExtension = "svg"; + fileType = "image/svg+xml"; + break; + case AgeRatingCategory.GRAC: + fileExtension = "svg"; + fileType = "image/svg+xml"; + break; + case AgeRatingCategory.CLASS_IND: + fileExtension = "svg"; + fileType = "image/svg+xml"; + break; + } + + string resourceName = "gaseous_server.Assets.Ratings." + gameAgeRating.RatingBoard.ToString() + "." + gameAgeRating.RatingTitle.ToString() + "." + fileExtension; + + var assembly = Assembly.GetExecutingAssembly(); + string[] resources = assembly.GetManifestResourceNames(); + if (resources.Contains(resourceName)) + { + using (Stream stream = assembly.GetManifestResourceStream(resourceName)) + using (StreamReader reader = new StreamReader(stream)) + { + byte[] filedata = new byte[stream.Length]; + stream.Read(filedata, 0, filedata.Length); + + string filename = gameAgeRating.RatingBoard.ToString() + "-" + gameAgeRating.RatingTitle.ToString() + "." + fileExtension; + string contentType = fileType; + + var cd = new System.Net.Mime.ContentDisposition + { + FileName = filename, + Inline = true, + }; + + Response.Headers.Add("Content-Disposition", cd.ToString()); + Response.Headers.Add("Cache-Control", "public, max-age=604800"); + + return File(filedata, contentType); + } + } + return NotFound(); + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("{GameId}/artwork")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ResponseCache(CacheProfileName = "7Days")] + public ActionResult GameArtwork(long GameId) + { + try + { + Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); + + List artworks = new List(); + if (gameObject.Artworks != null) + { + foreach (long ArtworkId in gameObject.Artworks.Ids) + { + Artwork GameArtwork = Artworks.GetArtwork(ArtworkId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject)); + artworks.Add(GameArtwork); + } + } + + return Ok(artworks); + } + catch + { + return NotFound(); + } + } + + [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); + + try + { + IGDB.Models.Artwork artworkObject = Artworks.GetArtwork(ArtworkId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject)); + if (artworkObject != null) + { + return Ok(artworkObject); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [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); + + try + { + IGDB.Models.Artwork artworkObject = Artworks.GetArtwork(ArtworkId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject)); + if (artworkObject != null) { + string coverFilePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Artwork", artworkObject.ImageId + ".png"); + if (System.IO.File.Exists(coverFilePath)) + { + string filename = artworkObject.ImageId + ".png"; + string filepath = coverFilePath; + byte[] filedata = System.IO.File.ReadAllBytes(filepath); + string contentType = "image/png"; + + var cd = new System.Net.Mime.ContentDisposition + { + FileName = filename, + Inline = true, + }; + + Response.Headers.Add("Content-Disposition", cd.ToString()); + Response.Headers.Add("Cache-Control", "public, max-age=604800"); + + return File(filedata, contentType); + } + else + { + return NotFound(); + } + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [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); + if (gameObject != null) + { + IGDB.Models.Cover coverObject = Covers.GetCover(gameObject.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject)); + if (coverObject != null) + { + return Ok(coverObject); + } + else + { + return NotFound(); + } + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [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); + + string coverFilePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Cover.png"); + if (System.IO.File.Exists(coverFilePath)) { + string filename = "Cover.png"; + string filepath = coverFilePath; + byte[] filedata = System.IO.File.ReadAllBytes(filepath); + string contentType = "image/png"; + + var cd = new System.Net.Mime.ContentDisposition + { + FileName = filename, + Inline = true, + }; + + Response.Headers.Add("Content-Disposition", cd.ToString()); + Response.Headers.Add("Cache-Control", "public, max-age=604800"); + + return File(filedata, contentType); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("{GameId}/genre")] + [ProducesResponseType(typeof(List), 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); + if (gameObject != null) + { + List genreObjects = new List(); + if (gameObject.Genres != null) + { + foreach (long genreId in gameObject.Genres.Ids) + { + genreObjects.Add(Classes.Metadata.Genres.GetGenres(genreId)); + } + } + + List sortedGenreObjects = genreObjects.OrderBy(o => o.Name).ToList(); + + return Ok(sortedGenreObjects); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("{GameId}/companies")] + [ProducesResponseType(typeof(List>), 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); + if (gameObject != null) + { + List> icObjects = new List>(); + if (gameObject.InvolvedCompanies != null) + { + foreach (long icId in gameObject.InvolvedCompanies.Ids) + { + InvolvedCompany involvedCompany = Classes.Metadata.InvolvedCompanies.GetInvolvedCompanies(icId); + Company company = Classes.Metadata.Companies.GetCompanies(involvedCompany.Company.Id); + company.Developed = null; + company.Published = null; + + Dictionary companyData = new Dictionary(); + companyData.Add("involvement", involvedCompany); + companyData.Add("company", company); + + icObjects.Add(companyData); + } + } + + return Ok(icObjects); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("{GameId}/companies/{CompanyId}")] + [ProducesResponseType(typeof(Dictionary), 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); + if (gameObject != null) + { + List> icObjects = new List>(); + if (gameObject.InvolvedCompanies != null) + { + InvolvedCompany involvedCompany = Classes.Metadata.InvolvedCompanies.GetInvolvedCompanies(CompanyId); + Company company = Classes.Metadata.Companies.GetCompanies(involvedCompany.Company.Id); + company.Developed = null; + company.Published = null; + + Dictionary companyData = new Dictionary(); + companyData.Add("involvement", involvedCompany); + companyData.Add("company", company); + + return Ok(companyData); + } else + { + return NotFound(); + } + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [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); + + InvolvedCompany involvedCompany = Classes.Metadata.InvolvedCompanies.GetInvolvedCompanies(CompanyId); + Company company = Classes.Metadata.Companies.GetCompanies(involvedCompany.Company.Id); + + string coverFilePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Company(company), "Logo_Medium.png"); + if (System.IO.File.Exists(coverFilePath)) + { + string filename = "Logo.png"; + string filepath = coverFilePath; + byte[] filedata = System.IO.File.ReadAllBytes(filepath); + string contentType = "image/png"; + + var cd = new System.Net.Mime.ContentDisposition + { + FileName = filename, + Inline = true, + }; + + Response.Headers.Add("Content-Disposition", cd.ToString()); + Response.Headers.Add("Cache-Control", "public, max-age=604800"); + + return File(filedata, contentType); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("{GameId}/roms")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + //[ResponseCache(CacheProfileName = "5Minute")] + public ActionResult GameRom(long GameId) + { + try + { + Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); + + List roms = Classes.Roms.GetRoms(GameId); + + return Ok(roms); + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("{GameId}/roms/{RomId}")] + [ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + //[ResponseCache(CacheProfileName = "5Minute")] + public ActionResult GameRom(long GameId, long RomId) + { + try + { + Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); + + Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId); + if (rom.GameId == GameId) + { + return Ok(rom); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [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); + + Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId); + if (rom.GameId == GameId) + { + rom = Classes.Roms.UpdateRom(RomId, NewPlatformId, NewGameId); + return Ok(rom); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [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); + + Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId); + if (rom.GameId == GameId) + { + Classes.Roms.DeleteRom(RomId); + return Ok(rom); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [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); + + Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId); + if (rom.GameId != GameId) + { + return NotFound(); + } + + string romFilePath = rom.Path; + if (System.IO.File.Exists(romFilePath)) + { + string filename = Path.GetFileName(romFilePath); + string filepath = romFilePath; + byte[] filedata = System.IO.File.ReadAllBytes(filepath); + string contentType = "application/octet-stream"; + + var cd = new System.Net.Mime.ContentDisposition + { + FileName = filename, + Inline = false, + }; + + Response.Headers.Add("Content-Disposition", cd.ToString()); + Response.Headers.Add("Cache-Control", "public, max-age=604800"); + + return File(filedata, contentType); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("search")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GameSearch(long RomId = 0, string SearchString = "") + { + try + { + if (RomId > 0) + { + Classes.Roms.GameRomItem romItem = Classes.Roms.GetRom(RomId); + Common.hashObject hash = new Common.hashObject(romItem.Path); + Models.Signatures_Games romSig = Classes.ImportGame.GetFileSignature(hash, new FileInfo(romItem.Path), romItem.Path); + List searchResults = Classes.ImportGame.SearchForGame_GetAll(romSig.Game.Name, romSig.Flags.IGDBPlatformId); + + return Ok(searchResults); + } + else + { + if (SearchString.Length > 0) + { + List searchResults = Classes.ImportGame.SearchForGame_GetAll(SearchString, 0); + + return Ok(searchResults); + } + else + { + return NotFound(); + } + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("{GameId}/screenshots")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ResponseCache(CacheProfileName = "7Days")] + public ActionResult GameScreenshot(long GameId) + { + try + { + Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); + + List screenshots = new List(); + if (gameObject.Screenshots != null) + { + foreach (long ScreenshotId in gameObject.Screenshots.Ids) + { + Screenshot GameScreenshot = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject)); + screenshots.Add(GameScreenshot); + } + } + + return Ok(screenshots); + } + catch + { + return NotFound(); + } + } + + [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); + if (gameObject != null) { + IGDB.Models.Screenshot screenshotObject = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject)); + if (screenshotObject != null) + { + return Ok(screenshotObject); + } + else + { + return NotFound(); + } + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [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.Screenshot screenshotObject = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject)); + + string coverFilePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Screenshots", screenshotObject.ImageId + ".png"); + if (System.IO.File.Exists(coverFilePath)) + { + string filename = screenshotObject.ImageId + ".png"; + string filepath = coverFilePath; + byte[] filedata = System.IO.File.ReadAllBytes(filepath); + string contentType = "image/png"; + + var cd = new System.Net.Mime.ContentDisposition + { + FileName = filename, + Inline = true, + }; + + Response.Headers.Add("Content-Disposition", cd.ToString()); + Response.Headers.Add("Cache-Control", "public, max-age=604800"); + + return File(filedata, contentType); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("{GameId}/videos")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ResponseCache(CacheProfileName = "7Days")] + public ActionResult GameVideo(long GameId) + { + try + { + Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); + + List videos = new List(); + if (gameObject.Videos != null) + { + foreach (long VideoId in gameObject.Videos.Ids) + { + GameVideo gameVideo = GamesVideos.GetGame_Videos(VideoId); + videos.Add(gameVideo); + } + } + + return Ok(videos); + } + catch + { + return NotFound(); + } + } + } +} diff --git a/gaseous-server/Controllers/PlatformsController.cs b/gaseous-server/Controllers/PlatformsController.cs new file mode 100644 index 0000000..7b5fb0c --- /dev/null +++ b/gaseous-server/Controllers/PlatformsController.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Reflection; +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.CodeAnalysis.Scripting; + +namespace gaseous_server.Controllers +{ + [Route("api/v1/[controller]")] + [ApiController] + public class PlatformsController : Controller + { + [HttpGet] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public ActionResult Platform() + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + + string sql = "SELECT * FROM gaseous.Platform WHERE Id IN (SELECT DISTINCT PlatformId FROM Games_Roms) ORDER BY `Name` ASC;"; + + List RetVal = new List(); + + DataTable dbResponse = db.ExecuteCMD(sql); + foreach (DataRow dr in dbResponse.Rows) + { + RetVal.Add(Classes.Metadata.Platforms.GetPlatform((long)dr["id"])); + } + + return Ok(RetVal); + } + + [HttpGet] + [Route("{PlatformId}")] + [ProducesResponseType(typeof(Platform), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult Platform(long PlatformId) + { + try + { + IGDB.Models.Platform platformObject = Classes.Metadata.Platforms.GetPlatform(PlatformId); + + if (platformObject != null) + { + return Ok(platformObject); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("{PlatformId}/platformlogo")] + [ProducesResponseType(typeof(PlatformLogo), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult PlatformLogo(long PlatformId) + { + try + { + IGDB.Models.Platform platformObject = Classes.Metadata.Platforms.GetPlatform(PlatformId); + if (platformObject != null) + { + IGDB.Models.PlatformLogo logoObject = PlatformLogos.GetPlatformLogo(platformObject.PlatformLogo.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(platformObject)); + if (logoObject != null) + { + return Ok(logoObject); + } + else + { + return NotFound(); + } + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [Route("{PlatformId}/platformlogo/image")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult PlatformLogoImage(long PlatformId) + { + try + { + IGDB.Models.Platform platformObject = Classes.Metadata.Platforms.GetPlatform(PlatformId); + + string logoFilePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(platformObject), "Logo_Medium.png"); + if (System.IO.File.Exists(logoFilePath)) + { + string filename = "Logo.png"; + string filepath = logoFilePath; + byte[] filedata = System.IO.File.ReadAllBytes(filepath); + string contentType = "image/png"; + + var cd = new System.Net.Mime.ContentDisposition + { + FileName = filename, + Inline = true, + }; + + Response.Headers.Add("Content-Disposition", cd.ToString()); + + return File(filedata, contentType); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + } +} + diff --git a/gaseous-server/Controllers/SearchController.cs b/gaseous-server/Controllers/SearchController.cs new file mode 100644 index 0000000..14c46a8 --- /dev/null +++ b/gaseous-server/Controllers/SearchController.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using gaseous_tools; +using IGDB; +using IGDB.Models; +using Microsoft.AspNetCore.Mvc; +using static gaseous_server.Classes.Metadata.Games; +using static gaseous_tools.Config.ConfigFile; + +namespace gaseous_server.Controllers +{ + [ApiController] + [Route("api/v1/[controller]")] + public class SearchController : Controller + { + private static IGDBClient igdb = new IGDBClient( + // Found in Twitch Developer portal for your app + Config.IGDB.ClientId, + Config.IGDB.Secret + ); + + [HttpGet] + [Route("Platform")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task SearchPlatform(string SearchString) + { + List RetVal = await _SearchForPlatform(SearchString); + return Ok(RetVal); + } + + private static async Task> _SearchForPlatform(string SearchString) + { + string searchBody = ""; + searchBody += "fields abbreviation,alternative_name,category,checksum,created_at,generation,name,platform_family,platform_logo,slug,summary,updated_at,url,versions,websites; "; + searchBody += "where name ~ *\"" + SearchString + "\"*;"; + + // get Platform metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.Platforms, query: searchBody); + + return results.ToList(); + } + + [HttpGet] + [Route("Game")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task SearchGame(long PlatformId, string SearchString) + { + List RetVal = await _SearchForGame(PlatformId, SearchString); + return Ok(RetVal); + } + + private static async Task> _SearchForGame(long PlatformId, string SearchString) + { + string searchBody = ""; + searchBody += "fields cover.*,first_release_date,name,platforms,slug; "; + searchBody += "search \"" + SearchString + "\";"; + searchBody += "where platforms = (" + PlatformId + ");"; + + // get Platform metadata + var results = await igdb.QueryAsync(IGDBClient.Endpoints.Games, query: searchBody); + + return results.ToList(); + } + } +} + diff --git a/gaseous-server/Controllers/SignaturesController.cs b/gaseous-server/Controllers/SignaturesController.cs index 7f69893..f59173a 100644 --- a/gaseous-server/Controllers/SignaturesController.cs +++ b/gaseous-server/Controllers/SignaturesController.cs @@ -12,7 +12,7 @@ using Microsoft.AspNetCore.Mvc; namespace gaseous_server.Controllers { [ApiController] - [Route("api/[controller]/[action]")] + [Route("api/v1/[controller]/[action]")] public class SignaturesController : ControllerBase { /// @@ -20,28 +20,42 @@ namespace gaseous_server.Controllers /// /// Number of sources, publishers, games, and rom signatures in the database [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] public Models.Signatures_Status Status() { return new Models.Signatures_Status(); } [HttpGet] - [Route("api/[controller]/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] public List GetSignature(string md5 = "", string sha1 = "") { if (md5.Length > 0) { - return _GetSignature("signatures_roms.md5 = @searchstring", md5); + return _GetSignature("Signatures_Roms.md5 = @searchstring", md5); } else { - return _GetSignature("signatures_roms.sha1 = @searchstring", sha1); + return _GetSignature("Signatures_Roms.sha1 = @searchstring", sha1); + } + } + + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public List GetByTosecName(string TosecName = "") + { + if (TosecName.Length > 0) + { + return _GetSignature("Signatures_Roms.name = @searchstring", TosecName); + } else + { + return null; } } private List _GetSignature(string sqlWhere, string searchString) { Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "SELECT \n view_signatures_games.*,\n signatures_roms.id AS romid,\n signatures_roms.name AS romname,\n signatures_roms.size,\n signatures_roms.crc,\n signatures_roms.md5,\n signatures_roms.sha1,\n signatures_roms.developmentstatus,\n signatures_roms.flags,\n signatures_roms.romtype,\n signatures_roms.romtypemedia,\n signatures_roms.medialabel\nFROM\n signatures_roms\n INNER JOIN\n view_signatures_games ON signatures_roms.gameid = view_signatures_games.id WHERE " + sqlWhere; + string sql = "SELECT view_Signatures_Games.*, Signatures_Roms.Id AS romid, Signatures_Roms.Name AS romname, Signatures_Roms.Size, Signatures_Roms.CRC, Signatures_Roms.MD5, Signatures_Roms.SHA1, Signatures_Roms.DevelopmentStatus, Signatures_Roms.Flags, Signatures_Roms.RomType, Signatures_Roms.RomTypeMedia, Signatures_Roms.MediaLabel, Signatures_Roms.MetadataSource FROM Signatures_Roms INNER JOIN view_Signatures_Games ON Signatures_Roms.GameId = view_Signatures_Games.Id WHERE " + sqlWhere; Dictionary dbDict = new Dictionary(); dbDict.Add("searchString", searchString); @@ -55,32 +69,33 @@ namespace gaseous_server.Controllers { Game = new Models.Signatures_Games.GameItem { - Id = (Int32)sigDbRow["id"], - Name = (string)sigDbRow["name"], - Description = (string)sigDbRow["description"], - Year = (string)sigDbRow["year"], - Publisher = (string)sigDbRow["publisher"], - Demo = (Models.Signatures_Games.GameItem.DemoTypes)(int)sigDbRow["demo"], - System = (string)sigDbRow["platform"], - SystemVariant = (string)sigDbRow["systemvariant"], - Video = (string)sigDbRow["video"], - Country = (string)sigDbRow["country"], - Language = (string)sigDbRow["language"], - Copyright = (string)sigDbRow["copyright"] + Id = (Int32)sigDbRow["Id"], + Name = (string)sigDbRow["Name"], + Description = (string)sigDbRow["Description"], + Year = (string)sigDbRow["Year"], + Publisher = (string)sigDbRow["Publisher"], + Demo = (Models.Signatures_Games.GameItem.DemoTypes)(int)sigDbRow["Demo"], + System = (string)sigDbRow["Platform"], + SystemVariant = (string)sigDbRow["SystemVariant"], + Video = (string)sigDbRow["Video"], + Country = (string)sigDbRow["Country"], + Language = (string)sigDbRow["Language"], + Copyright = (string)sigDbRow["Copyright"] }, Rom = new Models.Signatures_Games.RomItem { Id = (Int32)sigDbRow["romid"], Name = (string)sigDbRow["romname"], - Size = (Int64)sigDbRow["size"], - Crc = (string)sigDbRow["crc"], - Md5 = (string)sigDbRow["md5"], - Sha1 = (string)sigDbRow["sha1"], - DevelopmentStatus = (string)sigDbRow["developmentstatus"], - flags = Newtonsoft.Json.JsonConvert.DeserializeObject>((string)sigDbRow["flags"]), - RomType = (Models.Signatures_Games.RomItem.RomTypes)(int)sigDbRow["romtype"], - RomTypeMedia = (string)sigDbRow["romtypemedia"], - MediaLabel = (string)sigDbRow["medialabel"] + Size = (Int64)sigDbRow["Size"], + Crc = (string)sigDbRow["CRC"], + Md5 = (string)sigDbRow["MD5"], + Sha1 = (string)sigDbRow["SHA1"], + DevelopmentStatus = (string)sigDbRow["DevelopmentStatus"], + flags = Newtonsoft.Json.JsonConvert.DeserializeObject>((string)sigDbRow["Flags"]), + RomType = (Models.Signatures_Games.RomItem.RomTypes)(int)sigDbRow["RomType"], + RomTypeMedia = (string)sigDbRow["RomTypeMedia"], + MediaLabel = (string)sigDbRow["MediaLabel"], + SignatureSource = (Models.Signatures_Games.RomItem.SignatureSourceType)(Int32)sigDbRow["MetadataSource"] } }; GamesList.Add(gameItem); diff --git a/gaseous-server/Controllers/SystemController.cs b/gaseous-server/Controllers/SystemController.cs new file mode 100644 index 0000000..e4f285d --- /dev/null +++ b/gaseous-server/Controllers/SystemController.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using gaseous_tools; +using Microsoft.AspNetCore.Mvc; + +namespace gaseous_server.Controllers +{ + [ApiController] + [Route("api/v1/[controller]")] + public class SystemController : Controller + { + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public Dictionary GetSystemStatus() + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + + Dictionary ReturnValue = new Dictionary(); + + // disk size + List> Disks = new List>(); + //Disks.Add(GetDisk(gaseous_tools.Config.ConfigurationPath)); + Disks.Add(GetDisk(gaseous_tools.Config.LibraryConfiguration.LibraryRootDirectory)); + ReturnValue.Add("Paths", Disks); + + // database size + string sql = "SELECT table_schema, SUM(data_length + index_length) FROM information_schema.tables WHERE table_schema = '" + Config.DatabaseConfiguration.DatabaseName + "'"; + DataTable dbResponse = db.ExecuteCMD(sql); + ReturnValue.Add("DatabaseSize", dbResponse.Rows[0][1]); + + return ReturnValue; + } + + private Dictionary GetDisk(string Path) + { + Dictionary DiskValues = new Dictionary(); + DiskValues.Add("LibraryPath", Path); + DiskValues.Add("SpaceUsed", gaseous_tools.Common.DirSize(new DirectoryInfo(Path))); + DiskValues.Add("SpaceAvailable", new DriveInfo(Path).AvailableFreeSpace); + DiskValues.Add("TotalSpace", new DriveInfo(Path).TotalSize); + + return DiskValues; + } + } +} \ No newline at end of file diff --git a/gaseous-server/Models/PlatformMapping.cs b/gaseous-server/Models/PlatformMapping.cs new file mode 100644 index 0000000..f97dd7b --- /dev/null +++ b/gaseous-server/Models/PlatformMapping.cs @@ -0,0 +1,84 @@ +using System; +using System.Reflection; + +namespace gaseous_server.Models +{ + public class PlatformMapping + { + public PlatformMapping() + { + + } + + private static List _PlatformMaps = new List(); + public static List PlatformMap + { + get + { + if (_PlatformMaps.Count == 0) + { + // load platform maps from: gaseous_server.Support.PlatformMap.json + var assembly = Assembly.GetExecutingAssembly(); + var resourceName = "gaseous_server.Support.PlatformMap.json"; + using (Stream stream = assembly.GetManifestResourceStream(resourceName)) + using (StreamReader reader = new StreamReader(stream)) + { + string rawJson = reader.ReadToEnd(); + _PlatformMaps.Clear(); + _PlatformMaps = Newtonsoft.Json.JsonConvert.DeserializeObject>(rawJson); + } + } + + return _PlatformMaps; + } + } + + public static void GetIGDBPlatformMapping(ref Models.Signatures_Games Signature, FileInfo RomFileInfo, bool SetSystemName) + { + bool PlatformFound = false; + foreach (Models.PlatformMapping.PlatformMapItem PlatformMapping in Models.PlatformMapping.PlatformMap) + { + if (PlatformMapping.KnownFileExtensions.Contains(RomFileInfo.Extension, StringComparer.OrdinalIgnoreCase)) + { + if (SetSystemName == true) + { + if (Signature.Game != null) { Signature.Game.System = PlatformMapping.IGDBName; } + } + Signature.Flags.IGDBPlatformId = PlatformMapping.IGDBId; + Signature.Flags.IGDBPlatformName = PlatformMapping.IGDBName; + + PlatformFound = true; + break; + } + } + + if (PlatformFound == false) + { + foreach (Models.PlatformMapping.PlatformMapItem PlatformMapping in Models.PlatformMapping.PlatformMap) + { + if (PlatformMapping.AlternateNames.Contains(Signature.Game.System, StringComparer.OrdinalIgnoreCase)) + { + if (SetSystemName == true) + { + if (Signature.Game != null) { Signature.Game.System = PlatformMapping.IGDBName; } + } + Signature.Flags.IGDBPlatformId = PlatformMapping.IGDBId; + Signature.Flags.IGDBPlatformName = PlatformMapping.IGDBName; + + PlatformFound = true; + break; + } + } + } + } + + public class PlatformMapItem + { + public int IGDBId { get; set; } + public string IGDBName { get; set; } + public List AlternateNames { get; set; } = new List(); + public List KnownFileExtensions { get; set; } = new List(); + } + } +} + diff --git a/gaseous-server/Models/Signatures_Games.cs b/gaseous-server/Models/Signatures_Games.cs index 9765714..0208008 100644 --- a/gaseous-server/Models/Signatures_Games.cs +++ b/gaseous-server/Models/Signatures_Games.cs @@ -1,4 +1,5 @@ using System; +using System.Text.Json.Serialization; using static gaseous_romsignatureobject.RomSignatureObject.Game; namespace gaseous_server.Models @@ -12,6 +13,29 @@ namespace gaseous_server.Models public GameItem? Game { get; set; } public RomItem? Rom { get; set; } + //[JsonIgnore] + public int Score + { + get + { + int _score = 0; + + if (Game != null) + { + _score = _score + Game.Score; + } + + if (Rom != null) + { + _score = _score + Rom.Score; + } + + return _score; + } + } + + public SignatureFlags Flags = new SignatureFlags(); + public class GameItem { public Int32? Id { get; set; } @@ -36,6 +60,60 @@ namespace gaseous_server.Models demo_rolling = 4, demo_slideshow = 5 } + + [JsonIgnore] + public int Score + { + get + { + // calculate a score based on the availablility of data + int _score = 0; + var properties = this.GetType().GetProperties(); + foreach (var prop in properties) + { + if (prop.GetGetMethod() != null) + { + switch (prop.Name.ToLower()) + { + case "id": + case "score": + break; + case "name": + case "year": + case "publisher": + case "system": + if (prop.PropertyType == typeof(string)) + { + if (prop.GetValue(this) != null) + { + string propVal = prop.GetValue(this).ToString(); + if (propVal.Length > 0) + { + _score = _score + 10; + } + } + } + break; + default: + if (prop.PropertyType == typeof(string)) + { + if (prop.GetValue(this) != null) + { + string propVal = prop.GetValue(this).ToString(); + if (propVal.Length > 0) + { + _score = _score + 1; + } + } + } + break; + } + } + } + + return _score; + } + } } public class RomItem @@ -55,6 +133,14 @@ namespace gaseous_server.Models public string? RomTypeMedia { get; set; } public string? MediaLabel { get; set; } + public SignatureSourceType SignatureSource { get; set; } + + public enum SignatureSourceType + { + None = 0, + TOSEC = 1 + } + public enum RomTypes { /// @@ -92,6 +178,66 @@ namespace gaseous_server.Models /// Side = 6 } + + [JsonIgnore] + public int Score + { + get + { + // calculate a score based on the availablility of data + int _score = 0; + var properties = this.GetType().GetProperties(); + foreach (var prop in properties) + { + if (prop.GetGetMethod() != null) + { + switch (prop.Name.ToLower()) + { + case "name": + case "size": + case "crc": + case "developmentstatus": + case "flags": + case "romtypemedia": + case "medialabel": + if (prop.PropertyType == typeof(string) || prop.PropertyType == typeof(Int64) || prop.PropertyType == typeof(List)) + { + if (prop.GetValue(this) != null) + { + string propVal = prop.GetValue(this).ToString(); + if (propVal.Length > 0) + { + _score = _score + 10; + } + } + } + break; + default: + if (prop.PropertyType == typeof(string)) + { + if (prop.GetValue(this) != null) + { + string propVal = prop.GetValue(this).ToString(); + if (propVal.Length > 0) + { + _score = _score + 1; + } + } + } + break; + } + } + } + + return _score; + } + } + } + + public class SignatureFlags + { + public int IGDBPlatformId { get; set; } + public string IGDBPlatformName { get; set; } } } } diff --git a/gaseous-server/Models/Signatures_Status.cs b/gaseous-server/Models/Signatures_Status.cs index 8d2c904..44f9cac 100644 --- a/gaseous-server/Models/Signatures_Status.cs +++ b/gaseous-server/Models/Signatures_Status.cs @@ -15,7 +15,7 @@ namespace gaseous_server.Models public Signatures_Status() { Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "select (select count(*) from signatures_sources) as SourceCount, (select count(*) from signatures_platforms) as PlatformCount, (select count(*) from signatures_games) as GameCount, (select count(*) from signatures_roms) as RomCount;"; + string sql = "select (select count(*) from Signatures_Sources) as SourceCount, (select count(*) from Signatures_Platforms) as PlatformCount, (select count(*) from Signatures_Games) as GameCount, (select count(*) from Signatures_Roms) as RomCount;"; DataTable sigDb = db.ExecuteCMD(sql); if (sigDb.Rows.Count > 0) diff --git a/gaseous-server/ProcessQueue.cs b/gaseous-server/ProcessQueue.cs new file mode 100644 index 0000000..dcff93d --- /dev/null +++ b/gaseous-server/ProcessQueue.cs @@ -0,0 +1,140 @@ +using System; +using gaseous_tools; + +namespace gaseous_server +{ + public static class ProcessQueue + { + public static List QueueItems = new List(); + + public class QueueItem + { + public QueueItem(QueueItemType ItemType, int ExecutionInterval) + { + _ItemType = ItemType; + _ItemState = QueueItemState.NeverStarted; + _LastRunTime = DateTime.UtcNow.AddMinutes(ExecutionInterval); + _Interval = ExecutionInterval; + } + + public QueueItem(QueueItemType ItemType, int ExecutionInterval, List Blocks) + { + _ItemType = ItemType; + _ItemState = QueueItemState.NeverStarted; + _LastRunTime = DateTime.UtcNow.AddMinutes(ExecutionInterval); + _Interval = ExecutionInterval; + _Blocks = Blocks; + } + + private QueueItemType _ItemType = QueueItemType.NotConfigured; + private QueueItemState _ItemState = QueueItemState.NeverStarted; + private DateTime _LastRunTime = DateTime.UtcNow; + private DateTime _LastFinishTime = DateTime.UtcNow; + private int _Interval = 0; + private string _LastResult = ""; + private string? _LastError = null; + private bool _ForceExecute = false; + private List _Blocks = new List(); + + public QueueItemType ItemType => _ItemType; + public QueueItemState ItemState => _ItemState; + public DateTime LastRunTime => _LastRunTime; + public DateTime LastFinishTime => _LastFinishTime; + public DateTime NextRunTime { + get + { + return LastRunTime.AddMinutes(Interval); + } + } + public int Interval => _Interval; + public string LastResult => _LastResult; + public string? LastError => _LastError; + public bool Force => _ForceExecute; + public List Blocks => _Blocks; + + public void Execute() + { + if (_ItemState != QueueItemState.Disabled) + { + if ((DateTime.UtcNow > NextRunTime || _ForceExecute == true) && _ItemState != QueueItemState.Running) + { + // we can run - do some setup before we start processing + _LastRunTime = DateTime.UtcNow; + _ItemState = QueueItemState.Running; + _LastResult = ""; + _LastError = null; + + Logging.Log(Logging.LogType.Information, "Timered Event", "Executing " + _ItemType); + + try + { + switch (_ItemType) + { + case QueueItemType.SignatureIngestor: + Logging.Log(Logging.LogType.Information, "Timered Event", "Starting Signature Ingestor"); + SignatureIngestors.TOSEC.TOSECIngestor tIngest = new SignatureIngestors.TOSEC.TOSECIngestor(); + tIngest.Import(Config.LibraryConfiguration.LibrarySignatureImportDirectory_TOSEC); + break; + + case QueueItemType.TitleIngestor: + Logging.Log(Logging.LogType.Information, "Timered Event", "Starting Title Ingestor"); + Classes.ImportGames importGames = new Classes.ImportGames(Config.LibraryConfiguration.LibraryImportDirectory); + break; + + case QueueItemType.MetadataRefresh: + Logging.Log(Logging.LogType.Information, "Timered Event", "Starting Metadata Refresher"); + Classes.MetadataManagement.RefreshMetadata(true); + break; + + case QueueItemType.OrganiseLibrary: + Logging.Log(Logging.LogType.Information, "Timered Event", "Starting Library Organiser"); + Classes.ImportGame.OrganiseLibrary(); + break; + + case QueueItemType.LibraryScan: + Logging.Log(Logging.LogType.Information, "Timered Event", "Starting Library Scanner"); + Classes.ImportGame.LibraryScan(); + break; + + } + } + catch (Exception ex) + { + Logging.Log(Logging.LogType.Warning, "Timered Event", "An error occurred", ex); + _LastResult = ""; + _LastError = ex.ToString(); + } + + _ForceExecute = false; + _ItemState = QueueItemState.Stopped; + _LastFinishTime = DateTime.UtcNow; + } + } + } + + public void ForceExecute() + { + _ForceExecute = true; + } + } + + public enum QueueItemType + { + NotConfigured, + SignatureIngestor, + TitleIngestor, + MetadataRefresh, + OrganiseLibrary, + LibraryScan + } + + public enum QueueItemState + { + NeverStarted, + Running, + Stopped, + Disabled + } + } +} + diff --git a/gaseous-server/Program.cs b/gaseous-server/Program.cs index 6fd856e..82181bf 100644 --- a/gaseous-server/Program.cs +++ b/gaseous-server/Program.cs @@ -1,38 +1,120 @@ using System.Text.Json.Serialization; +using gaseous_server; using gaseous_tools; +using Microsoft.AspNetCore.Mvc; -var builder = WebApplication.CreateBuilder(args); - -// Add services to the container. - -builder.Services.AddControllers().AddJsonOptions(x => -{ - // serialize enums as strings in api responses (e.g. Role) - x.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); -}); -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); - -var app = builder.Build(); - -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.UseSwagger(); - app.UseSwaggerUI(); -} - -app.UseHttpsRedirection(); - -app.UseAuthorization(); - -app.MapControllers(); +Logging.Log(Logging.LogType.Information, "Startup", "Starting Gaseous Server"); // set up db Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); db.InitDB(); +// load app settings +Config.InitSettings(); + +// set initial values +Guid APIKey = Guid.NewGuid(); +if (Config.ReadSetting("API Key", "Test API Key") == "Test API Key") +{ + // it's a new api key save it + Logging.Log(Logging.LogType.Information, "Startup", "Setting initial API key"); + Config.SetSetting("API Key", APIKey.ToString()); +} + +// set up server +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddControllers().AddJsonOptions(x => +{ + // serialize enums as strings in api responses (e.g. Role) + x.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + + // suppress nulls + x.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; +}); +builder.Services.AddResponseCaching(); +builder.Services.AddControllers(options => +{ + options.CacheProfiles.Add("Default30", + new CacheProfile() + { + Duration = 30 + }); + options.CacheProfiles.Add("5Minute", + new CacheProfile() + { + Duration = 300, + Location = ResponseCacheLocation.Any + }); + options.CacheProfiles.Add("7Days", + new CacheProfile() + { + Duration = 604800, + Location = ResponseCacheLocation.Any + }); +}); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddHostedService(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +//if (app.Environment.IsDevelopment()) +//{ +app.UseSwagger(); +app.UseSwaggerUI(); +//} + +//app.UseHttpsRedirection(); + +app.UseResponseCaching(); + +app.UseAuthorization(); + +app.UseDefaultFiles(); +app.UseStaticFiles(); + +app.MapControllers(); + +// setup library directories +Config.LibraryConfiguration.InitLibrary(); + +// insert unknown platform and game if not present +gaseous_server.Classes.Metadata.Games.GetGame(0, false, false); +gaseous_server.Classes.Metadata.Platforms.GetPlatform(0); + +// organise library +//gaseous_server.Classes.ImportGame.OrganiseLibrary(); + +// add background tasks +ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(ProcessQueue.QueueItemType.SignatureIngestor, 60)); +ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( + ProcessQueue.QueueItemType.TitleIngestor, 1, + new List + { + ProcessQueue.QueueItemType.OrganiseLibrary, + ProcessQueue.QueueItemType.LibraryScan + }) + ); +ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(ProcessQueue.QueueItemType.MetadataRefresh, 360)); +ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( + ProcessQueue.QueueItemType.OrganiseLibrary, 2040, new List + { + ProcessQueue.QueueItemType.LibraryScan, + ProcessQueue.QueueItemType.TitleIngestor + }) + ); +ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( + ProcessQueue.QueueItemType.LibraryScan, 30, new List + { + ProcessQueue.QueueItemType.TitleIngestor, + ProcessQueue.QueueItemType.OrganiseLibrary + }) + ); + // start the app app.Run(); - diff --git a/gaseous-server/Support/PlatformMap.json b/gaseous-server/Support/PlatformMap.json new file mode 100644 index 0000000..2790941 --- /dev/null +++ b/gaseous-server/Support/PlatformMap.json @@ -0,0 +1,93 @@ +[ + { + "IGDBId": 15, + "IGDBName": "Commodore C64/128/MAX", + "AlternateNames": [ + "C64", + "Commodore C64", + "Commodore C128", + "Commodore C64DTV", + "Commodore C64/128/MAX" + ], + "KnownFileExtensions": [ + ".C64", + ".CRT", + ".D64", + ".D71", + ".D81", + ".G64", + ".PRG", + ".T64", + ".TAP" + ] + }, + { + "IGDBId": 16, + "IGDBName": "Amiga", + "AlternateNames": [ + "Amiga", + "Commodore Amiga" + ], + "KnownFileExtensions": [ + ".ADF", + ".ADZ", + ".DMS" + ] + }, + { + "IGDBId": 64, + "IGDBName": "Sega Master System/Mark III", + "AlternateNames": [ + "Sega Master System/Mark III", + "Sega Master System", + "Sega Mark III & Master System" + ], + "KnownFileExtensions": [ + ".SMS" + ] + }, + { + "IGDBId": 29, + "IGDBName": "Sega Mega Drive/Genesis", + "AlternateNames": [ + "Sega Mega Drive/Genesis", + "Sega Mega Drive", + "Sega Genesis", + "Sega Mega Drive & Genesis" + ], + "KnownFileExtensions": [ + ".GEN", + ".MD", + ".SG", + ".SMD" + ] + }, + { + "IGDBId": 4, + "IGDBName": "Nintendo 64", + "AlternateNames": [ + "Nintendo 64", + "N64" + ], + "KnownFileExtensions": [ + ".Z64" + ] + }, + { + "IGDBId": 18, + "IGDBName": "Nintendo Entertainment System", + "AlternateNames": [ + "Nintendo Entertainment System", + "NES" + ], + "KnownFileExtensions": [ + ".NES", + ".FDS", + ".FIG", + ".MGD", + ".SFC", + ".SMC", + ".SWC" + ] + } +] diff --git a/gaseous-server/Timer.cs b/gaseous-server/Timer.cs new file mode 100644 index 0000000..bbf8e1c --- /dev/null +++ b/gaseous-server/Timer.cs @@ -0,0 +1,79 @@ +using System; +using gaseous_tools; + +namespace gaseous_server +{ + // see: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-5.0&tabs=visual-studio-mac#timed-background-tasks-1 + public class TimedHostedService : IHostedService, IDisposable + { + private int executionCount = 0; + //private readonly ILogger _logger; + private Timer _timer; + + //public TimedHostedService(ILogger logger) + //{ + // _logger = logger; + //} + + public Task StartAsync(CancellationToken stoppingToken) + { + //_logger.LogInformation("Timed Hosted Service running."); + Logging.Log(Logging.LogType.Debug, "Background", "Starting background task monitor"); + + _timer = new Timer(DoWork, null, TimeSpan.Zero, + TimeSpan.FromSeconds(5)); + + return Task.CompletedTask; + } + + private void DoWork(object state) + { + var count = Interlocked.Increment(ref executionCount); + + //_logger.LogInformation( + // "Timed Hosted Service is working. Count: {Count}", count); + + foreach (ProcessQueue.QueueItem qi in ProcessQueue.QueueItems) { + if ((DateTime.UtcNow > qi.NextRunTime || qi.Force == true) && CheckProcessBlockList(qi) == true) { + qi.Execute(); + } + } + } + + public Task StopAsync(CancellationToken stoppingToken) + { + //_logger.LogInformation("Timed Hosted Service is stopping."); + Logging.Log(Logging.LogType.Debug, "Background", "Stopping background task monitor"); + + _timer?.Change(Timeout.Infinite, 0); + + return Task.CompletedTask; + } + + public void Dispose() + { + _timer?.Dispose(); + } + + private bool CheckProcessBlockList(ProcessQueue.QueueItem queueItem) + { + if (queueItem.Blocks.Count > 0) + { + foreach (ProcessQueue.QueueItem qi in ProcessQueue.QueueItems) + { + if (queueItem.Blocks.Contains(qi.ItemType) && qi.ItemState == ProcessQueue.QueueItemState.Running) + { + return false; + } + } + + return true; + } + else + { + return true; + } + } + } +} + diff --git a/gaseous-server/gaseous-server.csproj b/gaseous-server/gaseous-server.csproj index 14a2e01..98adb37 100644 --- a/gaseous-server/gaseous-server.csproj +++ b/gaseous-server/gaseous-server.csproj @@ -9,11 +9,103 @@ + + 4 + bin\Debug\net7.0\gaseous-server.xml + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -21,14 +113,56 @@ + + + - - - + - - + + true + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gaseous-server/wwwroot/.DS_Store b/gaseous-server/wwwroot/.DS_Store new file mode 100644 index 0000000..022e6ad Binary files /dev/null and b/gaseous-server/wwwroot/.DS_Store differ diff --git a/gaseous-server/wwwroot/android-chrome-192x192.png b/gaseous-server/wwwroot/android-chrome-192x192.png new file mode 100644 index 0000000..143a0d6 Binary files /dev/null and b/gaseous-server/wwwroot/android-chrome-192x192.png differ diff --git a/gaseous-server/wwwroot/android-chrome-512x512.png b/gaseous-server/wwwroot/android-chrome-512x512.png new file mode 100644 index 0000000..c6f960d Binary files /dev/null and b/gaseous-server/wwwroot/android-chrome-512x512.png differ diff --git a/gaseous-server/wwwroot/apple-touch-icon.png b/gaseous-server/wwwroot/apple-touch-icon.png new file mode 100644 index 0000000..72b8714 Binary files /dev/null and b/gaseous-server/wwwroot/apple-touch-icon.png differ diff --git a/gaseous-server/wwwroot/favicon-16x16.png b/gaseous-server/wwwroot/favicon-16x16.png new file mode 100644 index 0000000..a28d403 Binary files /dev/null and b/gaseous-server/wwwroot/favicon-16x16.png differ diff --git a/gaseous-server/wwwroot/favicon-32x32.png b/gaseous-server/wwwroot/favicon-32x32.png new file mode 100644 index 0000000..830c5bb Binary files /dev/null and b/gaseous-server/wwwroot/favicon-32x32.png differ diff --git a/gaseous-server/wwwroot/favicon.ico b/gaseous-server/wwwroot/favicon.ico new file mode 100644 index 0000000..df7b768 Binary files /dev/null and b/gaseous-server/wwwroot/favicon.ico differ diff --git a/gaseous-server/wwwroot/fonts/Commodore Pixelized v1.2.ttf b/gaseous-server/wwwroot/fonts/Commodore Pixelized v1.2.ttf new file mode 100644 index 0000000..6bf582a Binary files /dev/null and b/gaseous-server/wwwroot/fonts/Commodore Pixelized v1.2.ttf differ diff --git a/gaseous-server/wwwroot/images/YouTube.svg b/gaseous-server/wwwroot/images/YouTube.svg new file mode 100644 index 0000000..a2be57b --- /dev/null +++ b/gaseous-server/wwwroot/images/YouTube.svg @@ -0,0 +1 @@ + diff --git a/gaseous-server/wwwroot/images/cog.jpg b/gaseous-server/wwwroot/images/cog.jpg new file mode 100644 index 0000000..4b0b01e Binary files /dev/null and b/gaseous-server/wwwroot/images/cog.jpg differ diff --git a/gaseous-server/wwwroot/images/logo.png b/gaseous-server/wwwroot/images/logo.png new file mode 100644 index 0000000..302efbb Binary files /dev/null and b/gaseous-server/wwwroot/images/logo.png differ diff --git a/gaseous-server/wwwroot/images/unknowngame.png b/gaseous-server/wwwroot/images/unknowngame.png new file mode 100644 index 0000000..073f4a8 Binary files /dev/null and b/gaseous-server/wwwroot/images/unknowngame.png differ diff --git a/gaseous-server/wwwroot/index.html b/gaseous-server/wwwroot/index.html new file mode 100644 index 0000000..e99a798 --- /dev/null +++ b/gaseous-server/wwwroot/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + Gaseous Games + + + + + + +
+ +
+ + + + + + + diff --git a/gaseous-server/wwwroot/pages/dialogs/romdelete.html b/gaseous-server/wwwroot/pages/dialogs/romdelete.html new file mode 100644 index 0000000..245de17 --- /dev/null +++ b/gaseous-server/wwwroot/pages/dialogs/romdelete.html @@ -0,0 +1,18 @@ +

Are you sure you want to delete this ROM?

+

Warning: This cannot be undone!

+
+
+ +
+
+ +
+
+ + \ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/dialogs/rominfo.html b/gaseous-server/wwwroot/pages/dialogs/rominfo.html new file mode 100644 index 0000000..86dedfc --- /dev/null +++ b/gaseous-server/wwwroot/pages/dialogs/rominfo.html @@ -0,0 +1,301 @@ + + + +
+
General
+
Title Match
+ +
+
+ + + + + +
+ + diff --git a/gaseous-server/wwwroot/pages/game.html b/gaseous-server/wwwroot/pages/game.html new file mode 100644 index 0000000..80a1851 --- /dev/null +++ b/gaseous-server/wwwroot/pages/game.html @@ -0,0 +1,413 @@ +
+
+
+ +
+
+

+

+

+ Also known as: +

+
+ + +
+
+
+

Genres

+
+
+

Developers

+
+
+

Publishers

+
+
+

Age Ratings

+
+
+ +
+
+
+
<
+
>
+
+
+ +
+
+ + + + +
+
+

ROM's/Images

+
+
+ + +
+ \ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/home.html b/gaseous-server/wwwroot/pages/home.html new file mode 100644 index 0000000..439e388 --- /dev/null +++ b/gaseous-server/wwwroot/pages/home.html @@ -0,0 +1,16 @@ +
+
+
+
+ + \ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/system.html b/gaseous-server/wwwroot/pages/system.html new file mode 100644 index 0000000..794d783 --- /dev/null +++ b/gaseous-server/wwwroot/pages/system.html @@ -0,0 +1,206 @@ +
+
+

System

+
+ +

Background Tasks

+
+ +

Usage

+

Library

+
+

Database

+
+ +

Signatures

+
+
+ + diff --git a/gaseous-server/wwwroot/scripts/filterformating.js b/gaseous-server/wwwroot/scripts/filterformating.js new file mode 100644 index 0000000..b5ed334 --- /dev/null +++ b/gaseous-server/wwwroot/scripts/filterformating.js @@ -0,0 +1,124 @@ +function formatFilterPanel(targetElement, result) { + var panel = document.createElement('div'); + panel.id = 'filter_panel_box'; + + panel.appendChild(buildFilterPanelHeader('filter', 'Filter')); + + var containerPanelSearch = document.createElement('div'); + containerPanelSearch.className = 'filter_panel_box'; + var containerPanelSearchField = document.createElement('input'); + containerPanelSearchField.id = 'filter_panel_search'; + containerPanelSearchField.type = 'text'; + containerPanelSearchField.placeholder = 'Search'; + containerPanelSearchField.setAttribute('onkeydown', 'executeFilterDelayed();'); + containerPanelSearch.appendChild(containerPanelSearchField); + + panel.appendChild(containerPanelSearch); + + 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); + } + + 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); + } + + +} + +function buildFilterPanelHeader(headerString, friendlyHeaderString) { + var header = document.createElement('div'); + header.id = 'filter_panel_header_' + headerString; + header.className = 'filter_header'; + header.innerHTML = friendlyHeaderString; + + return header; +} + +function buildFilterPanelItem(filterType, itemString, friendlyItemString) { + var filterPanelItem = document.createElement('div'); + filterPanelItem.id = 'filter_panel_item_' + itemString; + filterPanelItem.className = 'filter_panel_item'; + + var filterPanelItemCheckBox = document.createElement('div'); + + var filterPanelItemCheckBoxItem = document.createElement('input'); + filterPanelItemCheckBoxItem.id = 'filter_panel_item_checkbox_' + itemString; + filterPanelItemCheckBoxItem.type = 'checkbox'; + filterPanelItemCheckBoxItem.className = 'filter_panel_item_checkbox'; + filterPanelItemCheckBoxItem.name = 'filter_' + filterType; + filterPanelItemCheckBoxItem.setAttribute('filter_id', itemString); + filterPanelItemCheckBoxItem.setAttribute('oninput' , 'executeFilter();'); + filterPanelItemCheckBox.appendChild(filterPanelItemCheckBoxItem); + + var filterPanelItemLabel = document.createElement('label'); + filterPanelItemLabel.id = 'filter_panel_item_label_' + itemString; + filterPanelItemLabel.className = 'filter_panel_item_label'; + filterPanelItemLabel.setAttribute('for', filterPanelItemCheckBoxItem.id); + filterPanelItemLabel.innerHTML = friendlyItemString; + + filterPanelItem.appendChild(filterPanelItemCheckBox); + filterPanelItem.appendChild(filterPanelItemLabel); + + return filterPanelItem; +} + +var filterExecutor = null; +function executeFilterDelayed() { + if (filterExecutor) { + filterExecutor = null; + } + + filterExecutor = setTimeout(executeFilter, 1000); +} + +function executeFilter() { + // build filter lists + var platforms = ''; + var genres = ''; + + var searchString = document.getElementById('filter_panel_search').value; + var platformFilters = document.getElementsByName('filter_platforms'); + var genreFilters = document.getElementsByName('filter_genres'); + + for (var i = 0; i < platformFilters.length; i++) { + if (platformFilters[i].checked) { + if (platforms.length > 0) { + platforms += ','; + } + platforms += platformFilters[i].getAttribute('filter_id'); + } + } + + for (var i = 0; i < genreFilters.length; i++) { + if (genreFilters[i].checked) { + if (genres.length > 0) { + genres += ','; + } + genres += genreFilters[i].getAttribute('filter_id'); + } + } + + ajaxCall('/api/v1/Games?name=' + searchString + '&platform=' + platforms + '&genre=' + genres, 'GET', function (result) { + var gameElement = document.getElementById('games_library'); + formatGamesPanel(gameElement, result); + }); +} \ No newline at end of file diff --git a/gaseous-server/wwwroot/scripts/gamesformating.js b/gaseous-server/wwwroot/scripts/gamesformating.js new file mode 100644 index 0000000..1dc237e --- /dev/null +++ b/gaseous-server/wwwroot/scripts/gamesformating.js @@ -0,0 +1,46 @@ +function formatGamesPanel(targetElement, result) { + targetElement.innerHTML = ''; + for (var i = 0; i < result.length; i++) { + var game = renderGameIcon(result[i], true, false); + targetElement.appendChild(game); + } +} + +function renderGameIcon(gameObject, showTitle, showRatings) { + var gameBox = document.createElement('div'); + gameBox.className = 'game_tile'; + gameBox.setAttribute('onclick', 'window.location.href = "/index.html?page=game&id=' + gameObject.id + '";'); + + var gameImage = document.createElement('img'); + gameImage.className = 'game_tile_image'; + if (gameObject.cover) { + gameImage.src = '/api/v1/Games/' + gameObject.id + '/cover/image'; + } else { + gameImage.src = '/images/unknowngame.png'; + gameImage.className = 'game_tile_image unknown'; + } + gameBox.appendChild(gameImage); + + if (showTitle == true) { + var gameBoxTitle = document.createElement('div'); + gameBoxTitle.class = 'game_tile_label'; + gameBoxTitle.innerHTML = gameObject.name; + gameBox.appendChild(gameBoxTitle); + } + + if (showRatings == true) { + if (gameObject.ageRatings) { + var ratingsSection = document.createElement('div'); + ratingsSection.id = 'ratings_section'; + for (var i = 0; i < gameObject.ageRatings.ids.length; i++) { + var ratingImage = document.createElement('img'); + ratingImage.src = '/api/v1/Games/' + gameObject.id + '/agerating/' + gameObject.ageRatings.ids[i] + '/image'; + ratingImage.className = 'rating_image_mini'; + ratingsSection.appendChild(ratingImage); + } + gameBox.appendChild(ratingsSection); + } + } + + return gameBox; +} \ No newline at end of file diff --git a/gaseous-server/wwwroot/scripts/main.js b/gaseous-server/wwwroot/scripts/main.js new file mode 100644 index 0000000..0c0d7c8 --- /dev/null +++ b/gaseous-server/wwwroot/scripts/main.js @@ -0,0 +1,102 @@ +function ajaxCall(endpoint, method, successFunction) { + $.ajax({ + + // Our sample url to make request + url: + endpoint, + + // Type of Request + type: method, + + // Function to call when to + // request is ok + success: function (data) { + var x = JSON.stringify(data); + console.log(x); + successFunction(data); + }, + + // Error handling + error: function (error) { + console.log(`Error ${error}`); + } + }); +} + +function formatBytes(bytes, decimals = 2) { + if (!+bytes) return '0 Bytes' + + const k = 1024 + const dm = decimals < 0 ? 0 : decimals + const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] + + const i = Math.floor(Math.log(bytes) / Math.log(k)) + + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}` +} + +function showDialog(dialogPage, variables) { + // Get the modal + var modal = document.getElementById("myModal"); + + // Get the modal content + var modalContent = document.getElementById("modal-content"); + + // Get the button that opens the modal + var btn = document.getElementById("myBtn"); + + // Get the element that closes the modal + var span = document.getElementsByClassName("close")[0]; + + // When the user clicks on the button, open the modal + modal.style.display = "block"; + + // When the user clicks on (x), close the modal + span.onclick = function () { + modal.style.display = "none"; + modalContent.innerHTML = ""; + modalVariables = null; + } + + // When the user clicks anywhere outside of the modal, close it + window.onclick = function (event) { + if (event.target == modal) { + modal.style.display = "none"; + modalContent.innerHTML = ""; + modalVariables = null; + } + } + + modalVariables = variables; + + $('#modal-content').load('/pages/dialogs/' + dialogPage + '.html'); +} + +function randomIntFromInterval(min, max) { // min and max included + var rand = Math.floor(Math.random() * (max - min + 1) + min); + return rand; +} + +function createTableRow(isHeader, row, rowClass, cellClass) { + var newRow = document.createElement('tr'); + newRow.className = rowClass; + + for (var i = 0; i < row.length; i++) { + var cellType = 'td'; + if (isHeader == true) { + cellType = 'th'; + } + + var newCell = document.createElement(cellType); + if (typeof(row[i]) != "object") { + newCell.innerHTML = row[i]; + } else { + newCell.appendChild(row[i]); + } + newCell.className = cellClass; + + newRow.appendChild(newCell); + } + + return newRow; +} \ No newline at end of file diff --git a/gaseous-server/wwwroot/site.webmanifest b/gaseous-server/wwwroot/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/gaseous-server/wwwroot/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/gaseous-server/wwwroot/styles/style.css b/gaseous-server/wwwroot/styles/style.css new file mode 100644 index 0000000..449efc9 --- /dev/null +++ b/gaseous-server/wwwroot/styles/style.css @@ -0,0 +1,581 @@ +body { + background-color: #383838; + color: white; + font-family: "PT Sans", Arial, Helvetica, sans-serif; + font-kerning: normal; + font-style: normal; + font-weight: 100; + font-size: 13px; +} + +@font-face { + font-family: Commodore64; + src: url('/fonts/Commodore Pixelized v1.2.ttf'); +} + +h3 { + border-bottom-style: solid; + /*border-bottom-color: #916b01;*/ + border-bottom-width: 1px; + /*border-image: linear-gradient(to right, blue 25%, yellow 25%, yellow 50%,red 50%, red 75%, teal 75%) 5;*/ + + border-image: linear-gradient(to right, rgba(255,0,0,1) 0%, rgba(251,255,0,1) 16%, rgba(0,255,250,1) 30%, rgba(0,16,255,1) 46%, rgba(250,0,255,1) 62%, rgba(255,0,0,1) 78%, rgba(255,237,0,1) 90%, rgba(20,255,0,1) 100%) 5; +} + +/* The Modal (background) */ +.modal { + display: none; /* Hidden by default */ + position: fixed; /* Stay in place */ + z-index: 1; /* Sit on top */ + left: 0; + top: 0; + width: 100%; /* Full width */ + height: 100%; /* Full height */ + overflow: auto; /* Enable scroll if needed */ + background-color: rgb(0,0,0); /* Fallback color */ + background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + filter: drop-shadow(5px 5px 10px #000); + -webkit-filter: drop-shadow(5px 5px 10px #000); +} + +/* Modal Content/Box */ +.modal-content { + background-color: #383838; + margin: 15% auto; /* 15% from the top and centered */ + padding: 10px; + border: 1px solid #888; + width: 700px; /* Could be more or less, depending on screen size */ + min-height: 358px; +} +.modal-content-sub { + background-color: #383838; + margin: 20% auto; /* 20% from the top and centered */ + padding: 10px; + border: 1px solid #888; + width: 300px; /* Could be more or less, depending on screen size */ + min-height: 110px; +} +#modal-heading { + margin-block: 5px; + border-bottom-style: solid; + /*border-bottom-color: #916b01;*/ + border-bottom-width: 3px; + /*border-image: linear-gradient(to right, blue 25%, yellow 25%, yellow 50%,red 50%, red 75%, teal 75%) 5;*/ + + border-image: linear-gradient(to right, rgba(255,0,0,1) 0%, rgba(251,255,0,1) 16%, rgba(0,255,250,1) 30%, rgba(0,16,255,1) 46%, rgba(250,0,255,1) 62%, rgba(255,0,0,1) 78%, rgba(255,237,0,1) 90%, rgba(20,255,0,1) 100%) 5; +} + +/* The Close Button */ +.close { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; +} + +.close:hover, +.close:focus { + color: black; + text-decoration: none; + cursor: pointer; +} + +#banner_icon { + background-color: white; + position: fixed; + top: 0px; + left: 0px; + width: 40px; + height: 40px; + align-items: center; + justify-content: center; + padding: 0px; + margin: 0px; + display: flex; +} + +#banner_icon:hover { + cursor: pointer; +} + +#banner_icon_image { + width: 30px; + height: 30px; +} + +#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; +} + +#banner_system_image { + height: 30px; + width: 30px; +} + +#banner_header { + background-color: rgba(0, 22, 56, 0.8); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + position: fixed; + top: 0px; + left: 40px; + height: 40px; + right: 0px; + align-items: center; + display: flex; +} + +#banner_header_label { + font-family: Commodore64; + display: inline; + padding: 10px; + font-size: 16pt; + vertical-align: top; + /*color: #edeffa;*/ + color: #7c70da; +} + +#content { + margin-top: 35px; + padding-top: 5px; + padding-left: 5px; + padding-right: 5px; + z-index: -100; +} + +#games_filter { + width: 200px; + border-style: solid; + border-width: 1px; + border-color: #2b2b2b; + margin-right: 10px; +} + +.filter_header { + padding: 10px; + background-color: #2b2b2b; +} + +.filter_panel_box { + padding: 10px; +} + +input[type='text'] { + background-color: #2b2b2b; + color: white; + padding: 5px; + font-family: "PT Sans", Arial, Helvetica, sans-serif; + font-kerning: normal; + font-style: normal; + font-weight: 100; + font-size: 13px; + border-radius: 5px; + border-width: 1px; + border-style: solid; + border-color: lightgray; +} + +input[id='filter_panel_search'] { + width: 160px; +} + +/* width */ +::-webkit-scrollbar { + width: 10px; + height: 5px; +} + +/* Track */ +::-webkit-scrollbar-track { + background: #383838; +} + +/* Handle */ +::-webkit-scrollbar-thumb { + background: #888; +} + +/* Handle on hover */ +::-webkit-scrollbar-thumb:hover { + background: #555; +} + +.text_link { + +} + +.text_link:hover { + cursor: pointer; + text-decoration: underline; +} + +#games_home { + display: flex; + margin-top: 20px; +} + +.filter_panel_item { + display: flex; + padding: 3px; +} + +.filter_panel_item_checkbox { + margin-right: 10px; +} + +#games_library { + width: 90%; + border-style: solid; + border-width: 1px; + border-color: #2b2b2b; + padding: 10px; +} + +.game_tile { + padding: 5px; + display: inline-block; + width: 220px; + align-items: center; + justify-content: center; + text-align: center; + vertical-align: top; + margin-bottom: 10px; +} + +.game_tile:hover { + cursor: pointer; + text-decoration: underline; + background-color: #2b2b2b; +} + +.game_tile_image { + max-width: 200px; + max-height: 200px; +} + +.game_tile_image, .unknown { + background-color: white; +} + +#bgImage { + position: fixed; + top: -30px; + left: -30px; + right: -30px; + bottom: -30px; + z-index: -100; +} + +#bgImage_Opacity { + background: rgba(56, 56, 56, 0.7); + position: fixed; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; + z-index: -90; +} + +#gamepage { + top: 0px; + bottom: 0; + left: 0; + right: 0; + margin: auto; + width: 90%; + min-width: 911px; + max-width: 1122px; + min-height: 550px; + + padding-top: 1px; + padding-left: 20px; + padding-right: 20px; + padding-bottom: 20px; +} + +#mainbody { + margin-left: 270px; +} + +#gamesummary { + float: left; + margin-right: 20px; + width: 250px; +} + +.game_cover_image { + display: block; + max-width: 250px; + max-height: 350px; + width: 100%; +} + +.gamegenrelabel { + display: block; + white-space: pre; +} + +.rating_image { + display: inline-block; + max-width: 64px; + max-height: 64px; + margin-right: 20px; + margin-bottom: 20px; +} + +.rating_image_mini { + display: inline-block; + max-width: 32px; + max-height: 32px; + margin-right: 2px; +} + +#gamescreenshots { + background-color: black; + padding: 10px; + /*height: 350px;*/ + margin-bottom: 20px; +} + +#gamescreenshots_main { + display: block; + width: 100%; + height: 290px; + margin-bottom: 10px; +} + +iframe { + display: block; + margin: 0 auto; +} + +#gamescreenshots_gallery { + left: 0px; + right: 0px; + height: 65px; + overflow-x: scroll; + overflow-y: hidden; + white-space: normal; +} + +#gamescreenshots_gallery_panel { + display: block; + height: 50px; + margin-bottom: 10px; + white-space: nowrap; +} + +.gamescreenshots_gallery_item { + display: inline-block; + height: 50px; + width: 70px; + margin-right: 10px; + white-space: nowrap; + border-color: black; + border-style: solid; + border-width: 2px; + cursor: pointer; +} + +.gamescreenshosts_gallery_item_youtube { + max-height: 20px; + max-width: 20px; + float: right; + margin-top: 30px; + margin-right: 5px; +} + +.gamescreenshots_arrows { + margin-top: 140px; + height: 50px; + width: 30px; + font-size: 40px; + background-color: #383838; + color: black; + text-align: center; + user-select: none; /* standard syntax */ + -webkit-user-select: none; /* webkit (safari, chrome) browsers */ + -moz-user-select: none; /* mozilla browsers */ + -khtml-user-select: none; /* webkit (konqueror) browsers */ + -ms-user-select: none; /* IE10+ */ +} + +.gamescreenshots_arrows:hover { + cursor: pointer; + background-color: #555; +} + +#gamescreenshots_left_arrow { + float: left; +} + +#gamescreenshots_right_arrow { + float: right; +} + +.gamescreenshosts_gallery_item:hover { + border-color: lightgray; +} + +.gamescreenshosts_gallery_item_selected { + border-color: white; +} + +#gamesummarytext_label { + +} + +.line-clamp-4 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + /* truncate to 4 lines */ + -webkit-line-clamp: 4; +} + +.romtable { + width: 100%; +} + +.romrow:hover { + background-color: #383838; + background: rgba(56, 56, 56, 0.3); +} + +.romcell { + padding: 5px; +} + +th { + text-align: left; + padding: 5px; +} + +.romlink { + color: white; + text-decoration: none; +} + +.romlink:hover { + color: white; + text-decoration: underline; + cursor: pointer; +} + +#gamedev_logo { + float: right; + max-height: 48px; +} + +#gamedeveloper_label { + font-size: 16px; +} + +#properties_toc { + float: left; + display: block; + width: 150px; + min-width: 150px; +} + +div[name="properties_toc_item"] { + padding: 10px; + border-bottom-width: 1px; + border-bottom-style: solid; + border-bottom-color: #2b2b2b; +} + +div[name="properties_toc_item"]:hover { + background-color: #2b2b2b; + cursor: pointer; +} + +.properties_toc_item_selected { + background-color: #2b2b2b; +} + +#properties_bodypanel { + border-left-width: 1px; + border-left-style: solid; + border-left-color: #2b2b2b; + margin-left: 150px; + padding-left: 10px; +} + +.select2-container--open .select2-dropdown--below, +.select2-container--open .select2-dropdown--above { + background: #2b2b2b; +} + +.select2-container--flat .select2-container--focus .select2-selection--multiple { + border: 1px solid #2b2b2b; +} + +.dropdown-div { + width: 100%; +} + +.dropdown-cover { + width: 85px; + max-width: 85px; +} + +.dropdown-label { + padding-left: 20px; + padding-top: 0px; + padding-bottom: 0px; + padding-right: 20px; + font-weight: bold; + text-align: left; +} + +button { + background-color: #888; + color: white; + border-width: 1px; + border-color: #555; + border-style: solid; + padding-top: 5px; + padding-bottom: 5px; + padding-left: 10px; + padding-right: 10px; +} + +button:hover { + background-color: #555; + cursor: pointer; +} + +button:disabled { + background-color: #555; + cursor: not-allowed; +} + +.redbutton { + background-color: darkred; + border-color: darkred; +} + +.redbutton:hover { + background-color: red; +} + +.redbutton:disabled { + background-color: #555; +} \ No newline at end of file diff --git a/gaseous-signature-ingestor/Program.cs b/gaseous-signature-ingestor/Program.cs index 6746f5f..3a0b65b 100644 --- a/gaseous-signature-ingestor/Program.cs +++ b/gaseous-signature-ingestor/Program.cs @@ -66,6 +66,7 @@ if (Directory.Exists(tosecXML)) tosecXML = Path.GetFullPath(tosecXML); string[] tosecPathContents = Directory.GetFiles(tosecXML); + Array.Sort(tosecPathContents); int lineFileNameLength = 0; int lineGameNameLength = 0; @@ -85,7 +86,7 @@ if (Directory.Exists(tosecXML)) // check tosec file md5 Console.WriteLine(" ==> Checking input file "); Common.hashObject hashObject = new Common.hashObject(tosecXMLFile); - sql = "SELECT * FROM signatures_sources WHERE sourcemd5=@sourcemd5"; + sql = "SELECT * FROM Signatures_Sources WHERE SourceMD5=@sourcemd5"; dbDict = new Dictionary(); dbDict.Add("sourcemd5", hashObject.md5hash); sigDB = db.ExecuteCMD(sql, dbDict); @@ -107,7 +108,7 @@ if (Directory.Exists(tosecXML)) Console.SetCursorPosition(0, Console.CursorTop - 1); Console.WriteLine(" ==> Storing file in database "); - sql = "SELECT * FROM signatures_sources WHERE sourcemd5=@sourcemd5"; + sql = "SELECT * FROM Signatures_Sources WHERE SourceMD5=@sourcemd5"; dbDict = new Dictionary(); dbDict.Add("name", Common.ReturnValueIfNull(tosecObject.Name, "")); dbDict.Add("description", Common.ReturnValueIfNull(tosecObject.Description, "")); @@ -125,7 +126,7 @@ if (Directory.Exists(tosecXML)) if (sigDB.Rows.Count == 0) { // entry not present, insert it - sql = "INSERT INTO signatures_sources (name, description, category, version, author, email, homepage, url, sourcetype, sourcemd5, sourcesha1) VALUES (@name, @description, @category, @version, @author, @email, @homepage, @uri, @sourcetype, @sourcemd5, @sourcesha1)"; + sql = "INSERT INTO Signatures_Sources (Name, Description, Category, Version, Author, Email, Homepage, Url, SourceType, SourceMD5, sourceSHA1) VALUES (@name, @description, @category, @version, @author, @email, @homepage, @uri, @sourcetype, @sourcemd5, @sourcesha1)"; db.ExecuteCMD(sql, dbDict); @@ -166,16 +167,16 @@ if (Directory.Exists(tosecXML)) int gameSystem = 0; if (gameObject.System != null) { - sql = "SELECT id FROM signatures_platforms WHERE platform=@platform"; + sql = "SELECT Id FROM Signatures_Platforms WHERE Platform=@platform"; sigDB = db.ExecuteCMD(sql, dbDict); if (sigDB.Rows.Count == 0) { // entry not present, insert it - sql = "INSERT INTO signatures_platforms (platform) VALUES (@platform); SELECT LAST_INSERT_ID()"; + sql = "INSERT INTO Signatures_Platforms (Platform) VALUES (@platform); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; sigDB = db.ExecuteCMD(sql, dbDict); - gameSystem = (int)sigDB.Rows[0][0]; + gameSystem = Convert.ToInt32(sigDB.Rows[0][0]); } else { @@ -188,15 +189,15 @@ if (Directory.Exists(tosecXML)) int gamePublisher = 0; if (gameObject.Publisher != null) { - sql = "SELECT * FROM signatures_publishers WHERE publisher=@publisher"; + sql = "SELECT * FROM Signatures_Publishers WHERE Publisher=@publisher"; sigDB = db.ExecuteCMD(sql, dbDict); if (sigDB.Rows.Count == 0) { // entry not present, insert it - sql = "INSERT INTO signatures_publishers (publisher) VALUES (@publisher); SELECT LAST_INSERT_ID()"; + sql = "INSERT INTO Signatures_Publishers (Publisher) VALUES (@publisher); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; sigDB = db.ExecuteCMD(sql, dbDict); - gamePublisher = (int)sigDB.Rows[0][0]; + gamePublisher = Convert.ToInt32(sigDB.Rows[0][0]); } else { @@ -207,18 +208,18 @@ if (Directory.Exists(tosecXML)) // store game int gameId = 0; - sql = "SELECT * FROM signatures_games WHERE name=@name AND year=@year AND publisherid=@publisher AND systemid=@systemid AND country=@country AND language=@language"; + sql = "SELECT * FROM Signatures_Games WHERE Name=@name AND Year=@year AND PublisherId=@publisher AND SystemId=@systemid AND Country=@country AND Language=@language"; sigDB = db.ExecuteCMD(sql, dbDict); if (sigDB.Rows.Count == 0) { // entry not present, insert it - sql = "INSERT INTO signatures_games " + - "(name, description, year, publisherid, demo, systemid, systemvariant, video, country, language, copyright) VALUES " + - "(@name, @description, @year, @publisherid, @demo, @systemid, @systemvariant, @video, @country, @language, @copyright); SELECT LAST_INSERT_ID()"; + sql = "INSERT INTO Signatures_Games " + + "(Name, Description, Year, PublisherId, Demo, SystemId, SystemVariant, Video, Country, Language, Copyright) VALUES " + + "(@name, @description, @year, @publisherid, @demo, @systemid, @systemvariant, @video, @country, @language, @copyright); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; sigDB = db.ExecuteCMD(sql, dbDict); - gameId = (int)sigDB.Rows[0][0]; + gameId = Convert.ToInt32(sigDB.Rows[0][0]); } else { @@ -231,7 +232,7 @@ if (Directory.Exists(tosecXML)) if (romObject.Md5 != null) { int romId = 0; - sql = "SELECT * FROM signatures_roms WHERE gameid=@gameid AND md5=@md5"; + sql = "SELECT * FROM Signatures_Roms WHERE GameId=@gameid AND MD5=@md5"; dbDict = new Dictionary(); dbDict.Add("gameid", gameId); dbDict.Add("name", Common.ReturnValueIfNull(romObject.Name, "")); @@ -264,11 +265,11 @@ if (Directory.Exists(tosecXML)) if (sigDB.Rows.Count == 0) { // entry not present, insert it - sql = "INSERT INTO signatures_roms (gameid, name, size, crc, md5, sha1, developmentstatus, flags, romtype, romtypemedia, medialabel) VALUES (@gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @flags, @romtype, @romtypemedia, @medialabel); SELECT LAST_INSERT_ID()"; + sql = "INSERT INTO Signatures_Roms (GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Flags, RomType, RomTypeMedia, MediaLabel) VALUES (@gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @flags, @romtype, @romtypemedia, @medialabel); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; sigDB = db.ExecuteCMD(sql, dbDict); - romId = (int)sigDB.Rows[0][0]; + romId = Convert.ToInt32(sigDB.Rows[0][0]); } else { diff --git a/gaseous-signature-ingestor/gaseous-signature-ingestor.csproj.user b/gaseous-signature-ingestor/gaseous-signature-ingestor.csproj.user new file mode 100644 index 0000000..46978fa --- /dev/null +++ b/gaseous-signature-ingestor/gaseous-signature-ingestor.csproj.user @@ -0,0 +1,8 @@ + + + + Project + -tosecpath ~/Downloads/TOSEC\ -\ DAT\ Pack\ -\ Complete\ \(3764\)\ \(TOSEC-v2023-01-23\)/TOSEC/ + true + + \ No newline at end of file diff --git a/gaseous-signature-parser/Parsers/TosecParser.cs b/gaseous-signature-parser/Parsers/TosecParser.cs index d63c4f8..a8e6f4d 100644 --- a/gaseous-signature-parser/Parsers/TosecParser.cs +++ b/gaseous-signature-parser/Parsers/TosecParser.cs @@ -365,6 +365,7 @@ namespace gaseous_signature_parser.parsers romObject.Crc = xmlGameDetail.Attributes["crc"]?.Value; romObject.Md5 = xmlGameDetail.Attributes["md5"]?.Value; romObject.Sha1 = xmlGameDetail.Attributes["sha1"]?.Value; + romObject.SignatureSource = RomSignatureObject.Game.Rom.SignatureSourceType.TOSEC; // parse name string[] romNameTokens = romDescription.Split("("); diff --git a/gaseous-tools/Common.cs b/gaseous-tools/Common.cs index 00bc713..9bae0ab 100644 --- a/gaseous-tools/Common.cs +++ b/gaseous-tools/Common.cs @@ -22,8 +22,20 @@ namespace gaseous_tools } } + static public DateTime ConvertUnixToDateTime(double UnixTimeStamp) + { + DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + dateTime = dateTime.AddSeconds(UnixTimeStamp).ToLocalTime(); + return dateTime; + } + public class hashObject { + public hashObject() + { + + } + public hashObject(string FileName) { var xmlStream = File.OpenRead(FileName); @@ -31,12 +43,15 @@ namespace gaseous_tools var md5 = MD5.Create(); byte[] md5HashByte = md5.ComputeHash(xmlStream); string md5Hash = BitConverter.ToString(md5HashByte).Replace("-", "").ToLowerInvariant(); - _md5hash = md5hash; + _md5hash = md5Hash; var sha1 = SHA1.Create(); + xmlStream.Position = 0; byte[] sha1HashByte = sha1.ComputeHash(xmlStream); string sha1Hash = BitConverter.ToString(sha1HashByte).Replace("-", "").ToLowerInvariant(); - _sha1hash = sha1hash; + _sha1hash = sha1Hash; + + xmlStream.Close(); } string _md5hash = ""; @@ -48,6 +63,10 @@ namespace gaseous_tools { return _md5hash; } + set + { + _md5hash = value; + } } public string sha1hash @@ -56,8 +75,30 @@ namespace gaseous_tools { return _sha1hash; } + set + { + _sha1hash = value; + } } } - } + + public static long DirSize(DirectoryInfo d) + { + long size = 0; + // Add file sizes. + FileInfo[] fis = d.GetFiles(); + foreach (FileInfo fi in fis) + { + size += fi.Length; + } + // Add subdirectory sizes. + DirectoryInfo[] dis = d.GetDirectories(); + foreach (DirectoryInfo di in dis) + { + size += DirSize(di); + } + return size; + } + } } diff --git a/gaseous-tools/Config.cs b/gaseous-tools/Config.cs index c9e8e4e..70d2830 100644 --- a/gaseous-tools/Config.cs +++ b/gaseous-tools/Config.cs @@ -1,5 +1,8 @@ using System; +using System.Data; +using Google.Protobuf.WellKnownTypes; using Newtonsoft.Json; +using IGDB.Models; namespace gaseous_tools { @@ -39,6 +42,51 @@ namespace gaseous_tools } } + public static ConfigFile.Library LibraryConfiguration + { + get + { + return _config.LibraryConfiguration; + } + } + + public static ConfigFile.IGDB IGDB + { + get + { + return _config.IGDBConfiguration; + } + } + + public static string LogPath + { + get + { + string logPath = Path.Combine(ConfigurationPath, "Logs"); + if (!Directory.Exists(logPath)) { + Directory.CreateDirectory(logPath); + } + return logPath; + } + } + + public static string LogFilePath + { + get + { + string logPathName = Path.Combine(LogPath, "Log " + DateTime.Now.ToUniversalTime().ToString("yyyyMMdd") + ".txt"); + return logPathName; + } + } + + public static ConfigFile.Logging LoggingConfiguration + { + get + { + return _config.LoggingConfiguration; + } + } + static Config() { if (_config == null) @@ -60,8 +108,7 @@ namespace gaseous_tools // no config file! // use defaults and save _config = new ConfigFile(); - string configRaw = Newtonsoft.Json.JsonConvert.SerializeObject(_config, Newtonsoft.Json.Formatting.Indented); - File.WriteAllText(ConfigurationFilePath, configRaw); + UpdateConfig(); } } @@ -72,7 +119,19 @@ namespace gaseous_tools public static void UpdateConfig() { // save any updates to the configuration - string configRaw = Newtonsoft.Json.JsonConvert.SerializeObject(_config, Newtonsoft.Json.Formatting.Indented); + Newtonsoft.Json.JsonSerializerSettings serializerSettings = new Newtonsoft.Json.JsonSerializerSettings + { + NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore, + Formatting = Newtonsoft.Json.Formatting.Indented + }; + serializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()); + string configRaw = Newtonsoft.Json.JsonConvert.SerializeObject(_config, serializerSettings); + + if (!Directory.Exists(ConfigurationPath)) + { + Directory.CreateDirectory(ConfigurationPath); + } + if (File.Exists(ConfigurationFilePath_Backup)) { File.Delete(ConfigurationFilePath_Backup); @@ -84,15 +143,154 @@ namespace gaseous_tools File.WriteAllText(ConfigurationFilePath, configRaw); } + private static Dictionary AppSettings = new Dictionary(); + + public static void InitSettings() + { + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT * FROM Settings"; + + DataTable dbResponse = db.ExecuteCMD(sql); + foreach (DataRow dataRow in dbResponse.Rows) + { + if (AppSettings.ContainsKey((string)dataRow["Setting"])) + { + AppSettings[(string)dataRow["Setting"]] = (string)dataRow["Value"]; + } + else + { + AppSettings.Add((string)dataRow["Setting"], (string)dataRow["Value"]); + } + } + } + + public static string ReadSetting(string SettingName, string DefaultValue) + { + if (AppSettings.ContainsKey(SettingName)) + { + return AppSettings[SettingName]; + } + else + { + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT * FROM Settings WHERE Setting = @SettingName"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("SettingName", SettingName); + dbDict.Add("Value", DefaultValue); + + try + { + Logging.Log(Logging.LogType.Debug, "Database", "Reading setting '" + SettingName + "'"); + DataTable dbResponse = db.ExecuteCMD(sql, dbDict); + if (dbResponse.Rows.Count == 0) + { + // no value with that name stored - respond with the default value + SetSetting(SettingName, DefaultValue); + return DefaultValue; + } + else + { + AppSettings.Add(SettingName, (string)dbResponse.Rows[0][0]); + return (string)dbResponse.Rows[0][0]; + } + } + catch (Exception ex) + { + Logging.Log(Logging.LogType.Critical, "Database", "Failed reading setting " + SettingName, ex); + throw; + } + } + } + + public static void SetSetting(string SettingName, string Value) + { + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "REPLACE INTO Settings (Setting, Value) VALUES (@SettingName, @Value)"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("SettingName", SettingName); + dbDict.Add("Value", Value); + + Logging.Log(Logging.LogType.Debug, "Database", "Storing setting '" + SettingName + "' to value: '" + Value + "'"); + try + { + db.ExecuteCMD(sql, dbDict); + + if (AppSettings.ContainsKey(SettingName)) + { + AppSettings[SettingName] = Value; + } + else + { + AppSettings.Add(SettingName, Value); + } + } + catch (Exception ex) + { + Logging.Log(Logging.LogType.Critical, "Database", "Failed storing setting" + SettingName, ex); + throw; + } + } + public class ConfigFile { public Database DatabaseConfiguration = new Database(); + [JsonIgnore] + public Library LibraryConfiguration = new Library(); + + public IGDB IGDBConfiguration = new IGDB(); + + public Logging LoggingConfiguration = new Logging(); + public class Database { - public string HostName = "localhost"; - public string UserName = "gaseous"; - public string Password = "gaseous"; + private static string _DefaultHostName { + get + { + if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("dbhost"))) + { + return Environment.GetEnvironmentVariable("dbhost"); + } + else + { + return "localhost"; + } + } + } + + private static string _DefaultUserName + { + get + { + if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("dbuser"))) + { + return Environment.GetEnvironmentVariable("dbuser"); + } + else + { + return "gaseous"; + } + } + } + + private static string _DefaultPassword + { + get + { + if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("dbpass"))) + { + return Environment.GetEnvironmentVariable("dbpass"); + } + else + { + return "gaseous"; + } + } + } + + public string HostName = _DefaultHostName; + public string UserName = _DefaultUserName; + public string Password = _DefaultPassword; public string DatabaseName = "gaseous"; public int Port = 3306; @@ -106,7 +304,141 @@ namespace gaseous_tools } } } + + public class Library + { + public string LibraryRootDirectory + { + get + { + return ReadSetting("LibraryRootDirectory", Path.Combine(Config.ConfigurationPath, "Data")); + } + set + { + SetSetting("LibraryRootDirectory", value); + } + } + + public string LibraryImportDirectory + { + get + { + return Path.Combine(LibraryRootDirectory, "Import"); + } + } + + public string LibraryDataDirectory + { + get + { + return Path.Combine(LibraryRootDirectory, "Library"); + } + } + + public string LibraryMetadataDirectory + { + get + { + return Path.Combine(LibraryRootDirectory, "Metadata"); + } + } + + public string LibraryMetadataDirectory_Platform(Platform platform) + { + string MetadataPath = Path.Combine(LibraryMetadataDirectory, "Platforms", platform.Slug); + if (!Directory.Exists(MetadataPath)) { Directory.CreateDirectory(MetadataPath); } + return MetadataPath; + } + + public string LibraryMetadataDirectory_Game(Game game) + { + string MetadataPath = Path.Combine(LibraryMetadataDirectory, "Games", game.Slug); + if (!Directory.Exists(MetadataPath)) { Directory.CreateDirectory(MetadataPath); } + return MetadataPath; + } + + public string LibraryMetadataDirectory_Company(Company company) + { + string MetadataPath = Path.Combine(LibraryMetadataDirectory, "Companies", company.Slug); + if (!Directory.Exists(MetadataPath)) { Directory.CreateDirectory(MetadataPath); } + return MetadataPath; + } + + public string LibrarySignatureImportDirectory + { + get + { + return Path.Combine(LibraryRootDirectory, "Signatures"); + } + } + + public string LibrarySignatureImportDirectory_TOSEC + { + get + { + return Path.Combine(LibrarySignatureImportDirectory, "TOSEC"); + } + } + + public void InitLibrary() + { + if (!Directory.Exists(LibraryRootDirectory)) { Directory.CreateDirectory(LibraryRootDirectory); } + if (!Directory.Exists(LibraryImportDirectory)) { Directory.CreateDirectory(LibraryImportDirectory); } + if (!Directory.Exists(LibraryDataDirectory)) { Directory.CreateDirectory(LibraryDataDirectory); } + if (!Directory.Exists(LibraryMetadataDirectory)) { Directory.CreateDirectory(LibraryMetadataDirectory); } + if (!Directory.Exists(LibrarySignatureImportDirectory)) { Directory.CreateDirectory(LibrarySignatureImportDirectory); } + if (!Directory.Exists(LibrarySignatureImportDirectory_TOSEC)) { Directory.CreateDirectory(LibrarySignatureImportDirectory_TOSEC); } + } + } + + public class IGDB + { + private static string _DefaultIGDBClientId + { + get + { + if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("igdbclientid"))) + { + return Environment.GetEnvironmentVariable("igdbclientid"); + } + else + { + return ""; + } + } + } + + private static string _DefaultIGDBSecret + { + get + { + if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("igdbclientsecret"))) + { + return Environment.GetEnvironmentVariable("igdbclientsecret"); + } + else + { + return ""; + } + } + } + + public string ClientId = _DefaultIGDBClientId; + public string Secret = _DefaultIGDBSecret; + } + + public class Logging + { + public bool DebugLogging = false; + + public LoggingFormat LogFormat = Logging.LoggingFormat.Json; + + public enum LoggingFormat + { + Json, + Text + } + } } } } - diff --git a/gaseous-tools/Database.cs b/gaseous-tools/Database.cs index 81bc5f7..c7d1bfe 100644 --- a/gaseous-tools/Database.cs +++ b/gaseous-tools/Database.cs @@ -4,6 +4,7 @@ using System.Data.SqlClient; using System.Diagnostics; using System.Reflection; using MySql.Data.MySqlClient; +using static gaseous_tools.Database; namespace gaseous_tools { @@ -64,6 +65,7 @@ namespace gaseous_tools // check if the database exists first - first run must have permissions to create a database string sql = "CREATE DATABASE IF NOT EXISTS `" + Config.DatabaseConfiguration.DatabaseName + "`;"; Dictionary dbDict = new Dictionary(); + Logging.Log(Logging.LogType.Information, "Database", "Creating database if it doesn't exist"); ExecuteCMD(sql, dbDict, 30, "server=" + Config.DatabaseConfiguration.HostName + ";port=" + Config.DatabaseConfiguration.Port + ";userid=" + Config.DatabaseConfiguration.UserName + ";password=" + Config.DatabaseConfiguration.Password); // check if schema version table is in place - if not, create the schema version table @@ -71,8 +73,9 @@ namespace gaseous_tools DataTable SchemaVersionPresent = ExecuteCMD(sql, dbDict); if (SchemaVersionPresent.Rows.Count == 0) { - // no schema table present - create it - sql = "CREATE TABLE `schema_version` (`schema_version` INT NOT NULL, PRIMARY KEY (`schema_version`)); INSERT INTO `schema_version` (`schema_version`) VALUES (0);"; + // no schema table present - create it + Logging.Log(Logging.LogType.Information, "Database", "Schema version table doesn't exist. Creating it."); + sql = "CREATE TABLE `schema_version` (`schema_version` INT NOT NULL, PRIMARY KEY (`schema_version`)); INSERT INTO `schema_version` (`schema_version`) VALUES (0);"; ExecuteCMD(sql, dbDict); } @@ -87,25 +90,30 @@ namespace gaseous_tools using (Stream stream = assembly.GetManifestResourceStream(resourceName)) using (StreamReader reader = new StreamReader(stream)) { - dbScript = reader.ReadToEnd(); + dbScript = reader.ReadToEnd(); // apply script sql = "SELECT schema_version FROM schema_version;"; + dbDict = new Dictionary(); DataTable SchemaVersion = ExecuteCMD(sql, dbDict); if (SchemaVersion.Rows.Count == 0) { - // something is broken here... where's the table? - throw new Exception("schema_version table is missing!"); + // something is broken here... where's the table? + Logging.Log(Logging.LogType.Critical, "Database", "Schema table missing! This shouldn't happen!"); + throw new Exception("schema_version table is missing!"); } else { int SchemaVer = (int)SchemaVersion.Rows[0][0]; - if (SchemaVer < i) + Logging.Log(Logging.LogType.Information, "Database", "Schema version is " + SchemaVer); + if (SchemaVer < i) { - // apply schema! - ExecuteCMD(dbScript, dbDict); + // apply schema! + Logging.Log(Logging.LogType.Information, "Database", "Schema update available - applying"); + ExecuteCMD(dbScript, dbDict); sql = "UPDATE schema_version SET schema_version=@schemaver"; + dbDict = new Dictionary(); dbDict.Add("schemaver", i); ExecuteCMD(sql, dbDict); } @@ -113,7 +121,8 @@ namespace gaseous_tools } } } - break; + Logging.Log(Logging.LogType.Information, "Database", "Database setup complete"); + break; } } @@ -146,6 +155,46 @@ namespace gaseous_tools } } + public void ExecuteTransactionCMD(List CommandList, int Timeout = 60) + { + object conn; + switch (_ConnectorType) + { + case databaseType.MySql: + { + var commands = new List>(); + foreach (SQLTransactionItem CommandItem in CommandList) + { + var nCmd = new Dictionary(); + nCmd.Add("sql", CommandItem.SQLCommand); + nCmd.Add("values", CommandItem.Parameters); + commands.Add(nCmd); + } + + conn = new MySQLServerConnector(_ConnectionString); + ((MySQLServerConnector)conn).TransactionExecCMD(commands, Timeout); + break; + } + } + } + + public class SQLTransactionItem + { + public SQLTransactionItem() + { + + } + + public SQLTransactionItem(string SQLCommand, Dictionary Parameters) + { + this.SQLCommand = SQLCommand; + this.Parameters = Parameters; + } + + public string? SQLCommand; + public Dictionary? Parameters = new Dictionary(); + } + private partial class MySQLServerConnector { private string DBConn = ""; @@ -159,6 +208,7 @@ namespace gaseous_tools { DataTable RetTable = new DataTable(); + Logging.Log(Logging.LogType.Debug, "Database", "Connecting to database"); MySqlConnection conn = new MySqlConnection(DBConn); conn.Open(); @@ -176,17 +226,67 @@ namespace gaseous_tools try { - RetTable.Load(cmd.ExecuteReader()); + Logging.Log(Logging.LogType.Debug, "Database", "Executing sql: '" + SQL + "'"); + if (Parameters.Count > 0) + { + string dictValues = string.Join(";", Parameters.Select(x => string.Join("=", x.Key, x.Value))); + Logging.Log(Logging.LogType.Debug, "Database", "Parameters: " + dictValues); + } + RetTable.Load(cmd.ExecuteReader()); } catch (Exception ex) { + Logging.Log(Logging.LogType.Critical, "Database", "Error while executing '" + SQL + "'", ex); Trace.WriteLine("Error executing " + SQL); Trace.WriteLine("Full exception: " + ex.ToString()); } + Logging.Log(Logging.LogType.Debug, "Database", "Closing database connection"); conn.Close(); return RetTable; } - } - } + + public void TransactionExecCMD(List> Parameters, int Timeout) + { + var conn = new MySqlConnection(DBConn); + conn.Open(); + var command = conn.CreateCommand(); + MySqlTransaction transaction; + transaction = conn.BeginTransaction(); + command.Connection = conn; + command.Transaction = transaction; + foreach (Dictionary Parameter in Parameters) + { + var cmd = buildcommand(conn, Parameter["sql"].ToString(), (Dictionary)Parameter["values"], Timeout); + cmd.Transaction = transaction; + cmd.ExecuteNonQuery(); + } + + transaction.Commit(); + conn.Close(); + } + + private MySqlCommand buildcommand(MySqlConnection Conn, string SQL, Dictionary Parameters, int Timeout) + { + var cmd = new MySqlCommand(); + cmd.Connection = Conn; + cmd.CommandText = SQL; + cmd.CommandTimeout = Timeout; + { + var withBlock = cmd.Parameters; + if (Parameters is object) + { + if (Parameters.Count > 0) + { + foreach (string param in Parameters.Keys) + withBlock.AddWithValue(param, Parameters[param]); + } + } + } + + return cmd; + } + + } + } } diff --git a/gaseous-tools/Database/MySQL/gaseous-1000.sql b/gaseous-tools/Database/MySQL/gaseous-1000.sql index 83b73cb..8d27bb7 100644 --- a/gaseous-tools/Database/MySQL/gaseous-1000.sql +++ b/gaseous-tools/Database/MySQL/gaseous-1000.sql @@ -1,13 +1,13 @@ --- MySQL dump 10.13 Distrib 8.0.32, for macos13.0 (arm64) +-- MySQL dump 10.13 Distrib 8.0.29, for macos12 (x86_64) -- -- Host: localhost Database: gaseous -- ------------------------------------------------------ --- Server version 8.0.32 +-- Server version 8.0.33 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!50503 SET NAMES utf8mb4 */; +/*!50503 SET NameS utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; @@ -16,131 +16,690 @@ /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- --- Table structure for table `signatures_games` +-- Table structure for table `AgeRating` -- -DROP TABLE IF EXISTS `signatures_games`; +DROP TABLE IF EXISTS `AgeRating`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `signatures_games` ( - `id` int NOT NULL AUTO_INCREMENT, - `name` varchar(255) DEFAULT NULL, - `description` varchar(255) DEFAULT NULL, - `year` varchar(15) DEFAULT NULL, - `publisherid` int DEFAULT NULL, - `demo` int DEFAULT NULL, - `systemid` int DEFAULT NULL, - `systemvariant` varchar(100) DEFAULT NULL, - `video` varchar(10) DEFAULT NULL, - `country` varchar(5) DEFAULT NULL, - `language` varchar(5) DEFAULT NULL, - `copyright` varchar(15) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `id_UNIQUE` (`id`), - KEY `publisher_idx` (`publisherid`), - KEY `system_idx` (`systemid`), - KEY `ingest_idx` (`name`,`year`,`publisherid`,`systemid`,`country`,`language`) USING BTREE, - CONSTRAINT `publisher` FOREIGN KEY (`publisherid`) REFERENCES `signatures_publishers` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `system` FOREIGN KEY (`systemid`) REFERENCES `signatures_platforms` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB AUTO_INCREMENT=1466355 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +CREATE TABLE `AgeRating` ( + `Id` bigint NOT NULL, + `Category` int DEFAULT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `ContentDescriptions` json DEFAULT NULL, + `Rating` int DEFAULT NULL, + `RatingCoverUrl` varchar(255) DEFAULT NULL, + `Synopsis` longtext, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- --- Table structure for table `signatures_platforms` +-- Table structure for table `AgeRatingContentDescription` -- -DROP TABLE IF EXISTS `signatures_platforms`; +DROP TABLE IF EXISTS `AgeRatingContentDescription`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `signatures_platforms` ( - `id` int NOT NULL AUTO_INCREMENT, - `platform` varchar(100) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `idsignatures_platforms_UNIQUE` (`id`), - KEY `platforms_idx` (`platform`,`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=1231 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +CREATE TABLE `AgeRatingContentDescription` ( + `Id` bigint NOT NULL, + `Category` int DEFAULT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `Description` varchar(255) DEFAULT NULL, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- --- Table structure for table `signatures_publishers` +-- Table structure for table `AlternativeName` -- -DROP TABLE IF EXISTS `signatures_publishers`; +DROP TABLE IF EXISTS `AlternativeName`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `signatures_publishers` ( - `id` int NOT NULL AUTO_INCREMENT, - `publisher` varchar(100) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `id_UNIQUE` (`id`), - KEY `publisher_idx` (`publisher`,`id`) -) ENGINE=InnoDB AUTO_INCREMENT=97693 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +CREATE TABLE `AlternativeName` ( + `Id` bigint NOT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `Comment` longtext, + `Game` bigint DEFAULT NULL, + `Name` varchar(255) DEFAULT NULL, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- --- Table structure for table `signatures_roms` +-- Table structure for table `Artwork` -- -DROP TABLE IF EXISTS `signatures_roms`; +DROP TABLE IF EXISTS `Artwork`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `signatures_roms` ( - `id` int NOT NULL AUTO_INCREMENT, - `gameid` int DEFAULT NULL, - `name` varchar(255) DEFAULT NULL, - `size` bigint DEFAULT NULL, - `crc` varchar(20) DEFAULT NULL, - `md5` varchar(100) DEFAULT NULL, - `sha1` varchar(100) DEFAULT NULL, - `developmentstatus` varchar(100) DEFAULT NULL, - `flags` json DEFAULT NULL, - `romtype` int DEFAULT NULL, - `romtypemedia` varchar(100) DEFAULT NULL, - `medialabel` varchar(100) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `id_UNIQUE` (`id`,`gameid`) USING BTREE, - KEY `gameid_idx` (`gameid`), - KEY `md5_idx` (`md5`) USING BTREE, - KEY `sha1_idx` (`sha1`) USING BTREE, - KEY `flags_idx` ((cast(`flags` as char(255) array))), - CONSTRAINT `gameid` FOREIGN KEY (`gameid`) REFERENCES `signatures_games` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB AUTO_INCREMENT=3350963 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +CREATE TABLE `Artwork` ( + `Id` bigint NOT NULL, + `AlphaChannel` tinyint(1) DEFAULT NULL, + `Animated` tinyint(1) DEFAULT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `Game` bigint DEFAULT NULL, + `Height` int DEFAULT NULL, + `ImageId` varchar(45) DEFAULT NULL, + `Url` varchar(255) DEFAULT NULL, + `Width` int DEFAULT NULL, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- --- Table structure for table `signatures_sources` +-- Table structure for table `Collection` -- -DROP TABLE IF EXISTS `signatures_sources`; +DROP TABLE IF EXISTS `Collection`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `signatures_sources` ( - `id` int NOT NULL AUTO_INCREMENT, - `name` varchar(255) DEFAULT NULL, - `description` varchar(255) DEFAULT NULL, - `category` varchar(45) DEFAULT NULL, - `version` varchar(45) DEFAULT NULL, - `author` varchar(255) DEFAULT NULL, - `email` varchar(45) DEFAULT NULL, - `homepage` varchar(45) DEFAULT NULL, - `url` varchar(45) DEFAULT NULL, - `sourcetype` varchar(45) DEFAULT NULL, - `sourcemd5` varchar(45) DEFAULT NULL, - `sourcesha1` varchar(45) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `id_UNIQUE` (`id`), - KEY `sourcemd5_idx` (`sourcemd5`,`id`) USING BTREE, - KEY `sourcesha1_idx` (`sourcesha1`,`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=7573 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +CREATE TABLE `Collection` ( + `Id` bigint NOT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `Games` json DEFAULT NULL, + `Name` varchar(255) DEFAULT NULL, + `Slug` varchar(100) DEFAULT NULL, + `CreatedAt` datetime DEFAULT NULL, + `UpdatedAt` datetime DEFAULT NULL, + `Url` varchar(255) DEFAULT NULL, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; -/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; -/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; -/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; +-- +-- Table structure for table `Company` +-- --- Dump completed on 2023-02-27 8:54:22 +DROP TABLE IF EXISTS `Company`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `Company` ( + `Id` bigint NOT NULL, + `ChangeDate` datetime DEFAULT NULL, + `ChangeDateCategory` int DEFAULT NULL, + `ChangedCompanyId` bigint DEFAULT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `Country` int DEFAULT NULL, + `CreatedAt` datetime DEFAULT NULL, + `Description` longtext, + `Developed` json DEFAULT NULL, + `Logo` bigint DEFAULT NULL, + `Name` varchar(255) DEFAULT NULL, + `Parent` bigint DEFAULT NULL, + `Published` json DEFAULT NULL, + `Slug` varchar(100) DEFAULT NULL, + `StartDate` datetime DEFAULT NULL, + `StartDateCategory` int DEFAULT NULL, + `UpdatedAt` datetime DEFAULT NULL, + `Url` varchar(255) DEFAULT NULL, + `Websites` json DEFAULT NULL, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `CompanyLogo` +-- + +DROP TABLE IF EXISTS `CompanyLogo`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `CompanyLogo` ( + `Id` bigint NOT NULL, + `AlphaChannel` tinyint(1) DEFAULT NULL, + `Animated` tinyint(1) DEFAULT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `Height` int DEFAULT NULL, + `ImageId` varchar(45) DEFAULT NULL, + `Url` varchar(255) DEFAULT NULL, + `Width` int DEFAULT NULL, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `Cover` +-- + +DROP TABLE IF EXISTS `Cover`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `Cover` ( + `Id` bigint NOT NULL, + `AlphaChannel` tinyint(1) DEFAULT NULL, + `Animated` tinyint(1) DEFAULT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `Game` bigint DEFAULT NULL, + `Height` int DEFAULT NULL, + `ImageId` varchar(45) DEFAULT NULL, + `Url` varchar(255) DEFAULT NULL, + `Width` int DEFAULT NULL, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `ExternalGame` +-- + +DROP TABLE IF EXISTS `ExternalGame`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `ExternalGame` ( + `Id` bigint NOT NULL, + `Category` int DEFAULT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `CreatedAt` datetime DEFAULT NULL, + `Countries` json DEFAULT NULL, + `Game` bigint DEFAULT NULL, + `Media` int DEFAULT NULL, + `Name` varchar(255) DEFAULT NULL, + `Platform` bigint DEFAULT NULL, + `Uid` varchar(255) DEFAULT NULL, + `UpdatedAt` datetime DEFAULT NULL, + `Url` varchar(255) DEFAULT NULL, + `Year` int DEFAULT NULL, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `Franchise` +-- + +DROP TABLE IF EXISTS `Franchise`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `Franchise` ( + `Id` bigint NOT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `CreatedAt` datetime DEFAULT NULL, + `UpdatedAt` datetime DEFAULT NULL, + `Games` json DEFAULT NULL, + `Name` varchar(255) DEFAULT NULL, + `Slug` varchar(255) DEFAULT NULL, + `Url` varchar(255) DEFAULT NULL, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `Game` +-- + +DROP TABLE IF EXISTS `Game`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `Game` ( + `Id` bigint NOT NULL, + `AgeRatings` json DEFAULT NULL, + `AggregatedRating` double DEFAULT NULL, + `AggregatedRatingCount` int DEFAULT NULL, + `AlternativeNames` json DEFAULT NULL, + `Artworks` json DEFAULT NULL, + `Bundles` json DEFAULT NULL, + `Category` int DEFAULT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `Collection` bigint DEFAULT NULL, + `Cover` bigint DEFAULT NULL, + `CreatedAt` datetime DEFAULT NULL, + `Dlcs` json DEFAULT NULL, + `Expansions` json DEFAULT NULL, + `ExternalGames` json DEFAULT NULL, + `FirstReleaseDate` datetime DEFAULT NULL, + `Follows` int DEFAULT NULL, + `Franchise` bigint DEFAULT NULL, + `Franchises` json DEFAULT NULL, + `GameEngines` json DEFAULT NULL, + `GameModes` json DEFAULT NULL, + `Genres` json DEFAULT NULL, + `Hypes` int DEFAULT NULL, + `InvolvedCompanies` json DEFAULT NULL, + `Keywords` json DEFAULT NULL, + `MultiplayerModes` json DEFAULT NULL, + `Name` varchar(255) DEFAULT NULL, + `ParentGame` bigint DEFAULT NULL, + `Platforms` json DEFAULT NULL, + `PlayerPerspectives` json DEFAULT NULL, + `Rating` double DEFAULT NULL, + `RatingCount` int DEFAULT NULL, + `ReleaseDates` json DEFAULT NULL, + `Screenshots` json DEFAULT NULL, + `SimilarGames` json DEFAULT NULL, + `Slug` varchar(100) DEFAULT NULL, + `StandaloneExpansions` json DEFAULT NULL, + `Status` int DEFAULT NULL, + `StoryLine` longtext, + `Summary` longtext, + `Tags` json DEFAULT NULL, + `Themes` json DEFAULT NULL, + `TotalRating` double DEFAULT NULL, + `TotalRatingCount` int DEFAULT NULL, + `UpdatedAt` datetime DEFAULT NULL, + `Url` varchar(255) DEFAULT NULL, + `VersionParent` bigint DEFAULT NULL, + `VersionTitle` varchar(100) DEFAULT NULL, + `Videos` json DEFAULT NULL, + `Websites` json DEFAULT NULL, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`), + UNIQUE KEY `Id_UNIQUE` (`Id`), + KEY `Idx_AgeRatings` ((cast(`AgeRatings` as unsigned array))), + KEY `Idx_Genres` ((cast(`Genres` as unsigned array))), + KEY `Idx_alternativeNames` ((cast(`AlternativeNames` as unsigned array))), + KEY `Idx_artworks` ((cast(`Artworks` as unsigned array))), + KEY `Idx_bundles` ((cast(`Bundles` as unsigned array))), + KEY `Idx_dlcs` ((cast(`Dlcs` as unsigned array))), + KEY `Idx_expansions` ((cast(`Expansions` as unsigned array))), + KEY `Idx_ExternalGames` ((cast(`ExternalGames` as unsigned array))), + KEY `Idx_franchises` ((cast(`Franchises` as unsigned array))), + KEY `Idx_Gameengines` ((cast(`GameEngines` as unsigned array))), + KEY `Idx_Gamemodes` ((cast(`GameModes` as unsigned array))), + KEY `Idx_involvedcompanies` ((cast(`InvolvedCompanies` as unsigned array))), + KEY `Idx_keywords` ((cast(`Keywords` as unsigned array))), + KEY `Idx_multiplayermodes` ((cast(`MultiplayerModes` as unsigned array))), + KEY `Idx_Platforms` ((cast(`Platforms` as unsigned array))), + KEY `Idx_playerperspectives` ((cast(`PlayerPerspectives` as unsigned array))), + KEY `Idx_releasedates` ((cast(`ReleaseDates` as unsigned array))), + KEY `Idx_Screenshots` ((cast(`Screenshots` as unsigned array))), + KEY `Idx_similarGames` ((cast(`SimilarGames` as unsigned array))), + KEY `Idx_standaloneexpansions` ((cast(`StandaloneExpansions` as unsigned array))), + KEY `Idx_tags` ((cast(`Tags` as unsigned array))), + KEY `Idx_themes` ((cast(`Themes` as unsigned array))), + KEY `Idx_vIdeos` ((cast(`Videos` as unsigned array))), + KEY `Idx_websites` ((cast(`Websites` as unsigned array))) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `Games_Roms` +-- + +DROP TABLE IF EXISTS `Games_Roms`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `Games_Roms` ( + `Id` bigint NOT NULL AUTO_INCREMENT, + `PlatformId` bigint DEFAULT NULL, + `GameId` bigint DEFAULT NULL, + `Name` varchar(255) DEFAULT NULL, + `Size` bigint DEFAULT NULL, + `CRC` varchar(20) DEFAULT NULL, + `MD5` varchar(100) DEFAULT NULL, + `SHA1` varchar(100) DEFAULT NULL, + `DevelopmentStatus` varchar(100) DEFAULT NULL, + `Flags` json DEFAULT NULL, + `RomType` int DEFAULT NULL, + `RomTypeMedia` varchar(100) DEFAULT NULL, + `MediaLabel` varchar(100) DEFAULT NULL, + `Path` longtext, + `MetadataSource` int DEFAULT NULL, + PRIMARY KEY (`Id`), + UNIQUE KEY `Id_UNIQUE` (`Id`), + INDEX `GameId` (`GameId` ASC) VISIBLE, + INDEX `Id_GameId` (`GameId` ASC, `Id` ASC) VISIBLE +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `GameVideo` +-- + +DROP TABLE IF EXISTS `GameVideo`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `GameVideo` ( + `Id` bigint NOT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `Game` bigint DEFAULT NULL, + `Name` varchar(100) DEFAULT NULL, + `VideoId` varchar(45) DEFAULT NULL, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `Genre` +-- + +DROP TABLE IF EXISTS `Genre`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `Genre` ( + `Id` bigint NOT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `CreatedAt` datetime DEFAULT NULL, + `UpdatedAt` datetime DEFAULT NULL, + `Name` varchar(255) DEFAULT NULL, + `Slug` varchar(100) DEFAULT NULL, + `Url` varchar(255) DEFAULT NULL, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `InvolvedCompany` +-- + +DROP TABLE IF EXISTS `InvolvedCompany`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `InvolvedCompany` ( + `Id` bigint NOT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `Company` bigint DEFAULT NULL, + `CreatedAt` datetime DEFAULT NULL, + `Developer` tinyint(1) DEFAULT NULL, + `Game` bigint DEFAULT NULL, + `Porting` tinyint(1) DEFAULT NULL, + `Publisher` tinyint(1) DEFAULT NULL, + `Supporting` tinyint(1) DEFAULT NULL, + `UpdatedAt` datetime DEFAULT NULL, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `Platform` +-- + +DROP TABLE IF EXISTS `Platform`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `Platform` ( + `Id` bigint NOT NULL, + `Abbreviation` varchar(45) DEFAULT NULL, + `AlternativeName` varchar(255) DEFAULT NULL, + `Category` int DEFAULT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `CreatedAt` datetime DEFAULT NULL, + `Generation` int DEFAULT NULL, + `Name` varchar(45) DEFAULT NULL, + `PlatformFamily` bigint DEFAULT NULL, + `PlatformLogo` bigint DEFAULT NULL, + `Slug` varchar(45) DEFAULT NULL, + `Summary` longtext, + `UpdatedAt` datetime DEFAULT NULL, + `Url` varchar(255) DEFAULT NULL, + `Versions` json DEFAULT NULL, + `Websites` json DEFAULT NULL, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`), + UNIQUE KEY `Id_UNIQUE` (`Id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `PlatformLogo` +-- + +DROP TABLE IF EXISTS `PlatformLogo`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `PlatformLogo` ( + `Id` bigint NOT NULL, + `AlphaChannel` tinyint(1) DEFAULT NULL, + `Animated` tinyint(1) DEFAULT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `Height` int DEFAULT NULL, + `ImageId` varchar(45) DEFAULT NULL, + `Url` varchar(255) DEFAULT NULL, + `Width` int DEFAULT NULL, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `Platformversion` +-- + +DROP TABLE IF EXISTS `PlatformVersion`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `PlatformVersion` ( + `Id` bigint NOT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `Companies` json DEFAULT NULL, + `Connectivity` longtext, + `CPU` longtext, + `Graphics` longtext, + `MainManufacturer` bigint DEFAULT NULL, + `Media` longtext, + `Memory` longtext, + `Name` longtext, + `OS` longtext, + `Output` longtext, + `PlatformLogo` int DEFAULT NULL, + `PlatformVersionReleaseDates` json DEFAULT NULL, + `Resolutions` longtext, + `Slug` longtext, + `Sound` longtext, + `Storage` longtext, + `Summary` longtext, + `Url` varchar(255) DEFAULT NULL, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `Screenshot` +-- + +DROP TABLE IF EXISTS `Screenshot`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `Screenshot` ( + `Id` bigint NOT NULL, + `AlphaChannel` tinyint(1) DEFAULT NULL, + `Animated` tinyint(1) DEFAULT NULL, + `Checksum` varchar(45) DEFAULT NULL, + `Game` bigint DEFAULT NULL, + `Height` int DEFAULT NULL, + `ImageId` varchar(45) DEFAULT NULL, + `Url` varchar(255) DEFAULT NULL, + `Width` int DEFAULT NULL, + `dateAdded` datetime DEFAULT NULL, + `lastUpdated` datetime DEFAULT NULL, + PRIMARY KEY (`Id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `Settings` +-- + +DROP TABLE IF EXISTS `Settings`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `Settings` ( + `Setting` varchar(45) NOT NULL, + `Value` longtext, + PRIMARY KEY (`Setting`), + UNIQUE KEY `Setting_UNIQUE` (`Setting`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `Signatures_Games` +-- + +DROP TABLE IF EXISTS `Signatures_Games`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `Signatures_Games` ( + `Id` int NOT NULL AUTO_INCREMENT, + `Name` varchar(255) DEFAULT NULL, + `Description` varchar(255) DEFAULT NULL, + `Year` varchar(15) DEFAULT NULL, + `PublisherId` int DEFAULT NULL, + `Demo` int DEFAULT NULL, + `SystemId` int DEFAULT NULL, + `SystemVariant` varchar(100) DEFAULT NULL, + `Video` varchar(10) DEFAULT NULL, + `Country` varchar(5) DEFAULT NULL, + `Language` varchar(5) DEFAULT NULL, + `Copyright` varchar(15) DEFAULT NULL, + PRIMARY KEY (`Id`), + UNIQUE KEY `Id_UNIQUE` (`Id`), + KEY `publisher_Idx` (`PublisherId`), + KEY `system_Idx` (`SystemId`), + KEY `ingest_Idx` (`Name`,`Year`,`PublisherId`,`SystemId`,`Country`,`Language`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `Signatures_Platforms` +-- + +DROP TABLE IF EXISTS `Signatures_Platforms`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `Signatures_Platforms` ( + `Id` int NOT NULL AUTO_INCREMENT, + `Platform` varchar(100) DEFAULT NULL, + PRIMARY KEY (`Id`), + UNIQUE KEY `IdSignatures_Platforms_UNIQUE` (`Id`), + KEY `Platforms_Idx` (`Platform`,`Id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `Signatures_Publishers` +-- + +DROP TABLE IF EXISTS `Signatures_Publishers`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `Signatures_Publishers` ( + `Id` int NOT NULL AUTO_INCREMENT, + `Publisher` varchar(100) DEFAULT NULL, + PRIMARY KEY (`Id`), + UNIQUE KEY `Id_UNIQUE` (`Id`), + KEY `publisher_Idx` (`Publisher`,`Id`) +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `Signatures_Roms` +-- + +DROP TABLE IF EXISTS `Signatures_Roms`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `Signatures_Roms` ( + `Id` int NOT NULL AUTO_INCREMENT, + `GameId` int DEFAULT NULL, + `Name` varchar(255) DEFAULT NULL, + `Size` bigint DEFAULT NULL, + `CRC` varchar(20) DEFAULT NULL, + `MD5` varchar(100) DEFAULT NULL, + `SHA1` varchar(100) DEFAULT NULL, + `DevelopmentStatus` varchar(100) DEFAULT NULL, + `Flags` json DEFAULT NULL, + `RomType` int DEFAULT NULL, + `RomTypeMedia` varchar(100) DEFAULT NULL, + `MediaLabel` varchar(100) DEFAULT NULL, + `MetadataSource` int DEFAULT NULL, + PRIMARY KEY (`Id`), + UNIQUE KEY `Id_UNIQUE` (`Id`,`GameId`) USING BTREE, + KEY `GameId_Idx` (`GameId`), + KEY `md5_Idx` (`MD5`) USING BTREE, + KEY `sha1_Idx` (`SHA1`) USING BTREE, + KEY `flags_Idx` ((cast(`Flags` as char(255) array))) +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `Signatures_Sources` +-- + +DROP TABLE IF EXISTS `Signatures_Sources`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `Signatures_Sources` ( + `Id` int NOT NULL AUTO_INCREMENT, + `Name` varchar(255) DEFAULT NULL, + `Description` varchar(255) DEFAULT NULL, + `Category` varchar(45) DEFAULT NULL, + `Version` varchar(45) DEFAULT NULL, + `Author` varchar(255) DEFAULT NULL, + `Email` varchar(45) DEFAULT NULL, + `Homepage` varchar(45) DEFAULT NULL, + `Url` varchar(45) DEFAULT NULL, + `SourceType` varchar(45) DEFAULT NULL, + `SourceMD5` varchar(45) DEFAULT NULL, + `SourceSHA1` varchar(45) DEFAULT NULL, + PRIMARY KEY (`Id`), + UNIQUE KEY `Id_UNIQUE` (`Id`), + KEY `sourcemd5_Idx` (`SourceMD5`,`Id`) USING BTREE, + KEY `sourcesha1_Idx` (`SourceSHA1`,`Id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping events for database 'gaseous' +-- + +-- +-- Dumping routines for database 'gaseous' +-- + +-- +-- Final view structure for view `view_Signatures_Games` +-- + +DROP VIEW IF EXISTS `view_Signatures_Games`; +CREATE VIEW `view_Signatures_Games` AS + SELECT + `Signatures_Games`.`Id` AS `Id`, + `Signatures_Games`.`Name` AS `Name`, + `Signatures_Games`.`Description` AS `Description`, + `Signatures_Games`.`Year` AS `Year`, + `Signatures_Games`.`PublisherId` AS `PublisherId`, + `Signatures_Publishers`.`Publisher` AS `Publisher`, + `Signatures_Games`.`Demo` AS `Demo`, + `Signatures_Games`.`SystemId` AS `PlatformId`, + `Signatures_Platforms`.`Platform` AS `Platform`, + `Signatures_Games`.`SystemVariant` AS `SystemVariant`, + `Signatures_Games`.`VIdeo` AS `Video`, + `Signatures_Games`.`Country` AS `Country`, + `Signatures_Games`.`Language` AS `Language`, + `Signatures_Games`.`Copyright` AS `Copyright` + FROM + ((`Signatures_Games` + JOIN `Signatures_Publishers` ON ((`Signatures_Games`.`PublisherId` = `Signatures_Publishers`.`Id`))) + JOIN `Signatures_Platforms` ON ((`Signatures_Games`.`SystemId` = `Signatures_Platforms`.`Id`))); \ No newline at end of file diff --git a/gaseous-tools/Database/MySQL/gaseous-1001.sql b/gaseous-tools/Database/MySQL/gaseous-1001.sql deleted file mode 100644 index 1b39072..0000000 --- a/gaseous-tools/Database/MySQL/gaseous-1001.sql +++ /dev/null @@ -1,25 +0,0 @@ -CREATE - ALGORITHM = UNDEFINED - DEFINER = `root`@`localhost` - SQL SECURITY DEFINER -VIEW `view_signatures_games` AS - SELECT - `signatures_games`.`id` AS `id`, - `signatures_games`.`name` AS `name`, - `signatures_games`.`description` AS `description`, - `signatures_games`.`year` AS `year`, - `signatures_games`.`publisherid` AS `publisherid`, - `signatures_publishers`.`publisher` AS `publisher`, - `signatures_games`.`demo` AS `demo`, - `signatures_games`.`systemid` AS `platformid`, - `signatures_platforms`.`platform` AS `platform`, - `signatures_games`.`systemvariant` AS `systemvariant`, - `signatures_games`.`video` AS `video`, - `signatures_games`.`country` AS `country`, - `signatures_games`.`language` AS `language`, - `signatures_games`.`copyright` AS `copyright` - FROM - ((`signatures_games` - JOIN `signatures_publishers` ON ((`signatures_games`.`publisherid` = `signatures_publishers`.`id`))) - JOIN `signatures_platforms` ON ((`signatures_games`.`systemid` = `signatures_platforms`.`id`))); - diff --git a/gaseous-tools/Logging.cs b/gaseous-tools/Logging.cs new file mode 100644 index 0000000..0243cbf --- /dev/null +++ b/gaseous-tools/Logging.cs @@ -0,0 +1,102 @@ +using System; +namespace gaseous_tools +{ + public class Logging + { + static public void Log(LogType EventType, string Section, string Message, Exception? ExceptionValue = null) + { + LogItem logItem = new LogItem + { + EventTime = DateTime.UtcNow, + EventType = EventType, + Section = Section, + Message = Message, + ExceptionValue = ExceptionValue + }; + + bool AllowWrite = false; + if (EventType == LogType.Debug) + { + if (Config.LoggingConfiguration.DebugLogging == true) + { + AllowWrite = true; + } + } + else + { + AllowWrite = true; + } + + if (AllowWrite == true) + { + // console output + string TraceOutput = logItem.EventTime.ToString("yyyyMMdd HHmmss") + ": " + logItem.EventType.ToString() + ": " + logItem.Section + ": " + logItem.Message; + if (logItem.ExceptionValue != null) + { + TraceOutput += Environment.NewLine + logItem.ExceptionValue.ToString(); + } + Console.WriteLine(TraceOutput); + + StreamWriter LogFile = File.AppendText(Config.LogFilePath); + switch (Config.LoggingConfiguration.LogFormat) + { + case Config.ConfigFile.Logging.LoggingFormat.Text: + LogFile.WriteLine(TraceOutput); + break; + + case Config.ConfigFile.Logging.LoggingFormat.Json: + Newtonsoft.Json.JsonSerializerSettings serializerSettings = new Newtonsoft.Json.JsonSerializerSettings + { + NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore, + Formatting = Newtonsoft.Json.Formatting.Indented + }; + serializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()); + string JsonOutput = Newtonsoft.Json.JsonConvert.SerializeObject(logItem, serializerSettings); + LogFile.WriteLine(JsonOutput); + break; + } + LogFile.Close(); + } + } + + public enum LogType + { + Information = 0, + Debug = 1, + Warning = 2, + Critical = 3 + } + + public class LogItem + { + public DateTime EventTime { get; set; } + public LogType EventType { get; set; } + private string _Section = ""; + public string Section + { + get + { + return _Section; + } + set + { + _Section = value; + } + } + private string _Message = ""; + public string Message + { + get + { + return _Message; + } + set + { + _Message = value; + } + } + public Exception? ExceptionValue { get; set; } + } + } +} + diff --git a/gaseous-tools/gaseous-tools.csproj b/gaseous-tools/gaseous-tools.csproj index 87115a1..b67489d 100644 --- a/gaseous-tools/gaseous-tools.csproj +++ b/gaseous-tools/gaseous-tools.csproj @@ -10,12 +10,12 @@ + - @@ -23,6 +23,5 @@ -