diff --git a/gaseous-server/Classes/Bios.cs b/gaseous-server/Classes/Bios.cs index 9217f18..78a4f56 100644 --- a/gaseous-server/Classes/Bios.cs +++ b/gaseous-server/Classes/Bios.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.InteropServices; using System.Security.Cryptography; +using gaseous_server.Classes.Metadata; using HasheousClient.Models.Metadata.IGDB; namespace gaseous_server.Classes @@ -12,6 +13,37 @@ namespace gaseous_server.Classes } + public static void ImportBiosFile(string FilePath, Common.hashObject Hash, ref Dictionary BiosFileInfo) + { + BiosFileInfo.Add("type", "bios"); + BiosFileInfo.Add("status", "notimported"); + + foreach (Classes.Bios.BiosItem biosItem in Classes.Bios.GetBios()) + { + if (biosItem.Available == false) + { + if (biosItem.hash == Hash.md5hash) + { + string biosPath = Path.Combine(Config.LibraryConfiguration.LibraryFirmwareDirectory, biosItem.hash + ".bios"); + Logging.Log(Logging.LogType.Information, "Import BIOS File", " " + FilePath + " is a BIOS file - moving to " + biosPath); + + File.Move(FilePath, biosItem.biosPath, true); + + BiosFileInfo.Add("name", biosItem.filename); + BiosFileInfo.Add("platform", Platforms.GetPlatform(biosItem.platformid)); + BiosFileInfo["status"] = "imported"; + } + } + else + { + if (biosItem.hash == Hash.md5hash) + { + BiosFileInfo["status"] = "duplicate"; + } + } + } + } + public static void MigrateToNewFolderStructure() { // migrate from old BIOS file structure which had each bios file inside a folder named for the platform to the new structure which has each file in a subdirectory named after the MD5 hash diff --git a/gaseous-server/Classes/FileSignature.cs b/gaseous-server/Classes/FileSignature.cs index 2869c62..c634c73 100644 --- a/gaseous-server/Classes/FileSignature.cs +++ b/gaseous-server/Classes/FileSignature.cs @@ -322,7 +322,7 @@ namespace gaseous_server.Classes switch (metadataResult.Source) { case HasheousClient.Models.MetadataSources.IGDB: - signature.Flags.IGDBPlatformId = (long)Platforms.GetPlatform(metadataResult.Id, false).Id; + signature.Flags.IGDBPlatformId = (long)Platforms.GetPlatform(metadataResult.Id).Id; break; } } diff --git a/gaseous-server/Classes/ImportGames.cs b/gaseous-server/Classes/ImportGames.cs index 7c1db8c..2575a15 100644 --- a/gaseous-server/Classes/ImportGames.cs +++ b/gaseous-server/Classes/ImportGames.cs @@ -18,6 +18,15 @@ namespace gaseous_server.Classes { public class ImportGame : QueueItemStatus { + /// + /// Scan the import directory for games and process them + /// + /// + /// The path to the directory to scan + /// + /// + /// Thrown when the import directory does not exist + /// public void ProcessDirectory(string ImportPath) { if (Directory.Exists(ImportPath)) @@ -47,22 +56,29 @@ namespace gaseous_server.Classes } } + /// + /// Import a single game file + /// + /// + /// The path to the game file to import + /// + /// + /// The platform to use for the game file + /// + /// + /// A dictionary containing the results of the import + /// public Dictionary ImportGameFile(string GameFileImportPath, Platform? OverridePlatform) { Dictionary RetVal = new Dictionary(); RetVal.Add("path", Path.GetFileName(GameFileImportPath)); - Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - 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 { - FileInfo fi = new FileInfo(GameFileImportPath); Common.hashObject hash = new Common.hashObject(GameFileImportPath); Models.PlatformMapping.PlatformMapItem? IsBios = Classes.Bios.BiosHashSignatureLookup(hash.md5hash); @@ -70,114 +86,234 @@ namespace gaseous_server.Classes if (IsBios == null) { // file is a rom - RetVal.Add("type", "rom"); - - // check to make sure we don't already have this file imported - sql = "SELECT COUNT(Id) AS count FROM view_Games_Roms WHERE MD5=@md5 AND SHA1=@sha1"; - dbDict.Add("md5", hash.md5hash); - dbDict.Add("sha1", hash.sha1hash); - DataTable importDB = db.ExecuteCMD(sql, dbDict); - if ((Int64)importDB.Rows[0]["count"] > 0) - { - // import source was the import directory - if (GameFileImportPath.StartsWith(Config.LibraryConfiguration.LibraryImportDirectory)) - { - Logging.Log(Logging.LogType.Warning, "Import Game", " " + GameFileImportPath + " already in database - moving to " + Config.LibraryConfiguration.LibraryImportDuplicatesDirectory); - - string targetPathWithFileName = GameFileImportPath.Replace(Config.LibraryConfiguration.LibraryImportDirectory, Config.LibraryConfiguration.LibraryImportDuplicatesDirectory); - string targetPath = Path.GetDirectoryName(targetPathWithFileName); - - if (!Directory.Exists(targetPath)) - { - Directory.CreateDirectory(targetPath); - } - File.Move(GameFileImportPath, targetPathWithFileName, true); - } - - // import source was the upload directory - if (GameFileImportPath.StartsWith(Config.LibraryConfiguration.LibraryUploadDirectory)) - { - Logging.Log(Logging.LogType.Warning, "Import Game", " " + GameFileImportPath + " already in database - skipping import"); - } - - RetVal.Add("status", "duplicate"); - } - else - { - Logging.Log(Logging.LogType.Information, "Import Game", " " + GameFileImportPath + " not in database - processing"); - - FileSignature fileSignature = new FileSignature(); - gaseous_server.Models.Signatures_Games discoveredSignature = fileSignature.GetFileSignature(GameLibrary.GetDefaultLibrary, hash, fi, GameFileImportPath); - - // get discovered platform - Platform? determinedPlatform = null; - if (OverridePlatform == null) - { - determinedPlatform = Metadata.Platforms.GetPlatform(discoveredSignature.Flags.IGDBPlatformId); - if (determinedPlatform == null) - { - determinedPlatform = new Platform(); - } - } - else - { - determinedPlatform = OverridePlatform; - discoveredSignature.Flags.IGDBPlatformId = (long)determinedPlatform.Id; - discoveredSignature.Flags.IGDBPlatformName = determinedPlatform.Name; - } - - gaseous_server.Models.Game determinedGame = SearchForGame(discoveredSignature, discoveredSignature.Flags.IGDBPlatformId, true); - - // add to database - long RomId = StoreROM(GameLibrary.GetDefaultLibrary, hash, determinedGame, determinedPlatform, discoveredSignature, GameFileImportPath, 0, true); - - // build return value - RetVal.Add("romid", RomId); - RetVal.Add("platform", determinedPlatform); - RetVal.Add("game", determinedGame); - RetVal.Add("signature", discoveredSignature); - RetVal.Add("status", "imported"); - } + _ImportGameFile(GameFileImportPath, hash, ref RetVal, OverridePlatform); } else { // file is a bios - RetVal.Add("type", "bios"); - RetVal.Add("status", "notimported"); - - foreach (Classes.Bios.BiosItem biosItem in Classes.Bios.GetBios()) - { - if (biosItem.Available == false) - { - if (biosItem.hash == hash.md5hash) - { - string biosPath = Path.Combine(Config.LibraryConfiguration.LibraryFirmwareDirectory, biosItem.hash + ".bios"); - Logging.Log(Logging.LogType.Information, "Import Game", " " + GameFileImportPath + " is a BIOS file - moving to " + biosPath); - - File.Move(GameFileImportPath, biosItem.biosPath, true); - - RetVal.Add("name", biosItem.filename); - RetVal.Add("platform", Platforms.GetPlatform(biosItem.platformid)); - RetVal["status"] = "imported"; - - return RetVal; - } - } - else - { - if (biosItem.hash == hash.md5hash) - { - RetVal["status"] = "duplicate"; - return RetVal; - } - } - } + Bios.ImportBiosFile(GameFileImportPath, hash, ref RetVal); } } return RetVal; } + /// + /// Import a single game file + /// + /// + /// The path to the game file to import + /// + /// + /// The hash of the game file + /// + /// + /// A dictionary to store the results of the import + /// + /// + /// The platform to use for the game file + /// + private static void _ImportGameFile(string FilePath, Common.hashObject Hash, ref Dictionary GameFileInfo, Platform? OverridePlatform) + { + GameFileInfo.Add("type", "rom"); + + // check to make sure we don't already have this file imported + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = ""; + Dictionary dbDict = new Dictionary(); + + sql = "SELECT COUNT(Id) AS count FROM view_Games_Roms WHERE MD5=@md5 AND SHA1=@sha1"; + dbDict.Add("md5", Hash.md5hash); + dbDict.Add("sha1", Hash.sha1hash); + DataTable importDB = db.ExecuteCMD(sql, dbDict); + if ((Int64)importDB.Rows[0]["count"] > 0) + { + // import source was the import directory + if (FilePath.StartsWith(Config.LibraryConfiguration.LibraryImportDirectory)) + { + Logging.Log(Logging.LogType.Warning, "Import Game", " " + FilePath + " already in database - moving to " + Config.LibraryConfiguration.LibraryImportDuplicatesDirectory); + + string targetPathWithFileName = FilePath.Replace(Config.LibraryConfiguration.LibraryImportDirectory, Config.LibraryConfiguration.LibraryImportDuplicatesDirectory); + string targetPath = Path.GetDirectoryName(targetPathWithFileName); + + if (!Directory.Exists(targetPath)) + { + Directory.CreateDirectory(targetPath); + } + File.Move(FilePath, targetPathWithFileName, true); + } + + // import source was the upload directory + if (FilePath.StartsWith(Config.LibraryConfiguration.LibraryUploadDirectory)) + { + Logging.Log(Logging.LogType.Warning, "Import Game", " " + FilePath + " already in database - skipping import"); + } + + GameFileInfo.Add("status", "duplicate"); + } + else + { + Logging.Log(Logging.LogType.Information, "Import Game", " " + FilePath + " not in database - processing"); + + FileInfo fi = new FileInfo(FilePath); + FileSignature fileSignature = new FileSignature(); + gaseous_server.Models.Signatures_Games discoveredSignature = fileSignature.GetFileSignature(GameLibrary.GetDefaultLibrary, Hash, fi, FilePath); + + // get discovered platform + Platform? determinedPlatform = null; + if (OverridePlatform == null) + { + determinedPlatform = Metadata.Platforms.GetPlatform(discoveredSignature.Flags.IGDBPlatformId); + if (determinedPlatform == null) + { + determinedPlatform = new Platform(); + } + } + else + { + determinedPlatform = OverridePlatform; + discoveredSignature.Flags.IGDBPlatformId = (long)determinedPlatform.Id; + discoveredSignature.Flags.IGDBPlatformName = determinedPlatform.Name; + } + + // add to database + long RomId = StoreGame(GameLibrary.GetDefaultLibrary, Hash, discoveredSignature, determinedPlatform, FilePath, 0, false); + + // build return value + GameFileInfo.Add("romid", RomId); + GameFileInfo.Add("platform", determinedPlatform); + GameFileInfo.Add("game", discoveredSignature.Game.Name); + GameFileInfo.Add("signature", discoveredSignature); + GameFileInfo.Add("status", "imported"); + } + } + + /// + /// Store a game in the database and move the file to the library (if required) + /// + /// + /// The library to store the game in + /// + /// + /// The hash of the game file + /// + /// + /// The signature of the game file + /// + /// + /// The path to the game file + /// + /// + /// The ID of the ROM in the database (if it already exists, 0 if it doesn't) + /// + /// + /// Whether the source of the file is external to the library + /// + public static long StoreGame(GameLibrary.LibraryItem library, Common.hashObject hash, Signatures_Games signature, Platform platform, string filePath, long romId = 0, bool SourceIsExternal = false) + { + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + + string sql = ""; + + Dictionary dbDict = new Dictionary(); + + // add the metadata map + // check if the map exists already + long mapId = 0; + sql = "SELECT ParentMapId FROM MetadataMapBridge WHERE SignatureGameName=@gamename AND SignaturePlatformId=@platformid;"; + dbDict = new Dictionary(){ + { "gamename", signature.Game.Name }, + { "platformid", platform.Id } + }; + DataTable mapDT = db.ExecuteCMD(sql, dbDict); + + if (mapDT.Rows.Count > 0) + { + mapId = (long)mapDT.Rows[0]["ParentMapId"]; + } + else + { + sql = "INSERT INTO MetadataMapBridge (ParentMapId, SignatureGameName, SignaturePlatformId, MetadataSourceType, MetadataSourceId, Preferred, ProcessedAtImport) VALUES (@parentmapid, @gamename, @platformid, @metadatasourcetype, @metadatasourceid, @preferred, @processedatimport); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + dbDict = new Dictionary(){ + { "parentmapid", 0 }, + { "gamename", signature.Game.Name }, + { "platformid", platform.Id }, + { "metadatasourcetype", MetadataModel.MetadataSources.None }, + { "metadatasourceid", 0 }, // set to zero as an initial value - another process will update this later + { "preferred", 1 }, + { "processedatimport", 0 } + }; + DataTable mapInsert = db.ExecuteCMD(sql, dbDict); + mapId = (long)mapInsert.Rows[0][0]; + } + + // add or update the rom + dbDict = new Dictionary(); + if (romId == 0) + { + sql = "INSERT INTO Games_Roms (PlatformId, GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Attributes, RomType, RomTypeMedia, MediaLabel, RelativePath, MetadataSource, MetadataGameName, MetadataVersion, LibraryId, RomDataVersion, MetadataMapId) VALUES (@platformid, @gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @Attributes, @romtype, @romtypemedia, @medialabel, @path, @metadatasource, @metadatagamename, @metadataversion, @libraryid, @romdataversion, @metadatamapid); 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, RomDataVersion=@romdataversion, MetadataMapId=@metadatamapid WHERE Id=@id;"; + dbDict.Add("id", romId); + } + dbDict.Add("platformid", Common.ReturnValueIfNull(platform.Id, 0)); + dbDict.Add("gameid", 0); // set to 0 - no longer required as game is mapped using the MetadataMapBridge table + dbDict.Add("name", Common.ReturnValueIfNull(signature.Rom.Name, 0)); + dbDict.Add("size", Common.ReturnValueIfNull(signature.Rom.Size, 0)); + dbDict.Add("md5", hash.md5hash); + dbDict.Add("sha1", hash.sha1hash); + dbDict.Add("crc", Common.ReturnValueIfNull(signature.Rom.Crc, "")); + dbDict.Add("developmentstatus", Common.ReturnValueIfNull(signature.Rom.DevelopmentStatus, "")); + dbDict.Add("metadatasource", signature.Rom.SignatureSource); + dbDict.Add("metadatagamename", signature.Game.Name); + dbDict.Add("metadataversion", 2); + dbDict.Add("libraryid", library.Id); + dbDict.Add("romdataversion", 2); + dbDict.Add("metadatamapid", mapId); + + if (signature.Rom.Attributes != null) + { + if (signature.Rom.Attributes.Count > 0) + { + dbDict.Add("attributes", Newtonsoft.Json.JsonConvert.SerializeObject(signature.Rom.Attributes)); + } + else + { + dbDict.Add("attributes", "[ ]"); + } + } + else + { + dbDict.Add("attributes", "[ ]"); + } + dbDict.Add("romtype", (int)signature.Rom.RomType); + dbDict.Add("romtypemedia", Common.ReturnValueIfNull(signature.Rom.RomTypeMedia, "")); + dbDict.Add("medialabel", Common.ReturnValueIfNull(signature.Rom.MediaLabel, "")); + + string libraryRootPath = library.Path; + if (libraryRootPath.EndsWith(Path.DirectorySeparatorChar.ToString()) == false) + { + libraryRootPath += Path.DirectorySeparatorChar; + } + dbDict.Add("path", filePath.Replace(libraryRootPath, "")); + + DataTable romInsert = db.ExecuteCMD(sql, dbDict); + if (romId == 0) + { + romId = (long)romInsert.Rows[0][0]; + } + + // move to destination + if (library.IsDefaultLibrary == true) + { + //MoveGameFile(romId, SourceIsExternal); + } + + return romId; + } + public static gaseous_server.Models.Game SearchForGame(gaseous_server.Models.Signatures_Games Signature, long PlatformId, bool FullDownload) { if (Signature.Flags != null) diff --git a/gaseous-server/Classes/Metadata/Games.cs b/gaseous-server/Classes/Metadata/Games.cs index 2a99270..65b8ee2 100644 --- a/gaseous-server/Classes/Metadata/Games.cs +++ b/gaseous-server/Classes/Metadata/Games.cs @@ -37,7 +37,10 @@ namespace gaseous_server.Classes.Metadata public static Game? GetGame(HasheousClient.Models.MetadataModel.MetadataSources SourceType, string? Slug) { - throw new NotImplementedException(); + Game? RetVal = Metadata.GetMetadata(SourceType, Slug, false); + RetVal.MetadataSource = SourceType; + RetVal = MassageResult(RetVal); + return RetVal; } public static Game GetGame(DataRow dataRow) diff --git a/gaseous-server/Classes/Metadata/Metadata.cs b/gaseous-server/Classes/Metadata/Metadata.cs index 2d9318c..3bb2635 100644 --- a/gaseous-server/Classes/Metadata/Metadata.cs +++ b/gaseous-server/Classes/Metadata/Metadata.cs @@ -17,7 +17,7 @@ namespace gaseous_server.Classes.Metadata { } - public InvalidMetadataId(string SourceType, string Id) : base("Invalid Metadata id: " + Id + " from source: " + SourceType) + public InvalidMetadataId(HasheousClient.Models.MetadataModel.MetadataSources SourceType, string Id) : base("Invalid Metadata id: " + Id + " from source: " + SourceType) { } } @@ -77,14 +77,30 @@ namespace gaseous_server.Classes.Metadata return _GetMetadata(SourceType, Id, ForceRefresh); } - private static T? _GetMetadata(HasheousClient.Models.MetadataModel.MetadataSources SourceType, long Id, Boolean ForceRefresh) where T : class + public static T? GetMetadata(HasheousClient.Models.MetadataModel.MetadataSources SourceType, string Slug, Boolean ForceRefresh = false) where T : class + { + return _GetMetadata(SourceType, Slug, ForceRefresh); + } + + private static T? _GetMetadata(HasheousClient.Models.MetadataModel.MetadataSources SourceType, object Id, Boolean ForceRefresh) where T : class { // get T type as string string type = typeof(T).Name; + // get type of Id as string + IdType idType = Id.GetType() == typeof(long) ? IdType.Long : IdType.String; + // check cached metadata status // if metadata is not cached or expired, get it from the source. Otherwise, return the cached metadata - Storage.CacheStatus? cacheStatus = Storage.GetCacheStatus(SourceType, type, Id); + Storage.CacheStatus? cacheStatus; + if (idType == IdType.Long) + { + cacheStatus = Storage.GetCacheStatus(SourceType, type, (long)Id); + } + else + { + cacheStatus = Storage.GetCacheStatus(SourceType, type, (string)Id); + } // if ForceRefresh is true, set cache status to expired if it is current if (ForceRefresh == true) @@ -100,16 +116,37 @@ namespace gaseous_server.Classes.Metadata switch (cacheStatus) { case Storage.CacheStatus.Current: - metadata = Storage.GetCacheValue(SourceType, metadata, "Id", Id); + if (idType == IdType.Long) + { + metadata = Storage.GetCacheValue(SourceType, metadata, "Id", (long)Id); + } + else + { + metadata = Storage.GetCacheValue(SourceType, metadata, "Slug", (string)Id); + } break; case Storage.CacheStatus.Expired: - metadata = GetMetadataFromServer(SourceType, Id).Result; + if (idType == IdType.Long) + { + metadata = GetMetadataFromServer(SourceType, (long)Id).Result; + } + else + { + metadata = GetMetadataFromServer(SourceType, (string)Id).Result; + } Storage.NewCacheValue(SourceType, metadata, true); break; case Storage.CacheStatus.NotPresent: - metadata = GetMetadataFromServer(SourceType, Id).Result; + if (idType == IdType.Long) + { + metadata = GetMetadataFromServer(SourceType, (long)Id).Result; + } + else + { + metadata = GetMetadataFromServer(SourceType, (string)Id).Result; + } Storage.NewCacheValue(SourceType, metadata, false); break; } @@ -117,6 +154,12 @@ namespace gaseous_server.Classes.Metadata return metadata; } + private enum IdType + { + Long, + String + } + private static async Task GetMetadataFromServer(HasheousClient.Models.MetadataModel.MetadataSources SourceType, long Id) where T : class { // get T type as string @@ -135,6 +178,24 @@ namespace gaseous_server.Classes.Metadata return results.FirstOrDefault(); } + private static async Task GetMetadataFromServer(HasheousClient.Models.MetadataModel.MetadataSources SourceType, string Id) where T : class + { + // get T type as string + string type = typeof(T).Name; + + // get metadata from the server + Communications comms = new Communications(); + var results = await comms.APIComm(SourceType, (Communications.MetadataEndpoint)Enum.Parse(typeof(Communications.MetadataEndpoint), type, true), Id); + + // check for errors + if (results == null) + { + throw new InvalidMetadataId(SourceType, Id); + } + + return results.FirstOrDefault(); + } + #endregion } } \ No newline at end of file diff --git a/gaseous-server/Classes/Metadata/Platforms.cs b/gaseous-server/Classes/Metadata/Platforms.cs index 484430b..9a76f16 100644 --- a/gaseous-server/Classes/Metadata/Platforms.cs +++ b/gaseous-server/Classes/Metadata/Platforms.cs @@ -27,9 +27,19 @@ namespace gaseous_server.Classes.Metadata } } - public static Platform GetPlatform(string Slug, bool forceRefresh = false, bool GetImages = false) + public static Platform GetPlatform(string Slug) { - throw new NotImplementedException(); + // get platform id from slug - query Platform table + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string query = "SELECT Id FROM Platform WHERE slug = @slug AND SourceId = @sourceid;"; + DataTable result = db.ExecuteCMD(query, new Dictionary { { "@slug", Slug }, { "@sourceid", Communications.MetadataSource } }); + if (result.Rows.Count == 0) + { + throw new Metadata.InvalidMetadataId(Slug); + } + + long Id = (long)result.Rows[0]["Id"]; + return GetPlatform(Id); } private static void AddPlatformMapping(Platform platform) diff --git a/gaseous-server/Support/Database/MySQL/gaseous-1024.sql b/gaseous-server/Support/Database/MySQL/gaseous-1024.sql index eafa23d..0e75bd8 100644 --- a/gaseous-server/Support/Database/MySQL/gaseous-1024.sql +++ b/gaseous-server/Support/Database/MySQL/gaseous-1024.sql @@ -120,15 +120,26 @@ CREATE TABLE `MetadataMap` ( CREATE TABLE `MetadataMapBridge` ( `ParentMapId` bigint(20) NOT NULL, + `SignatureGameName` varchar(255) NOT NULL, + `SignaturePlatformId` bigint(20) NOT NULL, `MetadataSourceType` int(11) NOT NULL DEFAULT 0, - `MetadataSourceId` bigint(20) NOT NULL `Preferred` BOOLEAN NOT NULL DEFAULT 0, + `MetadataSourceId` bigint(20) NOT NULL, + `Preferred` BOOLEAN NOT NULL DEFAULT 0, + `ProcessedAtImport` BOOLEAN NOT NULL DEFAULT 0, PRIMARY KEY ( `ParentMapId`, `MetadataSourceType`, `MetadataSourceId` + ), + INDEX `idx_gamename` ( + `SignatureGameName`, + `SignaturePlatformId` ) ); +ALTER TABLE `Games_Roms` +ADD CONSTRAINT metadataMapId FOREIGN KEY (`MetadataMapId`) REFERENCES `MetadataMapBridge` (`ParentMapId`) ON DELETE CASCADE; + ALTER TABLE `Games_Roms` ADD COLUMN `MetadataMapId` BIGINT NOT NULL DEFAULT 0;