diff --git a/.gitignore b/.gitignore index c608dca..21896b9 100644 --- a/.gitignore +++ b/.gitignore @@ -404,4 +404,7 @@ ASALocalRun/ # Local History for Visual Studio .localhistory/ gaseous-server/.DS_Store +gaseous-server/wwwroot/.DS_Store gaseous-server/wwwroot/emulators/EmulatorJS +.devcontainer/.env +.mono/ diff --git a/.vscode/launch.json b/.vscode/launch.json index 82476c1..4ec8f9b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -24,7 +24,8 @@ }, "sourceFileMap": { "/Views": "${workspaceFolder}/Views" - } + }, + "enableStepFiltering": false }, { "name": ".NET Core Attach", diff --git a/Gaseous.sln b/Gaseous.sln index 8b88e80..9eca1ea 100644 --- a/Gaseous.sln +++ b/Gaseous.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 25.0.1704.4 @@ -27,30 +27,18 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {08699C93-15CD-4E39-9053-D3C8056CE938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {08699C93-15CD-4E39-9053-D3C8056CE938}.Debug|Any CPU.Build.0 = Debug|Any CPU - {08699C93-15CD-4E39-9053-D3C8056CE938}.Release|Any CPU.ActiveCfg = Release|Any CPU - {08699C93-15CD-4E39-9053-D3C8056CE938}.Release|Any CPU.Build.0 = Release|Any CPU - {FFCEC386-033F-4772-A45B-D33579F2E5EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FFCEC386-033F-4772-A45B-D33579F2E5EE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FFCEC386-033F-4772-A45B-D33579F2E5EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FFCEC386-033F-4772-A45B-D33579F2E5EE}.Release|Any CPU.Build.0 = Release|Any CPU {A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Release|Any CPU.Build.0 = Release|Any CPU - {B07A4655-A003-416B-A790-ADAA5B548E1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B07A4655-A003-416B-A790-ADAA5B548E1A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B07A4655-A003-416B-A790-ADAA5B548E1A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B07A4655-A003-416B-A790-ADAA5B548E1A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {979BF092-AFBC-4F19-B55F-32E73959BD1A} - EndGlobalSection GlobalSection(NestedProjects) = preSolution {F1A847C7-57BC-4DA9-8F83-CD060A7F5122} = {17FA6F12-8532-420C-9489-CB8FDE42137C} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {979BF092-AFBC-4F19-B55F-32E73959BD1A} + EndGlobalSection EndGlobal diff --git a/LICENSE b/LICENSE index 0f2028b..29ebfa5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies @@ -7,17 +7,15 @@ Preamble - The GNU General Public License is a free, copyleft license for -software and other kinds of works. + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to +our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. +software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you @@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. The precise terms and conditions for copying, distribution and modification follow. @@ -72,7 +60,7 @@ modification follow. 0. Definitions. - "This License" refers to version 3 of the GNU General Public License. + "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. @@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. - 13. Use with the GNU Affero General Public License. + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single +under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General +Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published +GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's +versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. @@ -631,44 +629,33 @@ to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - Gaseous - Copyright (C) 2023 Gaseous + + Copyright (C) This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + GNU Affero General Public License for more details. - You should have received a copy of the GNU General Public License + You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) 2023 Gaseous - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. +For more information on this, and how to apply and follow the GNU AGPL, see +. \ No newline at end of file diff --git a/gaseous-server/Classes/Common.cs b/gaseous-server/Classes/Common.cs index 4cb2cdf..bd6cf8e 100644 --- a/gaseous-server/Classes/Common.cs +++ b/gaseous-server/Classes/Common.cs @@ -1,5 +1,6 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.ComponentModel; +using System.Data; using System.IO.Compression; using System.Reflection; using System.Security.Cryptography; diff --git a/gaseous-server/Classes/Config.cs b/gaseous-server/Classes/Config.cs index 26f9963..9ce5cca 100644 --- a/gaseous-server/Classes/Config.cs +++ b/gaseous-server/Classes/Config.cs @@ -80,7 +80,8 @@ namespace gaseous_server.Classes get { string logPath = Path.Combine(ConfigurationPath, "Logs"); - if (!Directory.Exists(logPath)) { + if (!Directory.Exists(logPath)) + { Directory.CreateDirectory(logPath); } return logPath; @@ -92,7 +93,7 @@ namespace gaseous_server.Classes get { string logFileExtension = "txt"; - + string logPathName = Path.Combine(LogPath, "Server Log " + DateTime.Now.ToUniversalTime().ToString("yyyyMMdd") + "." + logFileExtension); return logPathName; } @@ -131,7 +132,7 @@ namespace gaseous_server.Classes _config.DatabaseConfiguration.DatabaseName = (string)Common.GetEnvVar("dbname", _config.DatabaseConfiguration.DatabaseName); _config.DatabaseConfiguration.Port = int.Parse((string)Common.GetEnvVar("dbport", _config.DatabaseConfiguration.Port.ToString())); _config.MetadataConfiguration.MetadataSource = (HasheousClient.Models.MetadataModel.MetadataSources)Enum.Parse(typeof(HasheousClient.Models.MetadataModel.MetadataSources), (string)Common.GetEnvVar("metadatasource", _config.MetadataConfiguration.MetadataSource.ToString())); - _config.MetadataConfiguration.SignatureSource = (HasheousClient.Models.MetadataModel.SignatureSources)Enum.Parse(typeof(HasheousClient.Models.MetadataModel.SignatureSources), (string)Common.GetEnvVar("signaturesource", _config.MetadataConfiguration.SignatureSource.ToString()));; + _config.MetadataConfiguration.SignatureSource = (HasheousClient.Models.MetadataModel.SignatureSources)Enum.Parse(typeof(HasheousClient.Models.MetadataModel.SignatureSources), (string)Common.GetEnvVar("signaturesource", _config.MetadataConfiguration.SignatureSource.ToString())); ; _config.MetadataConfiguration.MaxLibraryScanWorkers = int.Parse((string)Common.GetEnvVar("maxlibraryscanworkers", _config.MetadataConfiguration.MaxLibraryScanWorkers.ToString())); _config.MetadataConfiguration.HasheousHost = (string)Common.GetEnvVar("hasheoushost", _config.MetadataConfiguration.HasheousHost); _config.IGDBConfiguration.ClientId = (string)Common.GetEnvVar("igdbclientid", _config.IGDBConfiguration.ClientId); @@ -205,9 +206,9 @@ namespace gaseous_server.Classes { AppSettings.Remove(SettingName); } - + Logging.Log(Logging.LogType.Information, "Load Settings", "Loading setting " + SettingName + " from database"); - + try { if (Database.schema_version >= 1016) @@ -275,7 +276,7 @@ namespace gaseous_server.Classes if (Database.schema_version >= 1016) { sql = "SELECT Value, ValueDate FROM Settings WHERE Setting = @SettingName"; - + dbResponse = db.ExecuteCMD(sql, dbDict); Type type = typeof(T); if (dbResponse.Rows.Count == 0) @@ -301,7 +302,7 @@ namespace gaseous_server.Classes else { sql = "SELECT Value FROM Settings WHERE Setting = @SettingName"; - + dbResponse = db.ExecuteCMD(sql, dbDict); Type type = typeof(T); if (dbResponse.Rows.Count == 0) @@ -355,7 +356,7 @@ namespace gaseous_server.Classes Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); string sql; Dictionary dbDict; - + if (Database.schema_version >= 1016) { sql = "REPLACE INTO Settings (Setting, ValueType, Value, ValueDate) VALUES (@SettingName, @ValueType, @Value, @ValueDate)"; @@ -427,7 +428,8 @@ namespace gaseous_server.Classes public class Database { - private static string _DefaultHostName { + private static string _DefaultHostName + { get { if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("dbhost"))) @@ -643,7 +645,7 @@ namespace gaseous_server.Classes return MetadataPath; } - public string LibrarySignatureImportDirectory + public string LibrarySignaturesDirectory { get { @@ -651,6 +653,14 @@ namespace gaseous_server.Classes } } + public string LibrarySignaturesProcessedDirectory + { + get + { + return Path.Combine(LibraryRootDirectory, "Signatures - Processed"); + } + } + public void InitLibrary() { if (!Directory.Exists(LibraryRootDirectory)) { Directory.CreateDirectory(LibraryRootDirectory); } @@ -660,7 +670,8 @@ namespace gaseous_server.Classes if (!Directory.Exists(LibraryMetadataDirectory)) { Directory.CreateDirectory(LibraryMetadataDirectory); } if (!Directory.Exists(LibraryTempDirectory)) { Directory.CreateDirectory(LibraryTempDirectory); } if (!Directory.Exists(LibraryCollectionsDirectory)) { Directory.CreateDirectory(LibraryCollectionsDirectory); } - if (!Directory.Exists(LibrarySignatureImportDirectory)) { Directory.CreateDirectory(LibrarySignatureImportDirectory); } + if (!Directory.Exists(LibrarySignaturesDirectory)) { Directory.CreateDirectory(LibrarySignaturesDirectory); } + if (!Directory.Exists(LibrarySignaturesProcessedDirectory)) { Directory.CreateDirectory(LibrarySignaturesProcessedDirectory); } } } @@ -696,6 +707,10 @@ namespace gaseous_server.Classes } } + private static bool _HasheousSubmitFixes { get; set; } = false; + + private static string _HasheousAPIKey { get; set; } = ""; + private static int _MaxLibraryScanWorkers { get @@ -730,6 +745,10 @@ namespace gaseous_server.Classes public HasheousClient.Models.MetadataModel.SignatureSources SignatureSource = _SignatureSource; + public bool HasheousSubmitFixes = _HasheousSubmitFixes; + + public string HasheousAPIKey = _HasheousAPIKey; + public int MaxLibraryScanWorkers = _MaxLibraryScanWorkers; public string HasheousHost = _HasheousHost; diff --git a/gaseous-server/Classes/DatabaseMigration.cs b/gaseous-server/Classes/DatabaseMigration.cs index c9e65de..b701076 100644 --- a/gaseous-server/Classes/DatabaseMigration.cs +++ b/gaseous-server/Classes/DatabaseMigration.cs @@ -1,14 +1,17 @@ using System; using System.Data; using System.Reflection; +using gaseous_server.Classes.Metadata; +using gaseous_server.Models; +using IGDB.Models; namespace gaseous_server.Classes { - public static class DatabaseMigration - { + public static class DatabaseMigration + { public static List BackgroundUpgradeTargetSchemaVersions = new List(); - public static void PreUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType) + public static void PreUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType) { // load resources var assembly = Assembly.GetExecutingAssembly(); @@ -20,14 +23,14 @@ namespace gaseous_server.Classes Logging.Log(Logging.LogType.Information, "Database", "Checking for pre-upgrade for schema version " + TargetSchemaVersion); - switch(DatabaseType) + switch (DatabaseType) { case Database.databaseType.MySql: switch (TargetSchemaVersion) { case 1005: Logging.Log(Logging.LogType.Information, "Database", "Running pre-upgrade for schema version " + TargetSchemaVersion); - + // there was a mistake at dbschema version 1004-1005 // the first preview release of v1.7 reused dbschema version 1004 // if table "Relation_Game_AgeRatings" exists - then we need to apply the gaseous-fix-1005.sql script before applying the standard 1005 script @@ -62,14 +65,16 @@ namespace gaseous_server.Classes } } - public static void PostUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType) + public static void PostUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType) { + var assembly = Assembly.GetExecutingAssembly(); + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); string sql = ""; Dictionary dbDict = new Dictionary(); DataTable data; - switch(DatabaseType) + switch (DatabaseType) { case Database.databaseType.MySql: switch (TargetSchemaVersion) @@ -78,7 +83,7 @@ namespace gaseous_server.Classes // this is a safe background task BackgroundUpgradeTargetSchemaVersions.Add(1002); break; - + case 1004: // needs to run on start up @@ -103,6 +108,51 @@ namespace gaseous_server.Classes sql = "DELETE FROM Settings WHERE Setting LIKE 'LastRun_%';"; db.ExecuteNonQuery(sql); break; + + case 1022: + // load country list + Logging.Log(Logging.LogType.Information, "Database Upgrade", "Adding country look up table contents"); + + string countryResourceName = "gaseous_server.Support.Country.txt"; + using (Stream stream = assembly.GetManifestResourceStream(countryResourceName)) + using (StreamReader reader = new StreamReader(stream)) + { + do + { + string[] line = reader.ReadLine().Split("|"); + + sql = "INSERT INTO Country (Code, Value) VALUES (@code, @value);"; + dbDict = new Dictionary{ + { "code", line[0] }, + { "value", line[1] } + }; + db.ExecuteNonQuery(sql, dbDict); + } while (reader.EndOfStream == false); + } + + // load language list + Logging.Log(Logging.LogType.Information, "Database Upgrade", "Adding language look up table contents"); + + string languageResourceName = "gaseous_server.Support.Language.txt"; + using (Stream stream = assembly.GetManifestResourceStream(languageResourceName)) + using (StreamReader reader = new StreamReader(stream)) + { + do + { + string[] line = reader.ReadLine().Split("|"); + + sql = "INSERT INTO Language (Code, Value) VALUES (@code, @value);"; + dbDict = new Dictionary{ + { "code", line[0] }, + { "value", line[1] } + }; + db.ExecuteNonQuery(sql, dbDict); + } while (reader.EndOfStream == false); + } + + // this is a safe background task + BackgroundUpgradeTargetSchemaVersions.Add(1022); + break; } break; } @@ -117,11 +167,16 @@ namespace gaseous_server.Classes case 1002: MySql_1002_MigrateMetadataVersion(); break; + + case 1022: + MySql_1022_MigrateMetadataVersion(); + break; } } } - public static void MySql_1002_MigrateMetadataVersion() { + public static void MySql_1002_MigrateMetadataVersion() + { Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); string sql = ""; Dictionary dbDict = new Dictionary(); @@ -134,7 +189,7 @@ namespace gaseous_server.Classes Logging.Log(Logging.LogType.Information, "Signature Ingestor - Database Update", "Updating " + data.Rows.Count + " database entries"); int Counter = 0; int LastCounterCheck = 0; - foreach (DataRow row in data.Rows) + foreach (DataRow row in data.Rows) { List Flags = Newtonsoft.Json.JsonConvert.DeserializeObject>((string)Common.ReturnValueIfNull(row["flags"], "[]")); List> Attributes = new List>(); @@ -207,7 +262,7 @@ namespace gaseous_server.Classes dbDict.Add("id", (int)row["Id"]); db.ExecuteCMD(updateSQL, dbDict); - if ((Counter - LastCounterCheck) > 10) + if ((Counter - LastCounterCheck) > 10) { LastCounterCheck = Counter; Logging.Log(Logging.LogType.Information, "Signature Ingestor - Database Update", "Updating " + Counter + " / " + data.Rows.Count + " database entries"); @@ -216,5 +271,36 @@ namespace gaseous_server.Classes } } } + + public static void MySql_1022_MigrateMetadataVersion() + { + FileSignature fileSignature = new FileSignature(); + + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT * FROM Games_Roms WHERE RomDataVersion = 1;"; + DataTable data = db.ExecuteCMD(sql); + foreach (DataRow row in data.Rows) + { + Logging.Log(Logging.LogType.Information, "Database Migration", "Updating ROM table for ROM: " + (string)row["Name"]); + + GameLibrary.LibraryItem library = GameLibrary.GetLibrary((int)row["LibraryId"]); + Common.hashObject hash = new Common.hashObject() + { + md5hash = (string)row["MD5"], + sha1hash = (string)row["SHA1"] + }; + Signatures_Games signature = fileSignature.GetFileSignature( + library, + hash, + new FileInfo((string)row["Path"]), + (string)row["Path"] + ); + + Platform platform = Platforms.GetPlatform((long)row["PlatformId"], false); + Game game = Games.GetGame((long)row["GameId"], false, false, false); + + ImportGame.StoreROM(library, hash, game, platform, signature, (string)row["Path"], (long)row["Id"]); + } + } } } \ No newline at end of file diff --git a/gaseous-server/Classes/FileSignature.cs b/gaseous-server/Classes/FileSignature.cs index 2c78fc7..3df2ded 100644 --- a/gaseous-server/Classes/FileSignature.cs +++ b/gaseous-server/Classes/FileSignature.cs @@ -1,6 +1,8 @@ using System.Collections.Concurrent; using System.IO.Compression; +using gaseous_server.Classes.Metadata; using HasheousClient.Models; +using Microsoft.CodeAnalysis.CSharp.Syntax; using NuGet.Common; using SevenZip; using SharpCompress.Archives; @@ -31,7 +33,7 @@ namespace gaseous_server.Classes if (!Directory.Exists(ExtractPath)) { Directory.CreateDirectory(ExtractPath); } try { - switch(ImportedFileExtension) + switch (ImportedFileExtension) { case ".zip": Logging.Log(Logging.LogType.Information, "Get Signature", "Decompressing using zip"); @@ -105,31 +107,24 @@ namespace gaseous_server.Classes } break; } - + Logging.Log(Logging.LogType.Information, "Get Signature", "Processing decompressed files for signature matches"); // loop through contents until we find the first signature match List archiveFiles = new List(); bool signatureFound = false; + bool signatureSelectorAlreadyApplied = false; foreach (string file in Directory.GetFiles(ExtractPath, "*.*", SearchOption.AllDirectories)) { + bool signatureSelector = false; if (File.Exists(file)) { FileInfo zfi = new FileInfo(file); Common.hashObject zhash = new Common.hashObject(file); - + Logging.Log(Logging.LogType.Information, "Get Signature", "Checking signature of decompressed file " + file); if (zfi != null) { - ArchiveData archiveData = new ArchiveData{ - FileName = Path.GetFileName(file), - FilePath = zfi.Directory.FullName.Replace(ExtractPath, ""), - Size = zfi.Length, - MD5 = hash.md5hash, - SHA1 = hash.sha1hash - }; - archiveFiles.Add(archiveData); - if (signatureFound == false) { gaseous_server.Models.Signatures_Games zDiscoveredSignature = _GetFileSignature(zhash, zfi.Name, zfi.Extension, zfi.Length, file, true); @@ -138,7 +133,7 @@ namespace gaseous_server.Classes if (zDiscoveredSignature.Score > discoveredSignature.Score) { if ( - zDiscoveredSignature.Rom.SignatureSource == gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType.MAMEArcade || + zDiscoveredSignature.Rom.SignatureSource == gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType.MAMEArcade || zDiscoveredSignature.Rom.SignatureSource == gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType.MAMEMess ) { @@ -151,15 +146,37 @@ namespace gaseous_server.Classes discoveredSignature = zDiscoveredSignature; signatureFound = true; + + if (signatureSelectorAlreadyApplied == false) + { + signatureSelector = true; + signatureSelectorAlreadyApplied = true; + } } } + + ArchiveData archiveData = new ArchiveData + { + FileName = Path.GetFileName(file), + FilePath = zfi.Directory.FullName.Replace(ExtractPath, ""), + Size = zfi.Length, + MD5 = zhash.md5hash, + SHA1 = zhash.sha1hash, + isSignatureSelector = signatureSelector + }; + archiveFiles.Add(archiveData); } } } - discoveredSignature.Rom.Attributes.Add(new KeyValuePair( + if (discoveredSignature.Rom.Attributes == null) + { + discoveredSignature.Rom.Attributes = new Dictionary(); + } + + discoveredSignature.Rom.Attributes.Add( "ZipContents", Newtonsoft.Json.JsonConvert.SerializeObject(archiveFiles) - )); + ); } catch (Exception ex) { @@ -195,7 +212,7 @@ namespace gaseous_server.Classes { // signature retrieved from Hasheous Logging.Log(Logging.LogType.Information, "Import Game", "Signature retrieved from Hasheous for game: " + dbSignature.Game.Name); - + discoveredSignature = dbSignature; } else @@ -203,7 +220,7 @@ namespace gaseous_server.Classes // construct a signature from file data dbSignature = _GetFileSignatureFromFileData(hash, ImageName, ImageExtension, ImageSize, GameFileImportPath); Logging.Log(Logging.LogType.Information, "Import Game", "Signature generated from provided file for game: " + dbSignature.Game.Name); - + discoveredSignature = dbSignature; } } @@ -216,8 +233,8 @@ namespace gaseous_server.Classes return discoveredSignature; } - private gaseous_server.Models.Signatures_Games? _GetFileSignatureFromDatabase(Common.hashObject hash, string ImageName, string ImageExtension, long ImageSize, string GameFileImportPath) - { + private gaseous_server.Models.Signatures_Games? _GetFileSignatureFromDatabase(Common.hashObject hash, string ImageName, string ImageExtension, long ImageSize, string GameFileImportPath) + { Logging.Log(Logging.LogType.Information, "Get Signature", "Checking local database for MD5: " + hash.md5hash); // check 1: do we have a signature for it? @@ -378,6 +395,7 @@ namespace gaseous_server.Classes public long Size { get; set; } public string MD5 { get; set; } public string SHA1 { get; set; } + public bool isSignatureSelector { get; set; } = false; } } } \ No newline at end of file diff --git a/gaseous-server/Classes/ImportGames.cs b/gaseous-server/Classes/ImportGames.cs index d8d9701..e4a1267 100644 --- a/gaseous-server/Classes/ImportGames.cs +++ b/gaseous-server/Classes/ImportGames.cs @@ -16,48 +16,49 @@ using HasheousClient.Models; namespace gaseous_server.Classes { - public class ImportGame : QueueItemStatus - { + public class ImportGame : QueueItemStatus + { public void ProcessDirectory(string ImportPath) { if (Directory.Exists(ImportPath)) - { - string[] importContents = Directory.GetFiles(ImportPath, "*.*", SearchOption.AllDirectories); + { + string[] importContents = Directory.GetFiles(ImportPath, "*.*", SearchOption.AllDirectories); Logging.Log(Logging.LogType.Information, "Import Games", "Found " + importContents.Length + " files to process in import directory: " + ImportPath); - // import files first + // import files first int importCount = 1; - foreach (string importContent in importContents) { + foreach (string importContent in importContents) + { SetStatus(importCount, importContents.Length, "Importing file: " + importContent); - ImportGameFile(importContent, null); + ImportGameFile(importContent, null); importCount += 1; - } + } ClearStatus(); DeleteOrphanedDirectories(ImportPath); } - else - { - Logging.Log(Logging.LogType.Critical, "Import Games", "The import directory " + ImportPath + " does not exist."); - throw new DirectoryNotFoundException("Invalid path: " + ImportPath); - } + else + { + Logging.Log(Logging.LogType.Critical, "Import Games", "The import directory " + ImportPath + " does not exist."); + throw new DirectoryNotFoundException("Invalid path: " + ImportPath); + } } public void ImportGameFile(string GameFileImportPath, IGDB.Models.Platform? OverridePlatform) - { + { Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = ""; - Dictionary dbDict = new Dictionary(); + string sql = ""; + Dictionary dbDict = new Dictionary(); if (Common.SkippableFiles.Contains(Path.GetFileName(GameFileImportPath), StringComparer.OrdinalIgnoreCase)) - { + { Logging.Log(Logging.LogType.Debug, "Import Game", "Skipping item " + GameFileImportPath); } - else - { + else + { FileInfo fi = new FileInfo(GameFileImportPath); Common.hashObject hash = new Common.hashObject(GameFileImportPath); @@ -87,7 +88,7 @@ namespace gaseous_server.Classes } File.Move(GameFileImportPath, targetPathWithFileName, true); } - + // import source was the upload directory if (GameFileImportPath.StartsWith(Config.LibraryConfiguration.LibraryUploadDirectory)) { @@ -146,8 +147,8 @@ namespace gaseous_server.Classes } } } - } - } + } + } public static IGDB.Models.Game SearchForGame(gaseous_server.Models.Signatures_Games Signature, long PlatformId, bool FullDownload) { @@ -173,7 +174,7 @@ namespace gaseous_server.Classes string GameName = Signature.Game.Name; List SearchCandidates = GetSearchCandidates(GameName); - + foreach (string SearchCandidate in SearchCandidates) { bool GameFound = false; @@ -197,8 +198,10 @@ namespace gaseous_server.Classes Logging.Log(Logging.LogType.Information, "Import Game", " " + games.Length + " search results found"); // quite likely we've found sequels and alternate types - foreach (Game game in games) { - if (game.Name == SearchCandidate) { + foreach (Game game in games) + { + if (game.Name == SearchCandidate) + { // found game title matches the search candidate determinedGame = Metadata.Games.GetGame((long)games[0].Id, false, false, false); Logging.Log(Logging.LogType.Information, "Import Game", "Found exact match!"); @@ -273,7 +276,8 @@ namespace gaseous_server.Classes // assumption: no games have () in their titles so we'll remove them int idx = GameName.IndexOf('('); - if (idx >= 0) { + if (idx >= 0) + { GameName = GameName.Substring(0, idx); } @@ -304,10 +308,11 @@ namespace gaseous_server.Classes if (UpdateId == 0) { - sql = "INSERT INTO Games_Roms (PlatformId, GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Attributes, RomType, RomTypeMedia, MediaLabel, Path, MetadataSource, MetadataGameName, MetadataVersion, LibraryId) VALUES (@platformid, @gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @Attributes, @romtype, @romtypemedia, @medialabel, @path, @metadatasource, @metadatagamename, @metadataversion, @libraryid); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; - } else + sql = "INSERT INTO Games_Roms (PlatformId, GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Attributes, RomType, RomTypeMedia, MediaLabel, Path, MetadataSource, MetadataGameName, MetadataVersion, LibraryId, RomDataVersion) VALUES (@platformid, @gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @Attributes, @romtype, @romtypemedia, @medialabel, @path, @metadatasource, @metadatagamename, @metadataversion, @libraryid, @romdataversion); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + } + else { - sql = "UPDATE Games_Roms SET PlatformId=@platformid, GameId=@gameid, Name=@name, Size=@size, DevelopmentStatus=@developmentstatus, Attributes=@Attributes, RomType=@romtype, RomTypeMedia=@romtypemedia, MediaLabel=@medialabel, MetadataSource=@metadatasource, MetadataGameName=@metadatagamename, MetadataVersion=@metadataversion WHERE Id=@id;"; + sql = "UPDATE Games_Roms SET PlatformId=@platformid, GameId=@gameid, Name=@name, Size=@size, DevelopmentStatus=@developmentstatus, Attributes=@Attributes, RomType=@romtype, RomTypeMedia=@romtypemedia, MediaLabel=@medialabel, MetadataSource=@metadatasource, MetadataGameName=@metadatagamename, MetadataVersion=@metadataversion, RomDataVersion=@romdataversion WHERE Id=@id;"; dbDict.Add("id", UpdateId); } dbDict.Add("platformid", Common.ReturnValueIfNull(determinedPlatform.Id, 0)); @@ -322,6 +327,7 @@ namespace gaseous_server.Classes dbDict.Add("metadatagamename", discoveredSignature.Game.Name); dbDict.Add("metadataversion", 2); dbDict.Add("libraryid", library.Id); + dbDict.Add("romdataversion", 2); if (discoveredSignature.Rom.Attributes != null) { @@ -348,7 +354,8 @@ namespace gaseous_server.Classes if (UpdateId == 0) { romId = (long)romInsert.Rows[0][0]; - } else + } + else { romId = UpdateId; } @@ -362,73 +369,73 @@ namespace gaseous_server.Classes return romId; } - public static string ComputeROMPath(long RomId) - { - Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId); - - // get metadata - IGDB.Models.Platform platform = gaseous_server.Classes.Metadata.Platforms.GetPlatform(rom.PlatformId); - IGDB.Models.Game game = gaseous_server.Classes.Metadata.Games.GetGame(rom.GameId, false, false, false); - - // build path - string platformSlug = "Unknown Platform"; - if (platform != null) - { - platformSlug = platform.Slug; - } - string gameSlug = "Unknown Title"; - if (game != null) - { - gameSlug = game.Slug; - } - string DestinationPath = Path.Combine(GameLibrary.GetDefaultLibrary.Path, gameSlug, platformSlug); - if (!Directory.Exists(DestinationPath)) - { - Directory.CreateDirectory(DestinationPath); - } - - string DestinationPathName = Path.Combine(DestinationPath, rom.Name); - - return DestinationPathName; - } - - public static bool MoveGameFile(long RomId) - { + public static string ComputeROMPath(long RomId) + { Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId); - string romPath = rom.Path; + + // get metadata + IGDB.Models.Platform platform = gaseous_server.Classes.Metadata.Platforms.GetPlatform(rom.PlatformId); + IGDB.Models.Game game = gaseous_server.Classes.Metadata.Games.GetGame(rom.GameId, false, false, false); + + // build path + string platformSlug = "Unknown Platform"; + if (platform != null) + { + platformSlug = platform.Slug; + } + string gameSlug = "Unknown Title"; + if (game != null) + { + gameSlug = game.Slug; + } + string DestinationPath = Path.Combine(GameLibrary.GetDefaultLibrary.Path, gameSlug, platformSlug); + if (!Directory.Exists(DestinationPath)) + { + Directory.CreateDirectory(DestinationPath); + } + + string DestinationPathName = Path.Combine(DestinationPath, rom.Name); + + return DestinationPathName; + } + + public static bool MoveGameFile(long RomId) + { + Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId); + string romPath = rom.Path; if (File.Exists(romPath)) { string DestinationPath = ComputeROMPath(RomId); - if (romPath == DestinationPath) - { - Logging.Log(Logging.LogType.Debug, "Move Game ROM", "Destination path is the same as the current path - aborting"); + if (romPath == DestinationPath) + { + Logging.Log(Logging.LogType.Debug, "Move Game ROM", "Destination path is the same as the current path - aborting"); return true; - } - else - { - Logging.Log(Logging.LogType.Information, "Move Game ROM", "Moving " + romPath + " to " + DestinationPath); - if (File.Exists(DestinationPath)) - { - Logging.Log(Logging.LogType.Information, "Move Game ROM", "A file with the same name exists at the destination - aborting"); + } + else + { + Logging.Log(Logging.LogType.Information, "Move Game ROM", "Moving " + romPath + " to " + DestinationPath); + if (File.Exists(DestinationPath)) + { + Logging.Log(Logging.LogType.Information, "Move Game ROM", "A file with the same name exists at the destination - aborting"); return false; - } - else - { - File.Move(romPath, DestinationPath); + } + else + { + File.Move(romPath, DestinationPath); - // update the db - Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "UPDATE Games_Roms SET Path=@path WHERE Id=@id"; - Dictionary dbDict = new Dictionary(); - dbDict.Add("id", RomId); - dbDict.Add("path", DestinationPath); - db.ExecuteCMD(sql, dbDict); + // update the db + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "UPDATE Games_Roms SET Path=@path WHERE Id=@id"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", RomId); + dbDict.Add("path", DestinationPath); + db.ExecuteCMD(sql, dbDict); return true; - } - } + } + } } else { @@ -437,8 +444,8 @@ namespace gaseous_server.Classes } } - public void OrganiseLibrary() - { + public void OrganiseLibrary() + { Logging.Log(Logging.LogType.Information, "Organise Library", "Starting default library organisation"); GameLibrary.LibraryItem library = GameLibrary.GetDefaultLibrary; @@ -451,19 +458,19 @@ namespace gaseous_server.Classes DataTable romDT = db.ExecuteCMD(sql, dbDict); if (romDT.Rows.Count > 0) - { - for (int i = 0; i < romDT.Rows.Count; i++) - { + { + for (int i = 0; i < romDT.Rows.Count; i++) + { SetStatus(i, romDT.Rows.Count, "Processing file " + romDT.Rows[i]["name"]); - Logging.Log(Logging.LogType.Information, "Organise Library", "(" + i + "/" + romDT.Rows.Count + ") Processing ROM " + romDT.Rows[i]["name"]); + Logging.Log(Logging.LogType.Information, "Organise Library", "(" + i + "/" + romDT.Rows.Count + ") Processing ROM " + romDT.Rows[i]["name"]); long RomId = (long)romDT.Rows[i]["id"]; - MoveGameFile(RomId); - } - } + MoveGameFile(RomId); + } + } ClearStatus(); - // clean up empty directories - DeleteOrphanedDirectories(GameLibrary.GetDefaultLibrary.Path); + // clean up empty directories + DeleteOrphanedDirectories(GameLibrary.GetDefaultLibrary.Path); Logging.Log(Logging.LogType.Information, "Organise Library", "Finsihed default library organisation"); } @@ -476,7 +483,7 @@ namespace gaseous_server.Classes string[] files = Directory.GetFiles(directory); string[] directories = Directory.GetDirectories(directory); - + if (files.Length == 0 && directories.Length == 0) { @@ -563,7 +570,7 @@ namespace gaseous_server.Classes public void LibrarySpecificScan(GameLibrary.LibraryItem library) { - + Logging.Log(Logging.LogType.Information, "Library Scan", "Starting scan of library: " + library.Name); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); @@ -632,7 +639,7 @@ namespace gaseous_server.Classes { // file is not in database - process it Logging.Log(Logging.LogType.Information, "Library Scan", "Orphaned file found in library: " + LibraryFile); - + Common.hashObject hash = new Common.hashObject(LibraryFile); FileInfo fi = new FileInfo(LibraryFile); @@ -644,8 +651,8 @@ namespace gaseous_server.Classes // get discovered platform long PlatformId; IGDB.Models.Platform determinedPlatform; - - if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0 ) + + if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0) { // no platform discovered in the signature PlatformId = library.DefaultPlatformId; @@ -770,8 +777,8 @@ namespace gaseous_server.Classes // get discovered platform long PlatformId; IGDB.Models.Platform determinedPlatform; - - if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0 ) + + if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0) { // no platform discovered in the signature PlatformId = library.DefaultPlatformId; diff --git a/gaseous-server/Classes/Metadata/Games.cs b/gaseous-server/Classes/Metadata/Games.cs index 7e1fbc4..329c3a2 100644 --- a/gaseous-server/Classes/Metadata/Games.cs +++ b/gaseous-server/Classes/Metadata/Games.cs @@ -78,15 +78,17 @@ namespace gaseous_server.Classes.Metadata if (cacheStatus == Storage.CacheStatus.Current) { cacheStatus = Storage.CacheStatus.Expired; } } - // set up where clause string WhereClause = ""; + string searchField = ""; switch (searchUsing) { case SearchUsing.id: WhereClause = "where id = " + searchValue; + searchField = "id"; break; case SearchUsing.slug: - WhereClause = "where slug = " + searchValue; + WhereClause = "where slug = \"" + searchValue + "\""; + searchField = "slug"; break; default: throw new Exception("Invalid search type"); @@ -110,11 +112,11 @@ namespace gaseous_server.Classes.Metadata catch (Exception ex) { Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex); - returnValue = Storage.GetCacheValue(returnValue, "id", (long)searchValue); + returnValue = Storage.GetCacheValue(returnValue, searchField, searchValue); } return returnValue; case Storage.CacheStatus.Current: - returnValue = Storage.GetCacheValue(returnValue, "id", (long)searchValue); + returnValue = Storage.GetCacheValue(returnValue, searchField, searchValue); UpdateSubClasses(returnValue, false, false, false); return returnValue; default: diff --git a/gaseous-server/Classes/Metadata/Platforms.cs b/gaseous-server/Classes/Metadata/Platforms.cs index 5bab6b6..e02ffda 100644 --- a/gaseous-server/Classes/Metadata/Platforms.cs +++ b/gaseous-server/Classes/Metadata/Platforms.cs @@ -78,13 +78,16 @@ namespace gaseous_server.Classes.Metadata // set up where clause string WhereClause = ""; + string searchField = ""; switch (searchUsing) { case SearchUsing.id: WhereClause = "where id = " + searchValue; + searchField = "id"; break; case SearchUsing.slug: - WhereClause = "where slug = " + searchValue; + WhereClause = "where slug = \"" + searchValue + "\""; + searchField = "slug"; break; default: throw new Exception("Invalid search type"); @@ -111,10 +114,10 @@ namespace gaseous_server.Classes.Metadata catch (Exception ex) { Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex); - return Storage.GetCacheValue(returnValue, "id", (long)searchValue); + return Storage.GetCacheValue(returnValue, searchField, searchValue); } case Storage.CacheStatus.Current: - return Storage.GetCacheValue(returnValue, "id", (long)searchValue); + return Storage.GetCacheValue(returnValue, searchField, searchValue); default: throw new Exception("How did you get here?"); } diff --git a/gaseous-server/Classes/Roms.cs b/gaseous-server/Classes/Roms.cs index 22aceec..77b54ca 100644 --- a/gaseous-server/Classes/Roms.cs +++ b/gaseous-server/Classes/Roms.cs @@ -4,6 +4,9 @@ using gaseous_signature_parser.models.RomSignatureObject; using static gaseous_server.Classes.RomMediaGroup; using gaseous_server.Classes.Metadata; using IGDB.Models; +using static HasheousClient.Models.FixMatchModel; +using NuGet.Protocol.Core.Types; +using static gaseous_server.Classes.FileSignature; namespace gaseous_server.Classes { @@ -147,6 +150,53 @@ namespace gaseous_server.Classes GameRomItem rom = GetRom(RomId); + // send update to Hasheous if enabled + if (PlatformId != 0 && GameId != 0) + { + if (Config.MetadataConfiguration.HasheousSubmitFixes == true) + { + if ( + Config.MetadataConfiguration.SignatureSource == HasheousClient.Models.MetadataModel.SignatureSources.Hasheous && + ( + Config.MetadataConfiguration.HasheousAPIKey != null && + Config.MetadataConfiguration.HasheousAPIKey != "") + ) + { + try + { + // find signature used for identifing the rom + string md5String = rom.Md5; + string sha1String = rom.Sha1; + if (rom.Attributes.ContainsKey("ZipContents")) + { + bool selectorFound = false; + List archiveDataValues = Newtonsoft.Json.JsonConvert.DeserializeObject>(rom.Attributes["ZipContents"].ToString()); + foreach (ArchiveData archiveData in archiveDataValues) + { + if (archiveData.isSignatureSelector == true) + { + md5String = archiveData.MD5; + sha1String = archiveData.SHA1; + selectorFound = true; + break; + } + } + } + + HasheousClient.WebApp.HttpHelper.AddHeader("X-API-Key", Config.MetadataConfiguration.HasheousAPIKey); + HasheousClient.Hasheous hasheousClient = new HasheousClient.Hasheous(); + List metadataMatchList = new List(); + metadataMatchList.Add(new MetadataMatch(HasheousClient.Models.MetadataSources.IGDB, platform.Slug, game.Slug)); + hasheousClient.FixMatch(new HasheousClient.Models.FixMatchModel(md5String, sha1String, metadataMatchList)); + } + catch (Exception ex) + { + Logging.Log(Logging.LogType.Critical, "Fix Match", "An error occurred while sending a fixed match to Hasheous.", ex); + } + } + } + } + return rom; } @@ -179,6 +229,22 @@ namespace gaseous_server.Classes } } + Dictionary romAttributes = new Dictionary(); + if (romDR["attributes"] != DBNull.Value) + { + try + { + if ((string)romDR["attributes"] != "[ ]") + { + romAttributes = Newtonsoft.Json.JsonConvert.DeserializeObject>((string)romDR["attributes"]); + } + } + catch (Exception ex) + { + Logging.Log(Logging.LogType.Warning, "Roms", "Error parsing rom attributes: " + ex.Message); + } + } + GameRomItem romItem = new GameRomItem { Id = (long)romDR["id"], @@ -223,7 +289,7 @@ namespace gaseous_server.Classes public int Count { get; set; } } - public class GameRomItem : HasheousClient.Models.LookupResponseModel.RomItem + public class GameRomItem : HasheousClient.Models.SignatureModel.RomItem { public long PlatformId { get; set; } public string Platform { get; set; } diff --git a/gaseous-server/Classes/SignatureIngestors/XML.cs b/gaseous-server/Classes/SignatureIngestors/XML.cs index eda4fdc..ff5ee6f 100644 --- a/gaseous-server/Classes/SignatureIngestors/XML.cs +++ b/gaseous-server/Classes/SignatureIngestors/XML.cs @@ -8,21 +8,49 @@ namespace gaseous_server.SignatureIngestors.XML { public class XMLIngestor : QueueItemStatus { - public void Import(string SearchPath, gaseous_signature_parser.parser.SignatureParser XMLType) + public void Import(string SearchPath, string ProcessedDirectory, gaseous_signature_parser.parser.SignatureParser XMLType) { // connect to database Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string? XMLDBSearchPath = null; + string? XMLDBProcessedDirectory = null; + if (XMLType == gaseous_signature_parser.parser.SignatureParser.NoIntro) + { + XMLDBSearchPath = Path.Combine(SearchPath, "DB"); + XMLDBProcessedDirectory = Path.Combine(ProcessedDirectory, "DB"); + SearchPath = Path.Combine(SearchPath, "DAT"); + ProcessedDirectory = Path.Combine(ProcessedDirectory, "DAT"); + } + // process provided files - Logging.Log(Logging.LogType.Information, "Signature Ingestor - XML", "Importing from " + SearchPath); if (!Directory.Exists(SearchPath)) { Directory.CreateDirectory(SearchPath); } + if (!Directory.Exists(ProcessedDirectory)) + { + Directory.CreateDirectory(ProcessedDirectory); + } string[] PathContents = Directory.GetFiles(SearchPath); Array.Sort(PathContents); + string[]? DBPathContents = null; + if (XMLDBSearchPath != null) + { + if (!Directory.Exists(XMLDBSearchPath)) + { + Directory.CreateDirectory(XMLDBSearchPath); + } + if (!Directory.Exists(XMLDBProcessedDirectory)) + { + Directory.CreateDirectory(XMLDBProcessedDirectory); + } + + DBPathContents = Directory.GetFiles(XMLDBSearchPath); + } + string sql = ""; Dictionary dbDict = new Dictionary(); System.Data.DataTable sigDB; @@ -33,226 +61,380 @@ namespace gaseous_server.SignatureIngestors.XML SetStatus(i + 1, PathContents.Length, "Processing signature file: " + XMLFile); - if (Common.SkippableFiles.Contains(Path.GetFileName(XMLFile), StringComparer.OrdinalIgnoreCase)) - { - Logging.Log(Logging.LogType.Information, "Signature Ingestor - XML", "Skipping file: " + XMLFile); - } - else - { - // check xml file md5 - Common.hashObject hashObject = new Common.hashObject(XMLFile); - sql = "SELECT * FROM Signatures_Sources WHERE SourceMD5=@sourcemd5"; - dbDict = new Dictionary(); - dbDict.Add("sourcemd5", hashObject.md5hash); - sigDB = db.ExecuteCMD(sql, dbDict); + Logging.Log(Logging.LogType.Information, "Signature Ingest", "(" + (i + 1) + " / " + PathContents.Length + ") Processing " + XMLType.ToString() + " DAT file: " + XMLFile); - if (sigDB.Rows.Count == 0) + string? DBFile = null; + if (XMLDBSearchPath != null) + { + switch (XMLType) { - try - { - Logging.Log(Logging.LogType.Information, "Signature Ingestor - XML", "Importing file: " + XMLFile); - - // start parsing file - gaseous_signature_parser.parser Parser = new gaseous_signature_parser.parser(); - RomSignatureObject Object = Parser.ParseSignatureDAT(XMLFile, XMLType); - - // store in database - string[] flipNameAndDescription = { - "MAMEArcade", - "MAMEMess" - }; - - // store source object - bool processGames = false; - if (Object.SourceMd5 != null) + case gaseous_signature_parser.parser.SignatureParser.NoIntro: + for (UInt16 x = 0; x < DBPathContents.Length; x++) { - sql = "SELECT * FROM Signatures_Sources WHERE SourceMD5=@sourcemd5"; - dbDict = new Dictionary(); - string sourceUriStr = ""; - if (Object.Url != null) + string tempDBFileName = Path.GetFileNameWithoutExtension(DBPathContents[x].Replace(" (DB Export)", "")); + if (tempDBFileName == Path.GetFileNameWithoutExtension(XMLFile)) { - sourceUriStr = Object.Url.ToString(); + DBFile = DBPathContents[x]; + Logging.Log(Logging.LogType.Information, "Signature Ingest", "Using DB file: " + DBFile); + break; } - dbDict.Add("name", Common.ReturnValueIfNull(Object.Name, "")); - dbDict.Add("description", Common.ReturnValueIfNull(Object.Description, "")); - dbDict.Add("category", Common.ReturnValueIfNull(Object.Category, "")); - dbDict.Add("version", Common.ReturnValueIfNull(Object.Version, "")); - dbDict.Add("author", Common.ReturnValueIfNull(Object.Author, "")); - dbDict.Add("email", Common.ReturnValueIfNull(Object.Email, "")); - dbDict.Add("homepage", Common.ReturnValueIfNull(Object.Homepage, "")); - dbDict.Add("uri", sourceUriStr); - dbDict.Add("sourcetype", Common.ReturnValueIfNull(Object.SourceType, "")); - dbDict.Add("sourcemd5", Object.SourceMd5); - dbDict.Add("sourcesha1", Object.SourceSHA1); + } + break; + } + } + + // check xml file md5 + Common.hashObject hashObject = new Common.hashObject(XMLFile); + 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 + { + // start parsing file + gaseous_signature_parser.parser Parser = new gaseous_signature_parser.parser(); + RomSignatureObject Object = Parser.ParseSignatureDAT(XMLFile, DBFile, XMLType); + + // store in database + string[] flipNameAndDescription = { + "MAMEArcade", + "MAMEMess" + }; + + // store source object + bool processGames = false; + if (Object.SourceMd5 != null) + { + int sourceId = 0; + + sql = "SELECT * FROM Signatures_Sources WHERE `SourceMD5`=@sourcemd5"; + dbDict = new Dictionary + { + { "name", Common.ReturnValueIfNull(Object.Name, "") }, + { "description", Common.ReturnValueIfNull(Object.Description, "") }, + { "category", Common.ReturnValueIfNull(Object.Category, "") }, + { "version", Common.ReturnValueIfNull(Object.Version, "") }, + { "author", Common.ReturnValueIfNull(Object.Author, "") }, + { "email", Common.ReturnValueIfNull(Object.Email, "") }, + { "homepage", Common.ReturnValueIfNull(Object.Homepage, "") } + }; + if (Object.Url == null) + { + dbDict.Add("uri", ""); + } + else + { + dbDict.Add("uri", Common.ReturnValueIfNull(Object.Url.ToString(), "")); + } + dbDict.Add("sourcetype", Common.ReturnValueIfNull(Object.SourceType, "")); + dbDict.Add("sourcemd5", Object.SourceMd5); + dbDict.Add("sourcesha1", Object.SourceSHA1); + + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + // entry not present, insert it + sql = "INSERT INTO Signatures_Sources (`Name`, `Description`, `Category`, `Version`, `Author`, `Email`, `Homepage`, `Url`, `SourceType`, `SourceMD5`, `SourceSHA1`) VALUES (@name, @description, @category, @version, @author, @email, @homepage, @uri, @sourcetype, @sourcemd5, @sourcesha1); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; sigDB = db.ExecuteCMD(sql, dbDict); - if (sigDB.Rows.Count == 0) + + sourceId = Convert.ToInt32(sigDB.Rows[0][0]); + + processGames = true; + } + + if (processGames == true) + { + for (int x = 0; x < Object.Games.Count; ++x) { - // entry not present, insert it - 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)"; + RomSignatureObject.Game gameObject = Object.Games[x]; - db.ExecuteCMD(sql, dbDict); - - processGames = true; - } - - if (processGames == true) - { - for (int x = 0; x < Object.Games.Count; ++x) + // set up game dictionary + dbDict = new Dictionary(); + if (flipNameAndDescription.Contains(Object.SourceType)) { - RomSignatureObject.Game gameObject = Object.Games[x]; + dbDict.Add("name", Common.ReturnValueIfNull(gameObject.Description, "")); + dbDict.Add("description", Common.ReturnValueIfNull(gameObject.Name, "")); + } + else + { + dbDict.Add("name", Common.ReturnValueIfNull(gameObject.Name, "")); + dbDict.Add("description", Common.ReturnValueIfNull(gameObject.Description, "")); + } + dbDict.Add("year", Common.ReturnValueIfNull(gameObject.Year, "")); + dbDict.Add("publisher", Common.ReturnValueIfNull(gameObject.Publisher, "")); + dbDict.Add("demo", (int)gameObject.Demo); + dbDict.Add("system", Common.ReturnValueIfNull(gameObject.System, "")); + dbDict.Add("platform", Common.ReturnValueIfNull(gameObject.System, "")); + dbDict.Add("systemvariant", Common.ReturnValueIfNull(gameObject.SystemVariant, "")); + dbDict.Add("video", Common.ReturnValueIfNull(gameObject.Video, "")); - // set up game dictionary - dbDict = new Dictionary(); - if (flipNameAndDescription.Contains(Object.SourceType)) + List gameCountries = new List(); + if ( + gameObject.Country != null && + gameObject.Country != "Unknown" + ) + { + string[] countries = gameObject.Country.Split(","); + foreach (string country in countries) { - dbDict.Add("name", Common.ReturnValueIfNull(gameObject.Description, "")); - dbDict.Add("description", Common.ReturnValueIfNull(gameObject.Name, "")); - } - else - { - dbDict.Add("name", Common.ReturnValueIfNull(gameObject.Name, "")); - dbDict.Add("description", Common.ReturnValueIfNull(gameObject.Description, "")); - } - dbDict.Add("year", Common.ReturnValueIfNull(gameObject.Year, "")); - dbDict.Add("publisher", Common.ReturnValueIfNull(gameObject.Publisher, "")); - dbDict.Add("demo", (int)gameObject.Demo); - dbDict.Add("system", Common.ReturnValueIfNull(gameObject.System, "")); - dbDict.Add("platform", Common.ReturnValueIfNull(gameObject.System, "")); - dbDict.Add("systemvariant", Common.ReturnValueIfNull(gameObject.SystemVariant, "")); - dbDict.Add("video", Common.ReturnValueIfNull(gameObject.Video, "")); - 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) + int countryId = -1; + countryId = Common.GetLookupByCode(Common.LookupTypes.Country, (string)Common.ReturnValueIfNull(country.Trim(), "")); + if (countryId == -1) { - // 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); + countryId = Common.GetLookupByValue(Common.LookupTypes.Country, (string)Common.ReturnValueIfNull(country.Trim(), "")); - gameSystem = Convert.ToInt32(sigDB.Rows[0][0]); + if (countryId == -1) + { + Logging.Log(Logging.LogType.Warning, "Signature Ingest", "Unable to locate country id for " + country.Trim()); + sql = "INSERT INTO Country (`Code`, `Value`) VALUES (@code, @name); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + Dictionary countryDict = new Dictionary{ + { "code", country.Trim() }, + { "name", country.Trim() } + }; + countryId = int.Parse(db.ExecuteCMD(sql, countryDict).Rows[0][0].ToString()); + } } - else + + if (countryId > 0) { - gameSystem = (int)sigDB.Rows[0][0]; + gameCountries.Add(countryId); } } - dbDict.Add("systemid", gameSystem); + } - // store publisher - int gamePublisher = 0; - if (gameObject.Publisher != null) + List gameLanguages = new List(); + if ( + gameObject.Language != null && + gameObject.Language != "nolang" + ) + { + string[] languages = gameObject.Language.Split(","); + foreach (string language in languages) { - sql = "SELECT * FROM Signatures_Publishers WHERE Publisher=@publisher"; + int languageId = -1; + languageId = Common.GetLookupByCode(Common.LookupTypes.Language, (string)Common.ReturnValueIfNull(language.Trim(), "")); + if (languageId == -1) + { + languageId = Common.GetLookupByValue(Common.LookupTypes.Language, (string)Common.ReturnValueIfNull(language.Trim(), "")); - sigDB = db.ExecuteCMD(sql, dbDict); - if (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]); + if (languageId == -1) + { + Logging.Log(Logging.LogType.Warning, "Signature Ingest", "Unable to locate language id for " + language.Trim()); + sql = "INSERT INTO Language (`Code`, `Value`) VALUES (@code, @name); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + Dictionary langDict = new Dictionary{ + { "code", language.Trim() }, + { "name", language.Trim() } + }; + languageId = int.Parse(db.ExecuteCMD(sql, langDict).Rows[0][0].ToString()); + } } - else + + if (languageId > 0) { - gamePublisher = (int)sigDB.Rows[0][0]; + gameLanguages.Add(languageId); } } - dbDict.Add("publisherid", gamePublisher); + } - // store game - 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"; + 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_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);"; + sql = "INSERT INTO Signatures_Platforms (`Platform`) VALUES (@platform); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; sigDB = db.ExecuteCMD(sql, dbDict); - gameId = Convert.ToInt32(sigDB.Rows[0][0]); + gameSystem = Convert.ToInt32(sigDB.Rows[0][0]); } else { - gameId = (int)sigDB.Rows[0][0]; + gameSystem = (int)sigDB.Rows[0][0]; } + } + dbDict.Add("systemid", gameSystem); - // store rom - foreach (RomSignatureObject.Game.Rom romObject in gameObject.Roms) + // 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) { - if (romObject.Md5 != null || romObject.Sha1 != null) + // entry not present, insert it + sql = "INSERT INTO Signatures_Publishers (`Publisher`) VALUES (@publisher); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + sigDB = db.ExecuteCMD(sql, dbDict); + gamePublisher = Convert.ToInt32(sigDB.Rows[0][0]); + } + else + { + gamePublisher = (int)sigDB.Rows[0][0]; + } + } + dbDict.Add("publisherid", gamePublisher); + + // store game + long gameId = 0; + sql = "SELECT * FROM Signatures_Games WHERE `Name`=@name AND `Year`=@year AND `PublisherId`=@publisherid AND `SystemId`=@systemid"; + + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + // entry not present, insert it + sql = "INSERT INTO Signatures_Games " + + "(`Name`, `Description`, `Year`, `PublisherId`, `Demo`, `SystemId`, `SystemVariant`, `Video`, `Copyright`) VALUES " + + "(@name, @description, @year, @publisherid, @demo, @systemid, @systemvariant, @video, @copyright); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + sigDB = db.ExecuteCMD(sql, dbDict); + + gameId = Convert.ToInt32(sigDB.Rows[0][0]); + } + else + { + gameId = (int)sigDB.Rows[0][0]; + } + + // insert countries + foreach (int gameCountry in gameCountries) + { + try + { + sql = "SELECT * FROM Signatures_Games_Countries WHERE GameId = @gameid AND CountryId = @Countryid"; + Dictionary countryDict = new Dictionary{ + { "gameid", gameId }, + { "Countryid", gameCountry } + }; + if (db.ExecuteCMD(sql, countryDict).Rows.Count == 0) { - 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, "").ToString().ToLower()); - dbDict.Add("md5", Common.ReturnValueIfNull(romObject.Md5, "").ToString().ToLower()); - dbDict.Add("sha1", Common.ReturnValueIfNull(romObject.Sha1, "").ToString().ToLower()); - dbDict.Add("developmentstatus", Common.ReturnValueIfNull(romObject.DevelopmentStatus, "")); + sql = "INSERT INTO Signatures_Games_Countries (GameId, CountryId) VALUES (@gameid, @Countryid)"; + db.ExecuteCMD(sql, countryDict); + } + } + catch + { + Console.WriteLine("Game id: " + gameId + " with Country " + gameCountry); + } + } - if (romObject.Attributes != null) + // insert languages + foreach (int gameLanguage in gameLanguages) + { + try + { + sql = "SELECT * FROM Signatures_Games_Languages WHERE GameId = @gameid AND LanguageId = @languageid"; + Dictionary langDict = new Dictionary{ + { "gameid", gameId }, + { "languageid", gameLanguage } + }; + if (db.ExecuteCMD(sql, langDict).Rows.Count == 0) + { + sql = "INSERT INTO Signatures_Games_Languages (GameId, LanguageId) VALUES (@gameid, @languageid)"; + db.ExecuteCMD(sql, langDict); + } + } + catch + { + Console.WriteLine("Game id: " + gameId + " with language " + gameLanguage); + } + } + + // store rom + foreach (RomSignatureObject.Game.Rom romObject in gameObject.Roms) + { + if (romObject.Md5 != null || romObject.Sha1 != null) + { + long romId = 0; + sql = "SELECT * FROM Signatures_Roms WHERE `GameId`=@gameid AND (`MD5`=@md5 OR `SHA1`=@sha1)"; + dbDict = new Dictionary(); + dbDict.Add("gameid", gameId); + dbDict.Add("name", Common.ReturnValueIfNull(romObject.Name, "")); + dbDict.Add("size", Common.ReturnValueIfNull(romObject.Size, "")); + dbDict.Add("crc", Common.ReturnValueIfNull(romObject.Crc, "").ToString().ToLower()); + dbDict.Add("md5", Common.ReturnValueIfNull(romObject.Md5, "").ToString().ToLower()); + dbDict.Add("sha1", Common.ReturnValueIfNull(romObject.Sha1, "").ToString().ToLower()); + dbDict.Add("developmentstatus", Common.ReturnValueIfNull(romObject.DevelopmentStatus, "")); + + if (romObject.Attributes != null) + { + if (romObject.Attributes.Count > 0) { - if (romObject.Attributes.Count > 0) - { - dbDict.Add("attributes", Newtonsoft.Json.JsonConvert.SerializeObject(romObject.Attributes)); - } - else - { - dbDict.Add("attributes", "[ ]"); - } + dbDict.Add("attributes", Newtonsoft.Json.JsonConvert.SerializeObject(romObject.Attributes)); } else { - dbDict.Add("attributes", "[ ]"); + dbDict.Add("attributes", ""); } - dbDict.Add("romtype", (int)romObject.RomType); - dbDict.Add("romtypemedia", Common.ReturnValueIfNull(romObject.RomTypeMedia, "")); - dbDict.Add("medialabel", Common.ReturnValueIfNull(romObject.MediaLabel, "")); - dbDict.Add("metadatasource", romObject.SignatureSource); - dbDict.Add("ingestorversion", 2); + } + else + { + dbDict.Add("attributes", ""); + } + dbDict.Add("romtype", (int)romObject.RomType); + dbDict.Add("romtypemedia", Common.ReturnValueIfNull(romObject.RomTypeMedia, "")); + dbDict.Add("medialabel", Common.ReturnValueIfNull(romObject.MediaLabel, "")); + dbDict.Add("metadatasource", romObject.SignatureSource); + dbDict.Add("ingestorversion", 2); + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + // entry not present, insert it + sql = "INSERT INTO Signatures_Roms (`GameId`, `Name`, `Size`, `CRC`, `MD5`, `SHA1`, `DevelopmentStatus`, `Attributes`, `RomType`, `RomTypeMedia`, `MediaLabel`, `MetadataSource`, `IngestorVersion`) VALUES (@gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @attributes, @romtype, @romtypemedia, @medialabel, @metadatasource, @ingestorversion); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; sigDB = db.ExecuteCMD(sql, dbDict); - if (sigDB.Rows.Count == 0) - { - // entry not present, insert it - sql = "INSERT INTO Signatures_Roms (GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Attributes, RomType, RomTypeMedia, MediaLabel, MetadataSource, IngestorVersion) VALUES (@gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @attributes, @romtype, @romtypemedia, @medialabel, @metadatasource, @ingestorversion); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; - sigDB = db.ExecuteCMD(sql, dbDict); + romId = Convert.ToInt32(sigDB.Rows[0][0]); + } + else + { + romId = (int)sigDB.Rows[0][0]; + } - romId = Convert.ToInt32(sigDB.Rows[0][0]); - } - else - { - romId = (int)sigDB.Rows[0][0]; - } + // map the rom to the source + sql = "SELECT * FROM Signatures_RomToSource WHERE SourceId=@sourceid AND RomId=@romid;"; + dbDict.Add("romid", romId); + dbDict.Add("sourceId", sourceId); + + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + sql = "INSERT INTO Signatures_RomToSource (`SourceId`, `RomId`) VALUES (@sourceid, @romid);"; + db.ExecuteCMD(sql, dbDict); } } } } } } - catch (Exception ex) + + File.Move(XMLFile, Path.Combine(ProcessedDirectory, Path.GetFileName(XMLFile))); + if (DBFile != null) { - Logging.Log(Logging.LogType.Warning, "Signature Ingestor - XML", "Invalid import file: " + XMLFile, ex); + File.Move(DBFile, Path.Combine(XMLDBProcessedDirectory, Path.GetFileName(DBFile))); } } - else + catch (Exception ex) { - Logging.Log(Logging.LogType.Debug, "Signature Ingestor - XML", "Rejecting already imported file: " + XMLFile); + Logging.Log(Logging.LogType.Warning, "Signature Ingest", "Error ingesting " + XMLType.ToString() + " file: " + XMLFile, ex); + } + } + else + { + Logging.Log(Logging.LogType.Information, "Signature Ingest", "Rejecting already imported " + XMLType.ToString() + " file: " + XMLFile); + File.Move(XMLFile, Path.Combine(ProcessedDirectory, Path.GetFileName(XMLFile))); + if (DBFile != null) + { + File.Move(DBFile, Path.Combine(XMLDBProcessedDirectory, Path.GetFileName(DBFile))); } } } diff --git a/gaseous-server/Classes/SignatureManagement.cs b/gaseous-server/Classes/SignatureManagement.cs index 2ef5cde..cad5ebc 100644 --- a/gaseous-server/Classes/SignatureManagement.cs +++ b/gaseous-server/Classes/SignatureManagement.cs @@ -1,5 +1,6 @@ using System.Data; using gaseous_signature_parser.models.RomSignatureObject; +using static gaseous_server.Classes.Common; namespace gaseous_server.Classes { @@ -10,7 +11,8 @@ namespace gaseous_server.Classes if (md5.Length > 0) { return _GetSignature("Signatures_Roms.md5 = @searchstring", md5); - } else + } + else { return _GetSignature("Signatures_Roms.sha1 = @searchstring", sha1); } @@ -21,7 +23,8 @@ namespace gaseous_server.Classes if (TosecName.Length > 0) { return _GetSignature("Signatures_Roms.name = @searchstring", TosecName); - } else + } + else { return null; } @@ -44,7 +47,7 @@ namespace gaseous_server.Classes { Game = new gaseous_server.Models.Signatures_Games.GameItem { - Id = (Int32)sigDbRow["Id"], + Id = (long)(int)sigDbRow["Id"], Name = (string)sigDbRow["Name"], Description = (string)sigDbRow["Description"], Year = (string)sigDbRow["Year"], @@ -53,20 +56,20 @@ namespace gaseous_server.Classes System = (string)sigDbRow["Platform"], SystemVariant = (string)sigDbRow["SystemVariant"], Video = (string)sigDbRow["Video"], - Country = (string)sigDbRow["Country"], - Language = (string)sigDbRow["Language"], + Countries = new Dictionary(GetLookup(LookupTypes.Country, (long)(int)sigDbRow["Id"])), + Languages = new Dictionary(GetLookup(LookupTypes.Language, (long)(int)sigDbRow["Id"])), Copyright = (string)sigDbRow["Copyright"] }, Rom = new gaseous_server.Models.Signatures_Games.RomItem { - Id = (Int32)sigDbRow["romid"], + Id = (long)(int)sigDbRow["romid"], Name = (string)sigDbRow["romname"], Size = (Int64)sigDbRow["Size"], Crc = (string)sigDbRow["CRC"], Md5 = ((string)sigDbRow["MD5"]).ToLower(), Sha1 = ((string)sigDbRow["SHA1"]).ToLower(), DevelopmentStatus = (string)sigDbRow["DevelopmentStatus"], - Attributes = Newtonsoft.Json.JsonConvert.DeserializeObject>>((string)Common.ReturnValueIfNull(sigDbRow["Attributes"], "[]")), + Attributes = Newtonsoft.Json.JsonConvert.DeserializeObject>((string)Common.ReturnValueIfNull(sigDbRow["Attributes"], "[]")), RomType = (gaseous_server.Models.Signatures_Games.RomItem.RomTypes)(int)sigDbRow["RomType"], RomTypeMedia = (string)sigDbRow["RomTypeMedia"], MediaLabel = (string)sigDbRow["MediaLabel"], @@ -77,5 +80,36 @@ namespace gaseous_server.Classes } return GamesList; } + + public Dictionary GetLookup(LookupTypes LookupType, long GameId) + { + string tableName = ""; + switch (LookupType) + { + case LookupTypes.Country: + tableName = "Countries"; + break; + + case LookupTypes.Language: + tableName = "Languages"; + break; + + } + + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT " + LookupType.ToString() + ".Code, " + LookupType.ToString() + ".Value FROM Signatures_Games_" + tableName + " JOIN " + LookupType.ToString() + " ON Signatures_Games_" + tableName + "." + LookupType.ToString() + "Id = " + LookupType.ToString() + ".Id WHERE Signatures_Games_" + tableName + ".GameId = @id;"; + Dictionary dbDict = new Dictionary{ + { "id", GameId } + }; + DataTable data = db.ExecuteCMD(sql, dbDict); + + Dictionary returnDict = new Dictionary(); + foreach (DataRow row in data.Rows) + { + returnDict.Add((string)row["Code"], (string)row["Value"]); + } + + return returnDict; + } } } \ No newline at end of file diff --git a/gaseous-server/Controllers/V1.0/SystemController.cs b/gaseous-server/Controllers/V1.0/SystemController.cs index 10eb213..69b89a3 100644 --- a/gaseous-server/Controllers/V1.0/SystemController.cs +++ b/gaseous-server/Controllers/V1.0/SystemController.cs @@ -260,7 +260,14 @@ namespace gaseous_server.Controllers { AlwaysLogToDisk = Config.LoggingConfiguration.AlwaysLogToDisk, MinimumLogRetentionPeriod = Config.LoggingConfiguration.LogRetention, - EmulatorDebugMode = Boolean.Parse(Config.ReadSetting("emulatorDebugMode", false.ToString())) + EmulatorDebugMode = Boolean.Parse(Config.ReadSetting("emulatorDebugMode", false.ToString())), + SignatureSource = new SystemSettingsModel.SignatureSourceItem() + { + Source = Config.MetadataConfiguration.SignatureSource, + HasheousHost = Config.MetadataConfiguration.HasheousHost, + HasheousSubmitFixes = (bool)Config.MetadataConfiguration.HasheousSubmitFixes, + HasheousAPIKey = Config.MetadataConfiguration.HasheousAPIKey + } }; return Ok(systemSettingsModel); @@ -279,6 +286,10 @@ namespace gaseous_server.Controllers Config.LoggingConfiguration.AlwaysLogToDisk = model.AlwaysLogToDisk; Config.LoggingConfiguration.LogRetention = model.MinimumLogRetentionPeriod; Config.SetSetting("emulatorDebugMode", model.EmulatorDebugMode.ToString()); + Config.MetadataConfiguration.SignatureSource = model.SignatureSource.Source; + Config.MetadataConfiguration.HasheousHost = model.SignatureSource.HasheousHost; + Config.MetadataConfiguration.HasheousAPIKey = model.SignatureSource.HasheousAPIKey; + Config.MetadataConfiguration.HasheousSubmitFixes = model.SignatureSource.HasheousSubmitFixes; Config.UpdateConfig(); } @@ -719,5 +730,14 @@ namespace gaseous_server.Controllers public bool AlwaysLogToDisk { get; set; } public int MinimumLogRetentionPeriod { get; set; } public bool EmulatorDebugMode { get; set; } + public SignatureSourceItem SignatureSource { get; set; } + + public class SignatureSourceItem + { + public HasheousClient.Models.MetadataModel.SignatureSources Source { get; set; } + public string HasheousHost { get; set; } + public string HasheousAPIKey { get; set; } + public bool HasheousSubmitFixes { get; set; } + } } } \ No newline at end of file diff --git a/gaseous-server/Models/Signatures_Games.cs b/gaseous-server/Models/Signatures_Games.cs index b2fe9b7..cc389cc 100644 --- a/gaseous-server/Models/Signatures_Games.cs +++ b/gaseous-server/Models/Signatures_Games.cs @@ -4,12 +4,36 @@ using gaseous_signature_parser.models.RomSignatureObject; namespace gaseous_server.Models { - public class Signatures_Games : HasheousClient.Models.LookupResponseModel + public class Signatures_Games : HasheousClient.Models.SignatureModel { public Signatures_Games() { } + [JsonIgnore] + public int Score + { + get + { + int _score = 0; + + if (Game != null) + { + _score = _score + Game.Score; + } + + if (Rom != null) + { + _score = _score + Rom.Score; + } + + return _score; + } + } + + public GameItem Game = new GameItem(); + public RomItem Rom = new RomItem(); + public SignatureFlags Flags = new SignatureFlags(); public class SignatureFlags @@ -18,6 +42,213 @@ namespace gaseous_server.Models public string IGDBPlatformName { get; set; } public long IGDBGameId { get; set; } } + + public class GameItem : HasheousClient.Models.SignatureModel.GameItem + { + public GameItem() + { + + } + + [JsonIgnore] + public int Score + { + get + { + // calculate a score based on the availablility of data + int _score = 0; + var properties = this.GetType().GetProperties(); + foreach (var prop in properties) + { + if (prop.GetGetMethod() != null) + { + switch (prop.Name.ToLower()) + { + case "id": + case "score": + break; + case "name": + case "year": + case "publisher": + case "system": + if (prop.PropertyType == typeof(string)) + { + if (prop.GetValue(this) != null) + { + string propVal = prop.GetValue(this).ToString(); + if (propVal.Length > 0) + { + _score = _score + 10; + } + } + } + break; + default: + if (prop.PropertyType == typeof(string)) + { + if (prop.GetValue(this) != null) + { + string propVal = prop.GetValue(this).ToString(); + if (propVal.Length > 0) + { + _score = _score + 1; + } + } + } + break; + } + } + } + + return _score; + } + } + } + + public class RomItem : HasheousClient.Models.SignatureModel.RomItem + { + [JsonIgnore] + public int Score + { + get + { + // calculate a score based on the availablility of data + int _score = 0; + var properties = this.GetType().GetProperties(); + foreach (var prop in properties) + { + if (prop.GetGetMethod() != null) + { + switch (prop.Name.ToLower()) + { + case "name": + case "size": + case "crc": + case "developmentstatus": + case "flags": + case "attributes": + case "romtypemedia": + case "medialabel": + if (prop.PropertyType == typeof(string) || prop.PropertyType == typeof(Int64) || prop.PropertyType == typeof(List)) + { + if (prop.GetValue(this) != null) + { + string propVal = prop.GetValue(this).ToString(); + if (propVal.Length > 0) + { + _score = _score + 10; + } + } + } + break; + default: + if (prop.PropertyType == typeof(string)) + { + if (prop.GetValue(this) != null) + { + string propVal = prop.GetValue(this).ToString(); + if (propVal.Length > 0) + { + _score = _score + 1; + } + } + } + break; + } + } + } + + return _score; + } + } + + public class MediaType + { + public MediaType(SignatureSourceType Source, string MediaTypeString) + { + switch (Source) + { + case RomItem.SignatureSourceType.TOSEC: + string[] typeString = MediaTypeString.Split(" "); + + string inType = ""; + foreach (string typeStringVal in typeString) + { + if (inType == "") + { + switch (typeStringVal.ToLower()) + { + case "disk": + Media = RomItem.RomTypes.Disk; + + inType = typeStringVal; + break; + case "disc": + Media = RomItem.RomTypes.Disc; + + inType = typeStringVal; + break; + case "file": + Media = RomItem.RomTypes.File; + + inType = typeStringVal; + break; + case "part": + Media = RomItem.RomTypes.Part; + + inType = typeStringVal; + break; + case "tape": + Media = RomItem.RomTypes.Tape; + + inType = typeStringVal; + break; + case "of": + inType = typeStringVal; + break; + case "side": + inType = typeStringVal; + break; + } + } + else { + switch (inType.ToLower()) + { + case "disk": + case "disc": + case "file": + case "part": + case "tape": + Number = int.Parse(typeStringVal); + break; + case "of": + Count = int.Parse(typeStringVal); + break; + case "side": + Side = typeStringVal; + break; + } + inType = ""; + } + } + + break; + + default: + break; + + } + } + + public RomItem.RomTypes? Media { get; set; } + + public int? Number { get; set; } + + public int? Count { get; set; } + + public string? Side { get; set; } + } + } } } diff --git a/gaseous-server/ProcessQueue.cs b/gaseous-server/ProcessQueue.cs index 8b0f0d9..07dd941 100644 --- a/gaseous-server/ProcessQueue.cs +++ b/gaseous-server/ProcessQueue.cs @@ -245,15 +245,33 @@ namespace gaseous_server CallingQueueItem = this }; - Logging.Log(Logging.LogType.Debug, "Signature Import", "Processing TOSEC files"); - tIngest.Import(Path.Combine(Config.LibraryConfiguration.LibrarySignatureImportDirectory, "TOSEC"), gaseous_signature_parser.parser.SignatureParser.TOSEC); - - Logging.Log(Logging.LogType.Debug, "Signature Import", "Processing MAME Arcade files"); - tIngest.Import(Path.Combine(Config.LibraryConfiguration.LibrarySignatureImportDirectory, "MAME Arcade"), gaseous_signature_parser.parser.SignatureParser.MAMEArcade); + foreach (int i in Enum.GetValues(typeof(gaseous_signature_parser.parser.SignatureParser))) + { + gaseous_signature_parser.parser.SignatureParser parserType = (gaseous_signature_parser.parser.SignatureParser)i; + if ( + parserType != gaseous_signature_parser.parser.SignatureParser.Auto && + parserType != gaseous_signature_parser.parser.SignatureParser.Unknown + ) + { + Logging.Log(Logging.LogType.Debug, "Signature Import", "Processing " + parserType + " files"); + + string SignaturePath = Path.Combine(Config.LibraryConfiguration.LibrarySignaturesDirectory, parserType.ToString()); + string SignatureProcessedPath = Path.Combine(Config.LibraryConfiguration.LibrarySignaturesProcessedDirectory, parserType.ToString()); + + if (!Directory.Exists(SignaturePath)) + { + Directory.CreateDirectory(SignaturePath); + } + + if (!Directory.Exists(SignatureProcessedPath)) + { + Directory.CreateDirectory(SignatureProcessedPath); + } + + tIngest.Import(SignaturePath, SignatureProcessedPath, parserType); + } + } - Logging.Log(Logging.LogType.Debug, "Signature Import", "Processing MAME MESS files"); - tIngest.Import(Path.Combine(Config.LibraryConfiguration.LibrarySignatureImportDirectory, "MAME MESS"), gaseous_signature_parser.parser.SignatureParser.MAMEMess); - _SaveLastRunTime = true; break; diff --git a/gaseous-server/Program.cs b/gaseous-server/Program.cs index b623528..a4f7ab7 100644 --- a/gaseous-server/Program.cs +++ b/gaseous-server/Program.cs @@ -71,6 +71,7 @@ if (Directory.Exists(Config.LibraryConfiguration.LibraryUploadDirectory)) // kick off any delayed upgrade tasks // run 1002 background updates in the background on every start DatabaseMigration.BackgroundUpgradeTargetSchemaVersions.Add(1002); +DatabaseMigration.BackgroundUpgradeTargetSchemaVersions.Add(1022); // start the task ProcessQueue.QueueItem queueItem = new ProcessQueue.QueueItem( ProcessQueue.QueueItemType.BackgroundDatabaseUpgrade, diff --git a/gaseous-server/Support/Country.txt b/gaseous-server/Support/Country.txt new file mode 100644 index 0000000..5ee02a3 --- /dev/null +++ b/gaseous-server/Support/Country.txt @@ -0,0 +1,73 @@ +AE|United Arab Emirates +AL|Albania +AS|Asia +AT|Austria +AU|Australia +BA|Bosnia and Herzegovina +BE|Belgium +BG|Bulgaria +BR|Brazil +CA|Canada +CH|Switzerland +CL|Chile +CN|China +CS|Serbia and Montenegro +CY|Cyprus +CZ|Czech Republic +DE|Germany +DK|Denmark +EE|Estonia +EG|Egypt +ES|Spain +EU|Europe +FI|Finland +FR|France +GB|United Kingdom +GR|Greece +HK|Hong Kong +HR|Croatia +HU|Hungary +ID|Indonesia +IE|Ireland +IL|Israel +IN|India +IR|Iran +IS|Iceland +IT|Italy +JO|Jordan +JP|Japan +KR|Korea +KR|South Korea +LT|Lithuania +LU|Luxembourg +LV|Latvia +MN|Mongolia +MX|Mexico +MY|Malaysia +NL|Netherlands +NO|Norway +NP|Nepal +NZ|New Zealand +OM|Oman +PE|Peru +PH|Philippines +PL|Poland +PT|Portugal +QA|Qatar +RO|Romania +RU|Russia +SE|Sweden +SG|Singapore +SI|Slovenia +SK|Slovakia +TH|Thailand +TR|Turkey +TW|Taiwan +US|United States +USA|United States +VN|Vietnam +YU|Yugoslavia +ZA|South Africa +World|World +Europe|Europe +Asia|Asia diff --git a/gaseous-server/Support/Database/MySQL/gaseous-1022.sql b/gaseous-server/Support/Database/MySQL/gaseous-1022.sql new file mode 100644 index 0000000..b089e9c --- /dev/null +++ b/gaseous-server/Support/Database/MySQL/gaseous-1022.sql @@ -0,0 +1,40 @@ +CREATE TABLE `Signatures_RomToSource` ( + `SourceId` int NOT NULL, + `RomId` int NOT NULL, + PRIMARY KEY (`SourceId`, `RomId`) +); + +CREATE TABLE `Signatures_Games_Countries` ( + `GameId` INT NOT NULL, + `CountryId` INT NOT NULL, + PRIMARY KEY (`GameId`, `CountryId`), + CONSTRAINT `GameCountry` FOREIGN KEY (`GameId`) REFERENCES `Signatures_Games` (`Id`) ON DELETE CASCADE ON UPDATE NO ACTION +); + +CREATE TABLE `Signatures_Games_Languages` ( + `GameId` INT NOT NULL, + `LanguageId` INT NOT NULL, + PRIMARY KEY (`GameId`, `LanguageId`), + CONSTRAINT `GameLanguage` FOREIGN KEY (`GameId`) REFERENCES `Signatures_Games` (`Id`) ON DELETE CASCADE ON UPDATE NO ACTION +); + +CREATE TABLE `Country` ( + `Id` INT NOT NULL AUTO_INCREMENT, + `Code` VARCHAR(20) NULL, + `Value` VARCHAR(255) NULL, + PRIMARY KEY (`Id`), + INDEX `id_Code` (`Code` ASC) VISIBLE, + INDEX `id_Value` (`Value` ASC) VISIBLE +); + +CREATE TABLE `Language` ( + `Id` INT NOT NULL AUTO_INCREMENT, + `Code` VARCHAR(20) NULL, + `Value` VARCHAR(255) NULL, + PRIMARY KEY (`Id`), + INDEX `id_Code` (`Code` ASC) VISIBLE, + INDEX `id_Value` (`Value` ASC) VISIBLE +); + +ALTER TABLE `Games_Roms` +ADD COLUMN `RomDataVersion` INT DEFAULT 1; \ No newline at end of file diff --git a/gaseous-server/Support/Language.txt b/gaseous-server/Support/Language.txt new file mode 100644 index 0000000..cdf301c --- /dev/null +++ b/gaseous-server/Support/Language.txt @@ -0,0 +1,47 @@ +ar|Arabic +bg|Bulgarian +bs|Bosnian +cs|Czech +cy|Welsh +da|Danish +de|German +el|Greek +en|English +eo|Esperanto +es|Spanish +et|Estonian +fa|Persian +fi|Finnish +fr|French +fr-ca|French Canadian +ga|Irish +gd|Gaelic +gu|Gujarati +he|Hebrew +hi|Hindi +hr|Croatian +hu|Hungarian +is|Icelandic +it|Italian +ja|Japanese +ko|Korean +lt|Lithuanian +lv|Latvian +ms|Malay +nl|Dutch +no|Norwegian +pl|Polish +pt|Portuguese +ro|Romanian +ru|Russian +sk|Slovakian +sl|Slovenian +sq|Albanian +sr|Serbian +sv|Swedish +th|Thai +tr|Turkish +ur|Urdu +vi|Vietnamese +yi|Yiddish +zh|Chinese diff --git a/gaseous-server/gaseous-server.csproj b/gaseous-server/gaseous-server.csproj index 79071b8..a7d42af 100644 --- a/gaseous-server/gaseous-server.csproj +++ b/gaseous-server/gaseous-server.csproj @@ -16,9 +16,9 @@ bin\Release\net8.0\gaseous-server.xml - - - + + + @@ -29,7 +29,7 @@ - + @@ -39,6 +39,8 @@ + + @@ -64,6 +66,7 @@ + @@ -85,6 +88,8 @@ true PreserveNewest + + @@ -108,5 +113,6 @@ + \ No newline at end of file diff --git a/gaseous-server/wwwroot/.DS_Store b/gaseous-server/wwwroot/.DS_Store index f620c5d..d579713 100644 Binary files a/gaseous-server/wwwroot/.DS_Store and b/gaseous-server/wwwroot/.DS_Store differ diff --git a/gaseous-server/wwwroot/images/unknowngame.png b/gaseous-server/wwwroot/images/unknowngame.png index 78b8573..4eddb02 100644 Binary files a/gaseous-server/wwwroot/images/unknowngame.png and b/gaseous-server/wwwroot/images/unknowngame.png differ diff --git a/gaseous-server/wwwroot/images/unknowngame.pxd b/gaseous-server/wwwroot/images/unknowngame.pxd new file mode 100644 index 0000000..5dafd29 Binary files /dev/null and b/gaseous-server/wwwroot/images/unknowngame.pxd differ diff --git a/gaseous-server/wwwroot/pages/dialogs/librarynew.html b/gaseous-server/wwwroot/pages/dialogs/librarynew.html index 0f9c6d9..8525fce 100644 --- a/gaseous-server/wwwroot/pages/dialogs/librarynew.html +++ b/gaseous-server/wwwroot/pages/dialogs/librarynew.html @@ -39,7 +39,7 @@ }, processResults: function (data) { var arr = []; - + arr.push({ id: 0, text: 'Any' @@ -74,11 +74,11 @@ ajaxCall( '/api/v1.1/Library?Name=' + encodeURIComponent(libName) + '&DefaultPlatformId=' + libPlatform[0].id + '&Path=' + encodeURIComponent(libPath), 'POST', - function(result) { + function (result) { drawLibrary(); - closeSubDialog(); + closeDialog(); }, - function(error) { + function (error) { alert('An error occurred while creating the library:\n\n' + JSON.stringify(error.responseText)); } ); diff --git a/gaseous-server/wwwroot/pages/dialogs/rominfo.html b/gaseous-server/wwwroot/pages/dialogs/rominfo.html index b5ce6e2..3e848b9 100644 --- a/gaseous-server/wwwroot/pages/dialogs/rominfo.html +++ b/gaseous-server/wwwroot/pages/dialogs/rominfo.html @@ -1,7 +1,9 @@ 
General
- - + +
Title Match
@@ -59,7 +61,7 @@ @@ -144,7 +148,7 @@ document.getElementById('romDelete').style.display = 'none'; } - if (result.attributes.length > 0) { + if (result.attributes) { document.getElementById('properties_bodypanel_attributes').appendChild(BuildAttributesTable(result.attributes, result.source)); document.getElementById('properties_bodypanel_archive_content').appendChild(BuildArchiveTable(result.attributes, result.source)); } @@ -276,8 +280,8 @@ var aTable = document.createElement('table'); aTable.style.width = '100%'; - for (var i = 0; i < attributes.length; i++) { - if (attributes[i].key != "ZipContents") { + for (const [key, value] of Object.entries(attributes)) { + if (key != "ZipContents") { // show attributes button document.getElementById('properties_toc_attributes').style.display = ''; var aRow = document.createElement('tr'); @@ -285,15 +289,15 @@ var aTitleCell = document.createElement('th'); aTitleCell.width = "25%"; if (sourceName == "TOSEC") { - aTitleCell.innerHTML = ConvertTOSECAttributeName(attributes[i].key); + aTitleCell.innerHTML = ConvertTOSECAttributeName(key); } else { - aTitleCell.innerHTML = attributes[i].key; + aTitleCell.innerHTML = key; } aRow.appendChild(aTitleCell); var aValueCell = document.createElement('td'); aValueCell.width = "75%"; - aValueCell.innerHTML = attributes[i].value; + aValueCell.innerHTML = value; aRow.appendChild(aValueCell); aTable.appendChild(aRow); @@ -304,13 +308,13 @@ } function BuildArchiveTable(attributes, sourceName) { - for (var i = 0; i < attributes.length; i++) { - if (attributes[i].key == "ZipContents") { - var archiveContent = JSON.parse(attributes[i].value); - + for (const [key, value] of Object.entries(attributes)) { + if (key == "ZipContents") { + var archiveContent = JSON.parse(value); + // show archive button document.getElementById('properties_toc_archive').style.display = ''; - + var aTable = document.createElement('table'); aTable.className = 'romtable'; aTable.setAttribute('cellspacing', 0); @@ -343,6 +347,17 @@ hRow.appendChild(aHashCell); aBody.appendChild(hRow); + if (archiveContent[r].isSignatureSelector == true) { + var sigRow = document.createElement('tr'); + + var sigCell = document.createElement('td'); + sigCell.setAttribute('colspan', 2); + sigCell.style.paddingLeft = '20px'; + sigCell.innerHTML = "Hash used to identify this archive"; + sigRow.appendChild(sigCell); + aBody.appendChild(sigRow); + } + aTable.appendChild(aBody); } } @@ -354,18 +369,18 @@ function ConvertTOSECAttributeName(attributeName) { var tosecAttributeNames = { "cr": "Cracked", - "f" : "Fixed", - "h" : "Hacked", - "m" : "Modified", - "p" : "Pirated", - "t" : "Trained", + "f": "Fixed", + "h": "Hacked", + "m": "Modified", + "p": "Pirated", + "t": "Trained", "tr": "Translated", - "o" : "Over Dump", - "u" : "Under Dump", - "v" : "Virus", - "b" : "Bad Dump", - "a" : "Alternate", - "!" : "Known Verified Dump" + "o": "Over Dump", + "u": "Under Dump", + "v": "Virus", + "b": "Bad Dump", + "a": "Alternate", + "!": "Known Verified Dump" }; if (attributeName in tosecAttributeNames) { @@ -378,4 +393,4 @@ SelectTab('general'); document.getElementById('romDelete').setAttribute("onclick", "showSubDialog('romdelete', " + modalVariables + ");"); - + \ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/settings.html b/gaseous-server/wwwroot/pages/settings.html index 74cd83e..4b0b700 100644 --- a/gaseous-server/wwwroot/pages/settings.html +++ b/gaseous-server/wwwroot/pages/settings.html @@ -1,4 +1,5 @@ -
+
@@ -6,27 +7,40 @@
Settings
System
- - - + + + + +
Firmware
- +
About
- +
- Wallpaper by Lorenzo Herrera / Unsplash + Wallpaper by Lorenzo Herrera / Unsplash
+ \ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/settings/libraries.html b/gaseous-server/wwwroot/pages/settings/libraries.html new file mode 100644 index 0000000..d30f616 --- /dev/null +++ b/gaseous-server/wwwroot/pages/settings/libraries.html @@ -0,0 +1,63 @@ +
+

Libraries

+
+ + + +
+
+ + \ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/settings/services.html b/gaseous-server/wwwroot/pages/settings/services.html new file mode 100644 index 0000000..5d61e6c --- /dev/null +++ b/gaseous-server/wwwroot/pages/settings/services.html @@ -0,0 +1,290 @@ +
+

Services

+
+ + +
+
+ + \ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/settings/settings.html b/gaseous-server/wwwroot/pages/settings/settings.html index 93e6a06..aaa8a6e 100644 --- a/gaseous-server/wwwroot/pages/settings/settings.html +++ b/gaseous-server/wwwroot/pages/settings/settings.html @@ -2,37 +2,74 @@

Settings

-

Libraries

- - -
-
- -

Advanced Settings

-

Warning Do not modify the below settings unless you know what you're doing.

-

Background Task Timers

- - -
-
- -

System Settings

- +
- + + + + + + + + + + + + + + + + + + + + + + + + @@ -47,10 +84,12 @@ - + - + @@ -61,339 +100,11 @@
Logging +

Metadata Sources

+
+ Signature Source + + + +
+ + +
+ + + +
+

Logging

+
Write logs - +
- +
Emulator +

Emulator

+
Enable debug mode
\ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/settings/system.html b/gaseous-server/wwwroot/pages/settings/system.html index 8e24b68..d7210e6 100644 --- a/gaseous-server/wwwroot/pages/settings/system.html +++ b/gaseous-server/wwwroot/pages/settings/system.html @@ -22,7 +22,7 @@

Database

-

Signatures

+

Local Database Signatures

+ \ No newline at end of file diff --git a/gaseous-server/wwwroot/styles/style.css b/gaseous-server/wwwroot/styles/style.css index f141bd6..a6200cf 100644 --- a/gaseous-server/wwwroot/styles/style.css +++ b/gaseous-server/wwwroot/styles/style.css @@ -340,6 +340,11 @@ input[type="datetime-local"]:hover { border-color: #939393; } +textarea { + height: unset; + font-family: 'Courier New', Courier, monospace; +} + input[id='filter_panel_search'] { width: 160px; } @@ -607,22 +612,22 @@ input[name='filter_panel_range_max'] { .game_tile:hover { cursor: pointer; - text-decoration: underline; + /* text-decoration: underline; background-color: #2b2b2b; border-radius: 10px 10px 10px 10px; -webkit-border-radius: 10px 10px 10px 10px; -moz-border-radius: 10px 10px 10px 10px; - border: 1px solid #2b2b2b; + border: 1px solid #2b2b2b; */ } .game_tile_small:hover { cursor: pointer; - text-decoration: underline; + /* text-decoration: underline; background-color: #2b2b2b; border-radius: 10px 10px 10px 10px; -webkit-border-radius: 10px 10px 10px 10px; -moz-border-radius: 10px 10px 10px 10px; - border: 1px solid #2b2b2b; + border: 1px solid #2b2b2b; */ } .game_tile_small_search { @@ -652,6 +657,19 @@ input[name='filter_panel_range_max'] { display: inline-block; max-width: 200px; max-height: 200px; + border-radius: 7px; + border-width: 2px; + border-style: solid; + border-color: transparent; + overflow: hidden; +} + +.game_tile:hover .game_tile_box { + border-color: yellow; + outline-width: 2px; + outline-style: solid; + outline-offset: -3px; + outline-color: black; } .game_tile_box_row {