diff --git a/.gitignore b/.gitignore index c608dca..b849cf0 100644 --- a/.gitignore +++ b/.gitignore @@ -405,3 +405,5 @@ ASALocalRun/ .localhistory/ gaseous-server/.DS_Store gaseous-server/wwwroot/emulators/EmulatorJS +.devcontainer/.env +.mono/ \ No newline at end of file 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/gaseous-server/Classes/Common.cs b/gaseous-server/Classes/Common.cs index 7ecc320..4df3f74 100644 --- a/gaseous-server/Classes/Common.cs +++ b/gaseous-server/Classes/Common.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.ComponentModel; +using System.Data; using System.IO.Compression; using System.Reflection; using System.Security.Cryptography; @@ -19,7 +20,8 @@ namespace gaseous_server.Classes if (ObjectToCheck == null || ObjectToCheck == System.DBNull.Value) { return IfNullValue; - } else + } + else { return ObjectToCheck; } @@ -27,10 +29,10 @@ namespace gaseous_server.Classes static public DateTime ConvertUnixToDateTime(double UnixTimeStamp) { - DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - dateTime = dateTime.AddSeconds(UnixTimeStamp).ToLocalTime(); - return dateTime; - } + DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + dateTime = dateTime.AddSeconds(UnixTimeStamp).ToLocalTime(); + return dateTime; + } public class hashObject { @@ -41,21 +43,21 @@ namespace gaseous_server.Classes public hashObject(string FileName) { - var xmlStream = File.OpenRead(FileName); + var xmlStream = File.OpenRead(FileName); - var md5 = MD5.Create(); - byte[] md5HashByte = md5.ComputeHash(xmlStream); - string md5Hash = BitConverter.ToString(md5HashByte).Replace("-", "").ToLowerInvariant(); + var md5 = MD5.Create(); + byte[] md5HashByte = md5.ComputeHash(xmlStream); + string md5Hash = BitConverter.ToString(md5HashByte).Replace("-", "").ToLowerInvariant(); _md5hash = md5Hash; - var sha1 = SHA1.Create(); + var sha1 = SHA1.Create(); xmlStream.Position = 0; - byte[] sha1HashByte = sha1.ComputeHash(xmlStream); - string sha1Hash = BitConverter.ToString(sha1HashByte).Replace("-", "").ToLowerInvariant(); + byte[] sha1HashByte = sha1.ComputeHash(xmlStream); + string sha1Hash = BitConverter.ToString(sha1HashByte).Replace("-", "").ToLowerInvariant(); _sha1hash = sha1Hash; xmlStream.Close(); - } + } string _md5hash = ""; string _sha1hash = ""; @@ -85,29 +87,29 @@ namespace gaseous_server.Classes } } - public static long DirSize(DirectoryInfo d) - { - long size = 0; - // Add file sizes. - FileInfo[] fis = d.GetFiles(); - foreach (FileInfo fi in fis) - { - size += fi.Length; - } - // Add subdirectory sizes. - DirectoryInfo[] dis = d.GetDirectories(); - foreach (DirectoryInfo di in dis) - { - size += DirSize(di); - } - return size; - } + public static long DirSize(DirectoryInfo d) + { + long size = 0; + // Add file sizes. + FileInfo[] fis = d.GetFiles(); + foreach (FileInfo fi in fis) + { + size += fi.Length; + } + // Add subdirectory sizes. + DirectoryInfo[] dis = d.GetDirectories(); + foreach (DirectoryInfo di in dis) + { + size += DirSize(di); + } + return size; + } public static string[] SkippableFiles = { ".DS_STORE", "desktop.ini" }; - + public static string NormalizePath(string path) { return Path.GetFullPath(new Uri(path).LocalPath) @@ -155,30 +157,74 @@ namespace gaseous_server.Classes return defaultValue; } } - } + + public static int GetLookupByCode(LookupTypes LookupType, string Code) + { + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT Id FROM " + LookupType.ToString() + " WHERE Code = @code"; + Dictionary dbDict = new Dictionary{ + { "code", Code } + }; + + DataTable data = db.ExecuteCMD(sql, dbDict); + if (data.Rows.Count == 0) + { + return -1; + } + else + { + return (int)data.Rows[0]["Id"]; + } + } + + public static int GetLookupByValue(LookupTypes LookupType, string Value) + { + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT Id FROM " + LookupType.ToString() + " WHERE Value = @value"; + Dictionary dbDict = new Dictionary{ + { "value", Value } + }; + + DataTable data = db.ExecuteCMD(sql, dbDict); + if (data.Rows.Count == 0) + { + return -1; + } + else + { + return (int)data.Rows[0]["Id"]; + } + } + + public enum LookupTypes + { + Country, + Language + } + } /// - /// Provides a way to set contextual data that flows with the call and - /// async context of a test or invocation. - /// - public static class CallContext - { - static ConcurrentDictionary> state = new ConcurrentDictionary>(); + /// Provides a way to set contextual data that flows with the call and + /// async context of a test or invocation. + /// + public static class CallContext + { + static ConcurrentDictionary> state = new ConcurrentDictionary>(); - /// - /// Stores a given object and associates it with the specified name. - /// - /// The name with which to associate the new item in the call context. - /// The object to store in the call context. - public static void SetData(string name, object data) => - state.GetOrAdd(name, _ => new AsyncLocal()).Value = data; + /// + /// Stores a given object and associates it with the specified name. + /// + /// The name with which to associate the new item in the call context. + /// The object to store in the call context. + public static void SetData(string name, object data) => + state.GetOrAdd(name, _ => new AsyncLocal()).Value = data; - /// - /// Retrieves an object with the specified name from the . - /// - /// The name of the item in the call context. - /// The object in the call context associated with the specified name, or if not found. - public static object GetData(string name) => - state.TryGetValue(name, out AsyncLocal data) ? data.Value : null; - } + /// + /// Retrieves an object with the specified name from the . + /// + /// The name of the item in the call context. + /// The object in the call context associated with the specified name, or if not found. + public static object GetData(string name) => + state.TryGetValue(name, out AsyncLocal data) ? data.Value : null; + } } \ No newline at end of file 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 9478d89..c27e9de 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? @@ -264,29 +281,67 @@ namespace gaseous_server.Classes if (Config.MetadataConfiguration.SignatureSource == HasheousClient.Models.MetadataModel.SignatureSources.Hasheous) { HasheousClient.Hasheous hasheous = new HasheousClient.Hasheous(); - SignatureLookupItem? HasheousResult = hasheous.RetrieveFromHasheousAsync(new HashLookupModel{ - MD5 = hash.md5hash, - SHA1 = hash.sha1hash - }); + Console.WriteLine(HasheousClient.WebApp.HttpHelper.BaseUri); + LookupItemModel? HasheousResult = null; + try + { + HasheousResult = hasheous.RetrieveFromHasheous(new HashLookupModel + { + MD5 = hash.md5hash, + SHA1 = hash.sha1hash + }); + } + catch (Exception ex) + { + Logging.Log(Logging.LogType.Warning, "Import Game", "An error occurred while importing " + ImageName, ex); + return null; + } if (HasheousResult != null) { if (HasheousResult.Signature != null) { gaseous_server.Models.Signatures_Games signature = new Models.Signatures_Games(); - signature.Game = HasheousResult.Signature.Game; - signature.Rom = HasheousResult.Signature.Rom; - - if (HasheousResult.MetadataResults != null) + string gameJson = Newtonsoft.Json.JsonConvert.SerializeObject(HasheousResult.Signature.Game); + string romJson = Newtonsoft.Json.JsonConvert.SerializeObject(HasheousResult.Signature.Rom); + signature.Game = Newtonsoft.Json.JsonConvert.DeserializeObject(gameJson); + signature.Rom = Newtonsoft.Json.JsonConvert.DeserializeObject(romJson); + + // get platform metadata + if (HasheousResult.Platform != null) { - if (HasheousResult.MetadataResults.Count > 0) + if (HasheousResult.Platform.metadata.Count > 0) { - foreach (SignatureLookupItem.MetadataResult metadataResult in HasheousResult.MetadataResults) + foreach (HasheousClient.Models.MetadataItem metadataResult in HasheousResult.Platform.metadata) { - if (metadataResult.Source == MetadataModel.MetadataSources.IGDB) + if (metadataResult.Id.Length > 0) { - signature.Flags.IGDBPlatformId = (long)metadataResult.PlatformId; - signature.Flags.IGDBGameId = (long)metadataResult.GameId; + switch (metadataResult.Source) + { + case HasheousClient.Models.MetadataSources.IGDB: + signature.Flags.IGDBPlatformId = (long)Platforms.GetPlatform(metadataResult.Id, false).Id; + break; + } + } + } + } + } + + // get game metadata + if (HasheousResult.Metadata != null) + { + if (HasheousResult.Metadata.Count > 0) + { + foreach (HasheousClient.Models.MetadataItem metadataResult in HasheousResult.Metadata) + { + if (metadataResult.Id.Length > 0) + { + switch (metadataResult.Source) + { + case HasheousClient.Models.MetadataSources.IGDB: + signature.Flags.IGDBGameId = (long)Games.GetGame(metadataResult.Id, false, false, false).Id; + break; + } } } } @@ -368,6 +423,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 18e4e2b..e524907 100644 --- a/gaseous-server/Classes/Metadata/Games.cs +++ b/gaseous-server/Classes/Metadata/Games.cs @@ -5,8 +5,8 @@ using IGDB.Models; namespace gaseous_server.Classes.Metadata { - public class Games - { + public class Games + { const string fieldList = "fields age_ratings,aggregated_rating,aggregated_rating_count,alternative_names,artworks,bundles,category,checksum,collection,collections,cover,created_at,dlcs,expanded_games,expansions,external_games,first_release_date,follows,forks,franchise,franchises,game_engines,game_localizations,game_modes,genres,hypes,involved_companies,keywords,language_supports,multiplayer_modes,name,parent_game,platforms,player_perspectives,ports,rating,rating_count,release_dates,remakes,remasters,screenshots,similar_games,slug,standalone_expansions,status,storyline,summary,tags,themes,total_rating,total_rating_count,updated_at,url,version_parent,version_title,videos,websites;"; public Games() @@ -15,9 +15,9 @@ namespace gaseous_server.Classes.Metadata } public class InvalidGameId : Exception - { + { public InvalidGameId(long Id) : base("Unable to find Game by id " + Id) - {} + { } } public static Game? GetGame(long Id, bool getAllMetadata, bool followSubGames, bool forceRefresh) @@ -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: @@ -285,7 +287,7 @@ namespace gaseous_server.Classes.Metadata { try { - Screenshot GameScreenshot = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh); + Screenshot GameScreenshot = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh); } catch (Exception ex) { @@ -347,7 +349,7 @@ namespace gaseous_server.Classes.Metadata // get missing metadata from parent if this is a port if (result.Category == Category.Port) - { + { if (result.Summary == null) { if (result.ParentGame != null) @@ -364,7 +366,7 @@ namespace gaseous_server.Classes.Metadata return result; } - + public static void AssignAllGamesToPlatformIdZero() { Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); @@ -428,7 +430,7 @@ namespace gaseous_server.Classes.Metadata } string sql = "SELECT Game.Id, Game.`Name`, Game.Slug, Relation_Game_Platforms.PlatformsId AS PlatformsId, Game.Summary FROM gaseous.Game JOIN Relation_Game_Platforms ON Game.Id = Relation_Game_Platforms.GameId WHERE " + whereClause + ";"; - + // get Game metadata Game[]? results = new Game[0]; @@ -439,7 +441,8 @@ namespace gaseous_server.Classes.Metadata DataTable data = db.ExecuteCMD(sql, dbDict); foreach (DataRow row in data.Rows) { - Game game = new Game{ + Game game = new Game + { Id = (long)row["Id"], Name = (string)Common.ReturnValueIfNull(row["Name"], ""), Slug = (string)Common.ReturnValueIfNull(row["Slug"], ""), @@ -476,12 +479,12 @@ namespace gaseous_server.Classes.Metadata searchBody = "where platforms = (" + PlatformId + ") & name ~ \"" + SearchString + "\";"; break; } - + // check search cache Game[]? games = Communications.GetSearchCache(searchFields, searchBody); if (games == null) - { + { // cache miss // get Game metadata Communications comms = new Communications(); @@ -513,7 +516,7 @@ namespace gaseous_server.Classes.Metadata foreach (DataRow row in data.Rows) { KeyValuePair valuePair = new KeyValuePair((long)row["PlatformId"], (string)row["Name"]); - platforms.Add(valuePair); + platforms.Add(valuePair); } return platforms; @@ -533,7 +536,7 @@ namespace gaseous_server.Classes.Metadata { } - + public MinimalGameItem(Game gameObject) { this.Id = gameObject.Id; diff --git a/gaseous-server/Classes/Metadata/Platforms.cs b/gaseous-server/Classes/Metadata/Platforms.cs index 531d771..2ab8ebd 100644 --- a/gaseous-server/Classes/Metadata/Platforms.cs +++ b/gaseous-server/Classes/Metadata/Platforms.cs @@ -7,16 +7,16 @@ using IGDB.Models; namespace gaseous_server.Classes.Metadata { public class Platforms - { + { const string fieldList = "fields abbreviation,alternative_name,category,checksum,created_at,generation,name,platform_family,platform_logo,slug,summary,updated_at,url,versions,websites;"; public Platforms() - { + { - } + } public static Platform? GetPlatform(long Id, bool forceRefresh = false) - { + { if (Id == 0) { Platform returnValue = new Platform(); @@ -44,7 +44,7 @@ namespace gaseous_server.Classes.Metadata Task RetVal = _GetPlatform(SearchUsing.id, Id, forceRefresh); return RetVal.Result; } - catch(Exception ex) + catch (Exception ex) { Logging.Log(Logging.LogType.Warning, "Metadata", "An error occurred fetching Platform Id " + Id, ex); return null; @@ -52,14 +52,14 @@ namespace gaseous_server.Classes.Metadata } } - public static Platform GetPlatform(string Slug, bool forceRefresh = false) - { - Task RetVal = _GetPlatform(SearchUsing.slug, Slug, forceRefresh); - return RetVal.Result; - } + public static Platform GetPlatform(string Slug, bool forceRefresh = false) + { + Task RetVal = _GetPlatform(SearchUsing.slug, Slug, forceRefresh); + return RetVal.Result; + } - private static async Task _GetPlatform(SearchUsing searchUsing, object searchValue, bool forceRefresh) - { + private static async Task _GetPlatform(SearchUsing searchUsing, object searchValue, bool forceRefresh) + { // check database first Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); if (searchUsing == SearchUsing.id) @@ -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?"); } @@ -158,11 +161,12 @@ namespace gaseous_server.Classes.Metadata { Logging.Log(Logging.LogType.Information, "Platform Map", "Importing " + platform.Name + " from predefined data."); // doesn't exist - add it - item = new Models.PlatformMapping.PlatformMapItem{ + item = new Models.PlatformMapping.PlatformMapItem + { IGDBId = (long)platform.Id, IGDBName = platform.Name, IGDBSlug = platform.Slug, - AlternateNames = new List{ platform.AlternativeName } + AlternateNames = new List { platform.AlternativeName } }; Models.PlatformMapping.WritePlatformMap(item, false, true); } diff --git a/gaseous-server/Classes/Roms.cs b/gaseous-server/Classes/Roms.cs index 81ddfd4..904b14c 100644 --- a/gaseous-server/Classes/Roms.cs +++ b/gaseous-server/Classes/Roms.cs @@ -4,29 +4,32 @@ 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 { public class Roms { public class InvalidRomId : Exception - { - public InvalidRomId(long Id) : base("Unable to find ROM by id " + Id) - {} - } + { + public InvalidRomId(long Id) : base("Unable to find ROM by id " + Id) + { } + } public static GameRomObject GetRoms(long GameId, long PlatformId = -1, string NameSearch = "", int pageNumber = 0, int pageSize = 0, string userid = "") { GameRomObject GameRoms = new GameRomObject(); - Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = ""; + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = ""; string sqlCount = ""; string sqlPlatform = ""; Dictionary dbDict = new Dictionary(); - dbDict.Add("id", GameId); + dbDict.Add("id", GameId); dbDict.Add("userid", userid); - + string NameSearchWhere = ""; if (NameSearch.Length > 0) { @@ -37,13 +40,16 @@ namespace gaseous_server.Classes // platform query sqlPlatform = "SELECT DISTINCT Games_Roms.PlatformId, Platform.`Name` FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id WHERE GameId = @id ORDER BY Platform.`Name`;"; - if (PlatformId == -1) { + if (PlatformId == -1) + { // data query sql = "SELECT DISTINCT Games_Roms.*, Platform.`Name` AS platformname, GameState.RomId AS SavedStateRomId FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id LEFT JOIN GameState ON (Games_Roms.Id = GameState.RomId AND GameState.UserId = @userid AND GameState.IsMediaGroup = 0) WHERE Games_Roms.GameId = @id" + NameSearchWhere + " ORDER BY Platform.`Name`, Games_Roms.`Name` LIMIT 1000;"; - + // count query sqlCount = "SELECT COUNT(Games_Roms.Id) AS RomCount FROM Games_Roms WHERE Games_Roms.GameId = @id" + NameSearchWhere + ";"; - } else { + } + else + { // data query sql = "SELECT DISTINCT Games_Roms.*, Platform.`Name` AS platformname, GameState.RomId AS SavedStateRomId FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id LEFT JOIN GameState ON (Games_Roms.Id = GameState.RomId AND GameState.UserId = @userid AND GameState.IsMediaGroup = 0) WHERE Games_Roms.GameId = @id AND Games_Roms.PlatformId = @platformid" + NameSearchWhere + " ORDER BY Platform.`Name`, Games_Roms.`Name` LIMIT 1000;"; @@ -52,12 +58,12 @@ namespace gaseous_server.Classes dbDict.Add("platformid", PlatformId); } - DataTable romDT = db.ExecuteCMD(sql, dbDict); + DataTable romDT = db.ExecuteCMD(sql, dbDict); Dictionary rowCount = db.ExecuteCMDDict(sqlCount, dbDict)[0]; DataTable platformDT = db.ExecuteCMD(sqlPlatform, dbDict); - if (romDT.Rows.Count > 0) - { + if (romDT.Rows.Count > 0) + { // set count of roms GameRoms.Count = int.Parse((string)rowCount["RomCount"]); @@ -73,12 +79,12 @@ namespace gaseous_server.Classes } return GameRoms; - } - else - { - throw new Games.InvalidGameId(GameId); - } - } + } + else + { + throw new Games.InvalidGameId(GameId); + } + } public static GameRomItem GetRom(long RomId) { @@ -108,18 +114,65 @@ namespace gaseous_server.Classes // ensure metadata for gameid is present IGDB.Models.Game game = Classes.Metadata.Games.GetGame(GameId, false, false, false); - Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "UPDATE Games_Roms SET PlatformId=@platformid, GameId=@gameid WHERE Id = @id"; - Dictionary dbDict = new Dictionary(); - dbDict.Add("id", RomId); + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "UPDATE Games_Roms SET PlatformId=@platformid, GameId=@gameid WHERE Id = @id"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", RomId); dbDict.Add("platformid", PlatformId); dbDict.Add("gameid", GameId); db.ExecuteCMD(sql, dbDict); GameRomItem rom = GetRom(RomId); + // 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; - } + } public static void DeleteRom(long RomId) { @@ -137,7 +190,7 @@ namespace gaseous_server.Classes dbDict.Add("id", RomId); db.ExecuteCMD(sql, dbDict); } - } + } private static GameRomItem BuildRom(DataRow romDR) { @@ -150,28 +203,44 @@ 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"], - PlatformId = (long)romDR["platformid"], + { + Id = (long)romDR["id"], + PlatformId = (long)romDR["platformid"], Platform = (string)romDR["platformname"], - GameId = (long)romDR["gameid"], - Name = (string)romDR["name"], - Size = (long)romDR["size"], - Crc = ((string)romDR["crc"]).ToLower(), - Md5 = ((string)romDR["md5"]).ToLower(), - Sha1 = ((string)romDR["sha1"]).ToLower(), - DevelopmentStatus = (string)romDR["developmentstatus"], - Attributes = Newtonsoft.Json.JsonConvert.DeserializeObject>>((string)Common.ReturnValueIfNull(romDR["attributes"], "[ ]")), - RomType = (HasheousClient.Models.LookupResponseModel.RomItem.RomTypes)(int)romDR["romtype"], - RomTypeMedia = (string)romDR["romtypemedia"], - MediaLabel = (string)romDR["medialabel"], - Path = (string)romDR["path"], + GameId = (long)romDR["gameid"], + Name = (string)romDR["name"], + Size = (long)romDR["size"], + Crc = ((string)romDR["crc"]).ToLower(), + Md5 = ((string)romDR["md5"]).ToLower(), + Sha1 = ((string)romDR["sha1"]).ToLower(), + DevelopmentStatus = (string)romDR["developmentstatus"], + Attributes = romAttributes, + RomType = (HasheousClient.Models.SignatureModel.RomItem.RomTypes)(int)romDR["romtype"], + RomTypeMedia = (string)romDR["romtypemedia"], + MediaLabel = (string)romDR["medialabel"], + Path = (string)romDR["path"], SignatureSource = (gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType)(Int32)romDR["metadatasource"], SignatureSourceGameTitle = (string)Common.ReturnValueIfNull(romDR["MetadataGameName"], ""), HasSaveStates = hasSaveStates, Library = GameLibrary.GetLibrary((int)romDR["LibraryId"]) - }; + }; // check for a web emulator and update the romItem foreach (Models.PlatformMapping.PlatformMapItem platformMapping in Models.PlatformMapping.PlatformMap) @@ -185,8 +254,8 @@ namespace gaseous_server.Classes } } - return romItem; - } + return romItem; + } public class GameRomObject { @@ -194,17 +263,17 @@ 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; } - public Models.PlatformMapping.PlatformMapItem.WebEmulatorItem? Emulator { get; set; } - public long GameId { get; set; } + public Models.PlatformMapping.PlatformMapItem.WebEmulatorItem? Emulator { get; set; } + public long GameId { get; set; } public string? Path { get; set; } - public string? SignatureSourceGameTitle { get; set;} + public string? SignatureSourceGameTitle { get; set; } public bool HasSaveStates { get; set; } = false; public GameLibrary.LibraryItem Library { 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 e7d95cf..2aced21 100644 --- a/gaseous-server/Controllers/V1.0/SystemController.cs +++ b/gaseous-server/Controllers/V1.0/SystemController.cs @@ -70,7 +70,8 @@ namespace gaseous_server.Controllers [HttpGet] [Route("Version")] [ProducesResponseType(StatusCodes.Status200OK)] - public Version GetSystemVersion() { + public Version GetSystemVersion() + { return Assembly.GetExecutingAssembly().GetName().Version; } @@ -80,18 +81,19 @@ namespace gaseous_server.Controllers [Route("VersionFile")] [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] - public FileContentResult GetSystemVersionAsFile() { + public FileContentResult GetSystemVersionAsFile() + { Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); // get age ratings dictionary Dictionary ClassificationBoardsStrings = new Dictionary(); - foreach(IGDB.Models.AgeRatingCategory ageRatingCategory in Enum.GetValues(typeof(IGDB.Models.AgeRatingCategory)) ) + foreach (IGDB.Models.AgeRatingCategory ageRatingCategory in Enum.GetValues(typeof(IGDB.Models.AgeRatingCategory))) { ClassificationBoardsStrings.Add((int)ageRatingCategory, ageRatingCategory.ToString()); } Dictionary AgeRatingsStrings = new Dictionary(); - foreach(IGDB.Models.AgeRatingTitle ageRatingTitle in Enum.GetValues(typeof(IGDB.Models.AgeRatingTitle)) ) + foreach (IGDB.Models.AgeRatingTitle ageRatingTitle in Enum.GetValues(typeof(IGDB.Models.AgeRatingTitle))) { AgeRatingsStrings.Add((int)ageRatingTitle, ageRatingTitle.ToString()); } @@ -99,13 +101,16 @@ namespace gaseous_server.Controllers string ver = "var AppVersion = \"" + Assembly.GetExecutingAssembly().GetName().Version.ToString() + "\";" + Environment.NewLine + "var DBSchemaVersion = \"" + db.GetDatabaseSchemaVersion() + "\";" + Environment.NewLine + "var FirstRunStatus = " + Config.ReadSetting("FirstRunStatus", "0") + ";" + Environment.NewLine + - "var AgeRatingBoardsStrings = " + JsonSerializer.Serialize(ClassificationBoardsStrings, new JsonSerializerOptions{ + "var AgeRatingBoardsStrings = " + JsonSerializer.Serialize(ClassificationBoardsStrings, new JsonSerializerOptions + { WriteIndented = true }) + ";" + Environment.NewLine + - "var AgeRatingStrings = " + JsonSerializer.Serialize(AgeRatingsStrings, new JsonSerializerOptions{ + "var AgeRatingStrings = " + JsonSerializer.Serialize(AgeRatingsStrings, new JsonSerializerOptions + { WriteIndented = true }) + ";" + Environment.NewLine + - "var AgeRatingGroups = " + JsonSerializer.Serialize(AgeGroups.AgeGroupingsFlat, new JsonSerializerOptions{ + "var AgeRatingGroups = " + JsonSerializer.Serialize(AgeGroups.AgeGroupingsFlat, new JsonSerializerOptions + { WriteIndented = true }) + ";" + Environment.NewLine + "var emulatorDebugMode = " + Config.ReadSetting("emulatorDebugMode", false.ToString()).ToLower() + ";"; @@ -159,7 +164,7 @@ namespace gaseous_server.Controllers { // update task enabled Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with enabled value " + TaskConfiguration.Enabled.ToString()); - + Config.SetSetting("Enabled_" + TaskConfiguration.Task, TaskConfiguration.Enabled.ToString()); // update existing process @@ -170,12 +175,12 @@ namespace gaseous_server.Controllers item.Enabled(Boolean.Parse(TaskConfiguration.Enabled.ToString())); } } - + // update task interval if (TaskConfiguration.Interval >= taskItem.MinimumAllowedInterval) { Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with new interval " + TaskConfiguration.Interval); - + Config.SetSetting("Interval_" + TaskConfiguration.Task, TaskConfiguration.Interval.ToString()); // update existing process @@ -194,7 +199,7 @@ namespace gaseous_server.Controllers // update task weekdays Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with new weekdays " + String.Join(", ", TaskConfiguration.AllowedDays)); - + Config.SetSetting("AllowedDays_" + TaskConfiguration.Task, Newtonsoft.Json.JsonConvert.SerializeObject(TaskConfiguration.AllowedDays)); // update existing process @@ -208,7 +213,7 @@ namespace gaseous_server.Controllers // update task hours Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with new hours " + TaskConfiguration.AllowedStartHours + ":" + TaskConfiguration.AllowedStartMinutes.ToString("00") + " to " + TaskConfiguration.AllowedEndHours + ":" + TaskConfiguration.AllowedEndMinutes.ToString("00")); - + Config.SetSetting("AllowedStartHours_" + TaskConfiguration.Task, TaskConfiguration.AllowedStartHours.ToString()); Config.SetSetting("AllowedStartMinutes_" + TaskConfiguration.Task, TaskConfiguration.AllowedStartMinutes.ToString()); Config.SetSetting("AllowedEndHours_" + TaskConfiguration.Task, TaskConfiguration.AllowedEndHours.ToString()); @@ -225,7 +230,7 @@ namespace gaseous_server.Controllers item.AllowedEndMinutes = TaskConfiguration.AllowedEndMinutes; } } - + } else { @@ -251,10 +256,18 @@ namespace gaseous_server.Controllers [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetSystemSettings() { - SystemSettingsModel systemSettingsModel = new SystemSettingsModel{ + SystemSettingsModel systemSettingsModel = new SystemSettingsModel + { 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); @@ -273,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(); } @@ -281,7 +298,8 @@ namespace gaseous_server.Controllers private SystemInfo.PathItem GetDisk(string Path) { - SystemInfo.PathItem pathItem = new SystemInfo.PathItem { + SystemInfo.PathItem pathItem = new SystemInfo.PathItem + { LibraryPath = Path, SpaceUsed = Common.DirSize(new DirectoryInfo(Path)), SpaceAvailable = new DriveInfo(Path).AvailableFreeSpace, @@ -293,11 +311,12 @@ namespace gaseous_server.Controllers public class SystemInfo { - public Version ApplicationVersion { + public Version ApplicationVersion + { get - { - return Assembly.GetExecutingAssembly().GetName().Version; - } + { + return Assembly.GetExecutingAssembly().GetName().Version; + } } public List? Paths { get; set; } public long DatabaseSize { get; set; } @@ -352,7 +371,7 @@ namespace gaseous_server.Controllers this.DefaultAllowedEndHours = 23; this.DefaultAllowedEndMinutes = 59; break; - + case ProcessQueue.QueueItemType.TitleIngestor: this._UserManageable = true; this.DefaultInterval = 1; @@ -589,7 +608,8 @@ namespace gaseous_server.Controllers } private bool _UserManageable; public bool UserManageable => _UserManageable; - public int Interval { + public int Interval + { get { return int.Parse(Config.ReadSetting("Interval_" + Task, DefaultInterval.ToString())); @@ -642,7 +662,7 @@ namespace gaseous_server.Controllers public List Blocks { get - { + { if (_Blocks.Contains(ProcessQueue.QueueItemType.All)) { List blockList = new List(); @@ -710,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 02db4a2..15a759e 100644 --- a/gaseous-server/Program.cs +++ b/gaseous-server/Program.cs @@ -64,6 +64,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, @@ -273,7 +274,7 @@ using (var scope = app.Services.CreateScope()) { var roleManager = scope.ServiceProvider.GetRequiredService(); var roles = new[] { "Admin", "Gamer", "Player" }; - + foreach (var role in roles) { if (await roleManager.FindByNameAsync(role, CancellationToken.None) == null) @@ -303,11 +304,11 @@ app.Use(async (context, next) => string correlationId = Guid.NewGuid().ToString(); CallContext.SetData("CorrelationId", correlationId); CallContext.SetData("CallingProcess", context.Request.Method + ": " + context.Request.Path); - + string userIdentity; try { - userIdentity = context.User.Claims.Where(x=>x.Type==System.Security.Claims.ClaimTypes.NameIdentifier).FirstOrDefault().Value; + userIdentity = context.User.Claims.Where(x => x.Type == System.Security.Claims.ClaimTypes.NameIdentifier).FirstOrDefault().Value; } catch { @@ -329,7 +330,7 @@ app.Use(async (context, next) => // - the server will not start while the RecoverAccount.txt file exists string PasswordRecoveryFile = Path.Combine(Config.LibraryConfiguration.LibraryRootDirectory, "RecoverAccount.txt"); if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("recoveraccount"))) -{ +{ if (File.Exists(PasswordRecoveryFile)) { // password has already been set - do nothing and just exit @@ -345,7 +346,7 @@ if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("recoveraccount"))) string password = new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray()); File.WriteAllText(PasswordRecoveryFile, password); - + // reset the password using (var scope = app.Services.CreateScope()) { 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 97cc403..5da4ac1 100644 --- a/gaseous-server/gaseous-server.csproj +++ b/gaseous-server/gaseous-server.csproj @@ -16,20 +16,20 @@ bin\Release\net8.0\gaseous-server.xml - - - + + + - - - - + + + + - - - + + + - + @@ -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/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 90bacfb..82c4582 100644 --- a/gaseous-server/wwwroot/styles/style.css +++ b/gaseous-server/wwwroot/styles/style.css @@ -23,21 +23,29 @@ h3 { border-bottom-width: 1px; /*border-image: linear-gradient(to right, blue 25%, yellow 25%, yellow 50%,red 50%, red 75%, teal 75%) 5;*/ - border-image: linear-gradient(to right, rgba(255,0,0,1) 0%, rgba(251,255,0,1) 16%, rgba(0,255,250,1) 30%, rgba(0,16,255,1) 46%, rgba(250,0,255,1) 62%, rgba(255,0,0,1) 78%, rgba(255,237,0,1) 90%, rgba(20,255,0,1) 100%) 5; + border-image: linear-gradient(to right, rgba(255, 0, 0, 1) 0%, rgba(251, 255, 0, 1) 16%, rgba(0, 255, 250, 1) 30%, rgba(0, 16, 255, 1) 46%, rgba(250, 0, 255, 1) 62%, rgba(255, 0, 0, 1) 78%, rgba(255, 237, 0, 1) 90%, rgba(20, 255, 0, 1) 100%) 5; } /* The Modal (background) */ .modal { - display: none; /* Hidden by default */ - position: fixed; /* Stay in place */ - z-index: 100; /* Sit on top */ + display: none; + /* Hidden by default */ + position: fixed; + /* Stay in place */ + z-index: 100; + /* Sit on top */ left: 0; top: 0; - width: 100%; /* Full width */ - height: 100%; /* Full height */ - overflow: none; /* Enable scroll if needed */ - background-color: rgb(0,0,0); /* Fallback color */ - background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ + width: 100%; + /* Full width */ + height: 100%; + /* Full height */ + overflow: none; + /* Enable scroll if needed */ + background-color: rgb(0, 0, 0); + /* Fallback color */ + background-color: rgba(0, 0, 0, 0.4); + /* Black w/ opacity */ backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); filter: drop-shadow(5px 5px 10px #000); @@ -47,22 +55,28 @@ h3 { /* Modal Content/Box */ .modal-content { background-color: #383838; - margin: 10% auto; /* 15% from the top and centered */ + margin: 10% auto; + /* 15% from the top and centered */ padding: 10px; border: 1px solid #888; border-radius: 10px; - width: 700px; /* Could be more or less, depending on screen size */ + width: 700px; + /* Could be more or less, depending on screen size */ min-height: 358px; } + .modal-content-sub { background-color: #383838; - margin: 20% auto; /* 20% from the top and centered */ + margin: 20% auto; + /* 20% from the top and centered */ padding: 10px; border: 1px solid #888; border-radius: 10px; - width: 300px; /* Could be more or less, depending on screen size */ + width: 300px; + /* Could be more or less, depending on screen size */ min-height: 110px; } + #modal-heading { margin-block: 5px; border-bottom-style: solid; @@ -70,8 +84,9 @@ h3 { border-bottom-width: 3px; /*border-image: linear-gradient(to right, blue 25%, yellow 25%, yellow 50%,red 50%, red 75%, teal 75%) 5;*/ - border-image: linear-gradient(to right, rgba(255,0,0,1) 0%, rgba(251,255,0,1) 16%, rgba(0,255,250,1) 30%, rgba(0,16,255,1) 46%, rgba(250,0,255,1) 62%, rgba(255,0,0,1) 78%, rgba(255,237,0,1) 90%, rgba(20,255,0,1) 100%) 5; + border-image: linear-gradient(to right, rgba(255, 0, 0, 1) 0%, rgba(251, 255, 0, 1) 16%, rgba(0, 255, 250, 1) 30%, rgba(0, 16, 255, 1) 46%, rgba(250, 0, 255, 1) 62%, rgba(255, 0, 0, 1) 78%, rgba(255, 237, 0, 1) 90%, rgba(20, 255, 0, 1) 100%) 5; } + #modal-content { height: 100%; } @@ -268,7 +283,7 @@ h3 { } #games_filter { - + width: 200px; /* border-style: solid; border-width: 1px; @@ -296,7 +311,13 @@ h3 { z-index: 1; } -input[type='text'], input[type='number'], input[type="email"], input[type="password"], input[type="datetime-local"] { +input[type='text'], +input[type='number'], +input[type="email"], +input[type="password"], +input[type="datetime-local"], +input[type="url"], +textarea { background-color: #2b2b2b; color: white; padding: 4px; @@ -313,10 +334,21 @@ input[type='text'], input[type='number'], input[type="email"], input[type="passw height: 21px; } -input[type='text']:hover, input[type='number']:hover, input[type="email"]:hover, input[type="password"]:hover, input[type="datetime-local"]:hover { +input[type='text']:hover, +input[type='number']:hover, +input[type="email"]:hover, +input[type="password"]:hover, +input[type="datetime-local"]:hover, +input[type="url"]:hover, +textarea:hover { border-color: #939393; } +textarea { + height: unset; + font-family: 'Courier New', Courier, monospace; +} + input[id='filter_panel_search'] { width: 160px; } @@ -351,9 +383,7 @@ input[name='filter_panel_range_max'] { background: #555; } -.text_link { - -} +.text_link {} .text_link:hover { cursor: pointer; @@ -390,9 +420,9 @@ input[name='filter_panel_range_max'] { background-color: rgba(0, 22, 56, 0.8); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); - box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); + box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); } .games_pager_number { @@ -525,13 +555,13 @@ input[name='filter_panel_range_max'] { overflow-y: auto; /* display: flex; */ justify-content: center; - align-items: center; + align-items: center; } #games_library_alpha_pager { width: 50px; justify-content: center; - align-items: center; + align-items: center; } .games_library_alpha_pager_letter { @@ -586,22 +616,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_row { @@ -625,6 +655,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 { @@ -706,9 +749,9 @@ input[name='filter_panel_range_max'] { } .game_tile_image_shadow { - box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); + box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); } .game_tile_image_row { @@ -720,11 +763,13 @@ input[name='filter_panel_range_max'] { background-color: transparent; } -.game_tile_image, .unknown { +.game_tile_image, +.unknown { background-color: transparent; } -.game_tile_image_row, .unknown { +.game_tile_image_row, +.unknown { background-color: transparent; } @@ -790,9 +835,9 @@ input[name='filter_panel_range_max'] { max-width: 250px; max-height: 350px; width: 100%; - box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); + box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); } .gamegenrelabel { @@ -842,9 +887,9 @@ input[name='filter_panel_range_max'] { padding: 10px; /*height: 350px;*/ margin-bottom: 20px; - box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); + box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); } #gamescreenshots_main { @@ -903,11 +948,16 @@ iframe { background-color: #383838; color: black; text-align: center; - user-select: none; /* standard syntax */ - -webkit-user-select: none; /* webkit (safari, chrome) browsers */ - -moz-user-select: none; /* mozilla browsers */ - -khtml-user-select: none; /* webkit (konqueror) browsers */ - -ms-user-select: none; /* IE10+ */ + user-select: none; + /* standard syntax */ + -webkit-user-select: none; + /* webkit (safari, chrome) browsers */ + -moz-user-select: none; + /* mozilla browsers */ + -khtml-user-select: none; + /* webkit (konqueror) browsers */ + -ms-user-select: none; + /* IE10+ */ } .gamescreenshots_arrows:hover { @@ -981,9 +1031,7 @@ iframe { background-color: rgba(56, 56, 56, 0.9); } -#gamesummarytext_label { - -} +#gamesummarytext_label {} .line-clamp-4 { overflow: hidden; @@ -1057,9 +1105,9 @@ th { -webkit-border-radius: 5px 5px 5px 5px; -moz-border-radius: 5px 5px 5px 5px; border: 1px solid #19d348; - box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); + box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); } .romstart:hover { @@ -1084,9 +1132,9 @@ th { border-color: white; background-color: blue; outline-color: blue; - box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); + box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); } .properties_button:hover { @@ -1115,14 +1163,18 @@ th { height: 100%; } -div[name="properties_toc_item"],div[name="properties_user_toc_item"],div[name="properties_profile_toc_item"] { +div[name="properties_toc_item"], +div[name="properties_user_toc_item"], +div[name="properties_profile_toc_item"] { padding: 10px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #2b2b2b; } -div[name="properties_toc_item"]:hover,div[name="properties_user_toc_item"]:hover,div[name="properties_profile_toc_item"]:hover { +div[name="properties_toc_item"]:hover, +div[name="properties_user_toc_item"]:hover, +div[name="properties_profile_toc_item"]:hover { background-color: #2b2b2b; cursor: pointer; } @@ -1150,7 +1202,8 @@ div[name="properties_toc_item"]:hover,div[name="properties_user_toc_item"]:hover border-radius: 5px; } -.select2-container--default:hover, .select2-selection--multiple:hover { +.select2-container--default:hover, +.select2-selection--multiple:hover { border-color: #939393; } @@ -1197,7 +1250,8 @@ div[name="properties_toc_item"]:hover,div[name="properties_user_toc_item"]:hover border-radius: 5px; } -.select2-selection--single:hover, .select2-selection__rendered:hover { +.select2-selection--single:hover, +.select2-selection__rendered:hover { border-color: #939393; } @@ -1302,9 +1356,7 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { background-color: #555; } -#emulator { - -} +#emulator {} .emulator_partscreen { margin: 0 auto; @@ -1377,15 +1429,14 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { margin-left: 15px; } -.rom_checkbox_box { - -} +.rom_checkbox_box {} .rom_checkbox_box_hidden { display: none; } -#rom_edit, #rom_edit_delete { +#rom_edit, +#rom_edit_delete { float: right; } @@ -1398,9 +1449,9 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { } #game { - box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); + box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); } #gametitle_criticrating { @@ -1440,7 +1491,7 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { width: 1000px; height: 90%; margin: 25px auto; -/* overflow-x: scroll;*/ + /* overflow-x: scroll;*/ position: relative; } @@ -1468,7 +1519,8 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { } .bgalt1 { - background-color: transparent;; + background-color: transparent; + ; } .logs_table_cell_150px { @@ -1520,13 +1572,33 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { background-color: #383838; } -.string { color: lightblue; } -.number { color: lightblue; } -.boolean { color: lightblue; } -.null { color: magenta; } -.key { color: greenyellow; } -.brace { color: #888; } -.square { color: #fff000; } +.string { + color: lightblue; +} + +.number { + color: lightblue; +} + +.boolean { + color: lightblue; +} + +.null { + color: magenta; +} + +.key { + color: greenyellow; +} + +.brace { + color: #888; +} + +.square { + color: #fff000; +} .tagBox { position: absolute; @@ -1557,12 +1629,16 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { } .loginwindow { - position: fixed; /* Stay in place */ + position: fixed; + /* Stay in place */ left: 0; top: 0; - width: 100%; /* Full width */ - height: 100%; /* Full height */ - overflow: auto; /* Enable scroll if needed */ + width: 100%; + /* Full width */ + height: 100%; + /* Full height */ + overflow: auto; + /* Enable scroll if needed */ /*background-color: rgb(0,0,0); /* Fallback color */ /*background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ /*backdrop-filter: blur(8px); @@ -1575,11 +1651,13 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { .loginwindow-content { position: relative; background-color: #383838; - margin: 15% auto; /* 15% from the top and centered */ + margin: 15% auto; + /* 15% from the top and centered */ padding: 10px; border: 1px solid #888; border-radius: 10px; - width: 350px; /* Could be more or less, depending on screen size */ + width: 350px; + /* Could be more or less, depending on screen size */ min-height: 250px; } @@ -1615,7 +1693,8 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { } /* Links inside the dropdown */ -.dropdown-content a, .dropdown-content span { +.dropdown-content a, +.dropdown-content span { color: black; padding: 12px 16px; text-decoration: none; @@ -1625,12 +1704,16 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { .dropdown-content span { cursor: auto; } - + /* Change color of dropdown links on hover */ -.dropdown-content a:hover {background-color: #ddd;} - +.dropdown-content a:hover { + background-color: #ddd; +} + /* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */ -.show {display:block;} +.show { + display: block; +} .dropdownroleitem { text-transform: capitalize;