From 19345585957bfe2eeb942ec5781ade9b57d635b4 Mon Sep 17 00:00:00 2001 From: Michael Green <84688932+michael-j-green@users.noreply.github.com> Date: Sun, 8 Oct 2023 18:19:59 -0700 Subject: [PATCH] Create libraries based on external unmanaged directories (#147) * Library management is now complete * Library functions complete * Added default platform support --- gaseous-server/Classes/GameLibrary.cs | 176 +++++++++++ gaseous-server/Classes/ImportGames.cs | 296 ++++++++++-------- gaseous-server/Classes/Roms.cs | 23 +- .../Classes/SignatureIngestors/XML.cs | 3 +- .../Controllers/LibraryController.cs | 77 +++++ .../Controllers/SystemController.cs | 6 +- gaseous-server/Support/PlatformMap.json | 32 +- gaseous-server/Timer.cs | 2 +- gaseous-server/wwwroot/emulators/EmulatorJS | 2 +- .../wwwroot/pages/dialogs/librarydelete.html | 27 ++ .../wwwroot/pages/dialogs/librarynew.html | 91 ++++++ .../wwwroot/pages/dialogs/rominfo.html | 9 + gaseous-server/wwwroot/pages/settings.html | 1 + .../wwwroot/pages/settings/settings.html | 63 ++++ gaseous-tools/Common.cs | 6 + gaseous-tools/Config.cs | 17 +- gaseous-tools/Database/MySQL/gaseous-1004.sql | 11 + gaseous-tools/DatabaseMigration.cs | 23 ++ gaseous-tools/gaseous-tools.csproj | 2 + 19 files changed, 709 insertions(+), 158 deletions(-) create mode 100644 gaseous-server/Classes/GameLibrary.cs create mode 100644 gaseous-server/Controllers/LibraryController.cs create mode 100644 gaseous-server/wwwroot/pages/dialogs/librarydelete.html create mode 100644 gaseous-server/wwwroot/pages/dialogs/librarynew.html create mode 100644 gaseous-server/wwwroot/pages/settings/settings.html create mode 100644 gaseous-tools/Database/MySQL/gaseous-1004.sql diff --git a/gaseous-server/Classes/GameLibrary.cs b/gaseous-server/Classes/GameLibrary.cs new file mode 100644 index 0000000..e7d7489 --- /dev/null +++ b/gaseous-server/Classes/GameLibrary.cs @@ -0,0 +1,176 @@ +using System; +using System.Data; +using gaseous_server.Classes.Metadata; +using gaseous_tools; +using IGDB.Models; +using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow; + +namespace gaseous_server +{ + public static class GameLibrary + { + // exceptions + public class PathExists : Exception + { + public PathExists(string path) : base("The library path " + path + " already exists.") + {} + } + + public class PathNotFound : Exception + { + public PathNotFound(string path) : base("The path " + path + " does not exist.") + {} + } + + public class LibraryNotFound : Exception + { + public LibraryNotFound(int LibraryId) : base("Library id " + LibraryId + " does not exist.") + {} + } + + public class CannotDeleteDefaultLibrary : Exception + { + public CannotDeleteDefaultLibrary() : base("Unable to delete the default library.") + {} + } + + // code + public static LibraryItem GetDefaultLibrary + { + get + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT * FROM GameLibraries WHERE DefaultLibrary=1 LIMIT 1"; + DataTable data = db.ExecuteCMD(sql); + DataRow row = data.Rows[0]; + LibraryItem library = new LibraryItem((int)row["Id"], (string)row["Name"], (string)row["Path"], (long)row["DefaultPlatform"], Convert.ToBoolean((int)row["DefaultLibrary"])); + + return library; + } + } + + public static List GetLibraries + { + get + { + List libraryItems = new List(); + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT * FROM GameLibraries"; + DataTable data = db.ExecuteCMD(sql); + foreach (DataRow row in data.Rows) + { + LibraryItem library = new LibraryItem((int)row["Id"], (string)row["Name"], (string)row["Path"], (long)row["DefaultPlatform"], Convert.ToBoolean((int)row["DefaultLibrary"])); + libraryItems.Add(library); + } + + return libraryItems; + } + } + + public static LibraryItem AddLibrary(string Name, string Path, long DefaultPlatformId) + { + string PathName = Common.NormalizePath(Path); + + // check path isn't already in place + foreach (LibraryItem item in GetLibraries) + { + if (Common.NormalizePath(PathName) == Common.NormalizePath(item.Path)) + { + // already existing path! + throw new PathExists(PathName); + } + } + + if (!System.IO.Path.Exists(PathName)) + { + throw new PathNotFound(PathName); + } + + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "INSERT INTO GameLibraries (Name, Path, DefaultPlatform, DefaultLibrary) VALUES (@name, @path, @defaultplatform, 0); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("name", Name); + dbDict.Add("path", PathName); + dbDict.Add("defaultplatform", DefaultPlatformId); + DataTable data = db.ExecuteCMD(sql, dbDict); + + int newLibraryId = (int)(long)data.Rows[0][0]; + + return GetLibrary(newLibraryId); + } + + public static void DeleteLibrary(int LibraryId) + { + if (GetLibrary(LibraryId).IsDefaultLibrary == false) + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "DELETE FROM Games_Roms WHERE LibraryId=@id; DELETE FROM GameLibraries WHERE Id=@id;"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", LibraryId); + db.ExecuteCMD(sql, dbDict); + } + else + { + throw new CannotDeleteDefaultLibrary(); + } + } + + public static LibraryItem GetLibrary(int LibraryId) + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT * FROM GameLibraries WHERE Id=@id"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", LibraryId); + DataTable data = db.ExecuteCMD(sql, dbDict); + if (data.Rows.Count > 0) + { + DataRow row = data.Rows[0]; + LibraryItem library = new LibraryItem((int)row["Id"], (string)row["Name"], (string)row["Path"], (long)row["DefaultPlatform"], Convert.ToBoolean((int)row["DefaultLibrary"])); + return library; + } + else + { + throw new LibraryNotFound(LibraryId); + } + } + + public class LibraryItem + { + public LibraryItem(int Id, string Name, string Path, long DefaultPlatformId, bool IsDefaultLibrary) + { + _Id = Id; + _Name = Name; + _Path = Path; + _DefaultPlatformId = DefaultPlatformId; + _IsDefaultLibrary = IsDefaultLibrary; + } + + int _Id = 0; + string _Name = ""; + string _Path = ""; + long _DefaultPlatformId = 0; + bool _IsDefaultLibrary = false; + + public int Id => _Id; + public string Name => _Name; + public string Path => _Path; + public long DefaultPlatformId => _DefaultPlatformId; + public string? DefaultPlatformName + { + get + { + if (_DefaultPlatformId != 0) + { + Platform platform = Platforms.GetPlatform(_DefaultPlatformId); + return platform.Name; + } + else + { + return ""; + } + } + } + public bool IsDefaultLibrary => _IsDefaultLibrary; + } + } +} \ No newline at end of file diff --git a/gaseous-server/Classes/ImportGames.cs b/gaseous-server/Classes/ImportGames.cs index 6379011..c004f2e 100644 --- a/gaseous-server/Classes/ImportGames.cs +++ b/gaseous-server/Classes/ImportGames.cs @@ -4,6 +4,7 @@ using System.IO.Compression; using System.Security.Policy; using System.Text.RegularExpressions; using System.Threading.Tasks; +using gaseous_server.Classes.Metadata; using gaseous_tools; using IGDB.Models; using MySqlX.XDevAPI; @@ -98,7 +99,7 @@ namespace gaseous_server.Classes IGDB.Models.Game determinedGame = SearchForGame(discoveredSignature.Game.Name, discoveredSignature.Flags.IGDBPlatformId); // add to database - StoreROM(hash, determinedGame, determinedPlatform, discoveredSignature, GameFileImportPath); + StoreROM(GameLibrary.GetDefaultLibrary, hash, determinedGame, determinedPlatform, discoveredSignature, GameFileImportPath); } } else @@ -391,7 +392,7 @@ namespace gaseous_server.Classes return SearchCandidates; } - public static long StoreROM(Common.hashObject hash, IGDB.Models.Game determinedGame, IGDB.Models.Platform determinedPlatform, Models.Signatures_Games discoveredSignature, string GameFileImportPath, long UpdateId = 0) + public static long StoreROM(GameLibrary.LibraryItem library, Common.hashObject hash, IGDB.Models.Game determinedGame, IGDB.Models.Platform determinedPlatform, Models.Signatures_Games discoveredSignature, string GameFileImportPath, long UpdateId = 0) { Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); @@ -401,7 +402,7 @@ 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) VALUES (@platformid, @gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @Attributes, @romtype, @romtypemedia, @medialabel, @path, @metadatasource, @metadatagamename, @metadataversion); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + 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 = "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;"; @@ -418,6 +419,7 @@ namespace gaseous_server.Classes dbDict.Add("metadatasource", discoveredSignature.Rom.SignatureSource); dbDict.Add("metadatagamename", discoveredSignature.Game.Name); dbDict.Add("metadataversion", 2); + dbDict.Add("libraryid", library.Id); if (discoveredSignature.Rom.Attributes != null) { @@ -450,7 +452,10 @@ namespace gaseous_server.Classes } // move to destination - MoveGameFile(romId); + if (library.IsDefaultLibrary == true) + { + MoveGameFile(romId); + } return romId; } @@ -474,7 +479,7 @@ namespace gaseous_server.Classes { gameSlug = game.Slug; } - string DestinationPath = Path.Combine(Config.LibraryConfiguration.LibraryDataDirectory, gameSlug, platformSlug); + string DestinationPath = Path.Combine(GameLibrary.GetDefaultLibrary.Path, gameSlug, platformSlug); if (!Directory.Exists(DestinationPath)) { Directory.CreateDirectory(DestinationPath); @@ -532,12 +537,16 @@ namespace gaseous_server.Classes public static void OrganiseLibrary() { - Logging.Log(Logging.LogType.Information, "Organise Library", "Starting library organisation"); + Logging.Log(Logging.LogType.Information, "Organise Library", "Starting default library organisation"); + + GameLibrary.LibraryItem library = GameLibrary.GetDefaultLibrary; // move rom files to their new location Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "SELECT * FROM Games_Roms"; - DataTable romDT = db.ExecuteCMD(sql); + string sql = "SELECT * FROM Games_Roms WHERE LibraryId = @libraryid"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("libraryid", library.Id); + DataTable romDT = db.ExecuteCMD(sql, dbDict); if (romDT.Rows.Count > 0) { @@ -550,9 +559,9 @@ namespace gaseous_server.Classes } // clean up empty directories - DeleteOrphanedDirectories(Config.LibraryConfiguration.LibraryDataDirectory); + DeleteOrphanedDirectories(GameLibrary.GetDefaultLibrary.Path); - Logging.Log(Logging.LogType.Information, "Organise Library", "Finsihed library organisation"); + Logging.Log(Logging.LogType.Information, "Organise Library", "Finsihed default library organisation"); } private static void DeleteOrphanedDirectories(string startLocation) @@ -570,154 +579,179 @@ namespace gaseous_server.Classes public static void LibraryScan() { - Logging.Log(Logging.LogType.Information, "Library Scan", "Starting library scan"); - - Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - - Logging.Log(Logging.LogType.Information, "Library Scan", "Looking for duplicate library files to clean up"); - string duplicateSql = "DELETE r1 FROM Games_Roms r1 INNER JOIN Games_Roms r2 WHERE r1.Id > r2.Id AND r1.MD5 = r2.MD5;"; - db.ExecuteCMD(duplicateSql); - - string sql = "SELECT * FROM Games_Roms ORDER BY `name`"; - DataTable dtRoms = db.ExecuteCMD(sql); - - // clean out database entries in the import folder - if (dtRoms.Rows.Count > 0) + foreach (GameLibrary.LibraryItem library in GameLibrary.GetLibraries) { - for (var i = 0; i < dtRoms.Rows.Count; i++) + Logging.Log(Logging.LogType.Information, "Library Scan", "Starting library scan. Library " + library.Name); + + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + + Logging.Log(Logging.LogType.Information, "Library Scan", "Looking for duplicate library files to clean up"); + string duplicateSql = "DELETE r1 FROM Games_Roms r1 INNER JOIN Games_Roms r2 WHERE r1.Id > r2.Id AND r1.MD5 = r2.MD5 AND r1.LibraryId=@libraryid AND r2.LibraryId=@libraryid;"; + Dictionary dupDict = new Dictionary(); + dupDict.Add("libraryid", library.Id); + db.ExecuteCMD(duplicateSql, dupDict); + + string sql = "SELECT * FROM Games_Roms WHERE LibraryId=@libraryid ORDER BY `name`"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("libraryid", library.Id); + DataTable dtRoms = db.ExecuteCMD(sql, dbDict); + + // clean out database entries in the import folder + if (dtRoms.Rows.Count > 0) { - long romId = (long)dtRoms.Rows[i]["Id"]; - string romPath = (string)dtRoms.Rows[i]["Path"]; - - if (!romPath.StartsWith(Config.LibraryConfiguration.LibraryDataDirectory)) - { - Logging.Log(Logging.LogType.Information, "Library Scan", " Deleting database entry for files with incorrect directory " + romPath); - string deleteSql = "DELETE FROM Games_Roms WHERE Id=@id"; - Dictionary deleteDict = new Dictionary(); - deleteDict.Add("Id", romId); - db.ExecuteCMD(deleteSql, deleteDict); - } - } - } - - sql = "SELECT * FROM Games_Roms ORDER BY `name`"; - dtRoms = db.ExecuteCMD(sql); - - // search for files in the library that aren't in the database - Logging.Log(Logging.LogType.Information, "Library Scan", "Looking for orphaned library files to add"); - string[] LibraryFiles = Directory.GetFiles(Config.LibraryConfiguration.LibraryDataDirectory, "*.*", SearchOption.AllDirectories); - foreach (string LibraryFile in LibraryFiles) - { - if (!Common.SkippableFiles.Contains(Path.GetFileName(LibraryFile), StringComparer.OrdinalIgnoreCase)) - { - Common.hashObject LibraryFileHash = new Common.hashObject(LibraryFile); - - // check if file is in database - bool romFound = false; for (var i = 0; i < dtRoms.Rows.Count; i++) { long romId = (long)dtRoms.Rows[i]["Id"]; string romPath = (string)dtRoms.Rows[i]["Path"]; - string romMd5 = (string)dtRoms.Rows[i]["MD5"]; - if ((LibraryFile == romPath) || (LibraryFileHash.md5hash == romMd5)) + if (!romPath.StartsWith(library.Path)) { - romFound = true; - break; + Logging.Log(Logging.LogType.Information, "Library Scan", " Deleting database entry for files with incorrect directory " + romPath); + string deleteSql = "DELETE FROM Games_Roms WHERE Id=@id AND LibraryId=@libraryid"; + Dictionary deleteDict = new Dictionary(); + deleteDict.Add("Id", romId); + deleteDict.Add("libraryid", library.Id); + db.ExecuteCMD(deleteSql, deleteDict); } } - - if (romFound == false) - { - // file is not in database - process it - Common.hashObject hash = new Common.hashObject(LibraryFile); - FileInfo fi = new FileInfo(LibraryFile); - - Models.Signatures_Games sig = GetFileSignature(hash, fi, LibraryFile); - - Logging.Log(Logging.LogType.Information, "Library Scan", " Orphaned file found in library: " + LibraryFile); - - // get discovered platform - IGDB.Models.Platform determinedPlatform = Metadata.Platforms.GetPlatform(sig.Flags.IGDBPlatformId); - if (determinedPlatform == null) - { - determinedPlatform = new IGDB.Models.Platform(); - } - - IGDB.Models.Game determinedGame = SearchForGame(sig.Game.Name, sig.Flags.IGDBPlatformId); - - StoreROM(hash, determinedGame, determinedPlatform, sig, LibraryFile); - } } - } - sql = "SELECT * FROM Games_Roms ORDER BY `name`"; - dtRoms = db.ExecuteCMD(sql); + sql = "SELECT * FROM Games_Roms ORDER BY `name`"; + dtRoms = db.ExecuteCMD(sql, dbDict); - // check all roms to see if their local file still exists - Logging.Log(Logging.LogType.Information, "Library Scan", "Checking library files exist on disk"); - if (dtRoms.Rows.Count > 0) - { - for (var i = 0; i < dtRoms.Rows.Count; i++) + // search for files in the library that aren't in the database + Logging.Log(Logging.LogType.Information, "Library Scan", "Looking for orphaned library files to add"); + string[] LibraryFiles = Directory.GetFiles(library.Path, "*.*", SearchOption.AllDirectories); + foreach (string LibraryFile in LibraryFiles) { - long romId = (long)dtRoms.Rows[i]["Id"]; - string romPath = (string)dtRoms.Rows[i]["Path"]; - gaseous_signature_parser.models.RomSignatureObject.RomSignatureObject.Game.Rom.SignatureSourceType romMetadataSource = (gaseous_signature_parser.models.RomSignatureObject.RomSignatureObject.Game.Rom.SignatureSourceType)(int)dtRoms.Rows[i]["MetadataSource"]; - - Logging.Log(Logging.LogType.Information, "Library Scan", " Processing ROM at path " + romPath); - - if (File.Exists(romPath)) + if (!Common.SkippableFiles.Contains(Path.GetFileName(LibraryFile), StringComparer.OrdinalIgnoreCase)) { - // file exists, so lets check to make sure the signature was matched, and update if a signature can be found - if ( - romMetadataSource == gaseous_signature_parser.models.RomSignatureObject.RomSignatureObject.Game.Rom.SignatureSourceType.None || - (int)dtRoms.Rows[i]["MetadataVersion"] == 1 - ) + Common.hashObject LibraryFileHash = new Common.hashObject(LibraryFile); + + // check if file is in database + bool romFound = false; + for (var i = 0; i < dtRoms.Rows.Count; i++) { - Common.hashObject hash = new Common.hashObject + long romId = (long)dtRoms.Rows[i]["Id"]; + string romPath = (string)dtRoms.Rows[i]["Path"]; + string romMd5 = (string)dtRoms.Rows[i]["MD5"]; + + if ((LibraryFile == romPath) || (LibraryFileHash.md5hash == romMd5)) { - md5hash = (string)dtRoms.Rows[i]["MD5"], - sha1hash = (string)dtRoms.Rows[i]["SHA1"] - }; - FileInfo fi = new FileInfo(romPath); - - Models.Signatures_Games sig = GetFileSignature(hash, fi, romPath); - if (sig.Rom.SignatureSource != gaseous_signature_parser.models.RomSignatureObject.RomSignatureObject.Game.Rom.SignatureSourceType.None) - { - Logging.Log(Logging.LogType.Information, "Library Scan", " Update signature found for " + romPath); - - // get discovered platform - IGDB.Models.Platform determinedPlatform = Metadata.Platforms.GetPlatform(sig.Flags.IGDBPlatformId); - if (determinedPlatform == null) - { - determinedPlatform = new IGDB.Models.Platform(); - } - - IGDB.Models.Game determinedGame = SearchForGame(sig.Game.Name, sig.Flags.IGDBPlatformId); - - StoreROM(hash, determinedGame, determinedPlatform, sig, romPath, romId); + romFound = true; + break; } } - if (romPath != ComputeROMPath(romId)) + if (romFound == false) { - MoveGameFile(romId); + // file is not in database - process it + Common.hashObject hash = new Common.hashObject(LibraryFile); + FileInfo fi = new FileInfo(LibraryFile); + + Models.Signatures_Games sig = GetFileSignature(hash, fi, LibraryFile); + + Logging.Log(Logging.LogType.Information, "Library Scan", " Orphaned file found in library: " + LibraryFile); + + // get discovered platform + IGDB.Models.Platform determinedPlatform = Metadata.Platforms.GetPlatform(sig.Flags.IGDBPlatformId); + + IGDB.Models.Game determinedGame = new Game(); + if (determinedPlatform == null) + { + if (library.DefaultPlatformId == 0) + { + determinedPlatform = new IGDB.Models.Platform(); + determinedGame = SearchForGame(sig.Game.Name, sig.Flags.IGDBPlatformId); + } + else + { + determinedPlatform = Platforms.GetPlatform(library.DefaultPlatformId); + determinedGame = SearchForGame(sig.Game.Name, library.DefaultPlatformId); + } + } + else + { + determinedGame = SearchForGame(sig.Game.Name, (long)determinedPlatform.Id); + } + + StoreROM(library, hash, determinedGame, determinedPlatform, sig, LibraryFile); } } - else - { - // file doesn't exist where it's supposed to be! delete it from the db - Logging.Log(Logging.LogType.Warning, "Library Scan", " Deleting orphaned database entry for " + romPath); + } - string deleteSql = "DELETE FROM Games_Roms WHERE Id = @id"; - Dictionary deleteDict = new Dictionary(); - deleteDict.Add("id", romId); - db.ExecuteCMD(deleteSql, deleteDict); + sql = "SELECT * FROM Games_Roms WHERE LibraryId=@libraryid ORDER BY `name`"; + dtRoms = db.ExecuteCMD(sql, dbDict); + + // check all roms to see if their local file still exists + Logging.Log(Logging.LogType.Information, "Library Scan", "Checking library files exist on disk"); + if (dtRoms.Rows.Count > 0) + { + for (var i = 0; i < dtRoms.Rows.Count; i++) + { + long romId = (long)dtRoms.Rows[i]["Id"]; + string romPath = (string)dtRoms.Rows[i]["Path"]; + gaseous_signature_parser.models.RomSignatureObject.RomSignatureObject.Game.Rom.SignatureSourceType romMetadataSource = (gaseous_signature_parser.models.RomSignatureObject.RomSignatureObject.Game.Rom.SignatureSourceType)(int)dtRoms.Rows[i]["MetadataSource"]; + + Logging.Log(Logging.LogType.Information, "Library Scan", " Processing ROM at path " + romPath); + + if (File.Exists(romPath)) + { + // file exists, so lets check to make sure the signature was matched, and update if a signature can be found + if ( + romMetadataSource == gaseous_signature_parser.models.RomSignatureObject.RomSignatureObject.Game.Rom.SignatureSourceType.None || + (int)dtRoms.Rows[i]["MetadataVersion"] == 1 + ) + { + Common.hashObject hash = new Common.hashObject + { + md5hash = (string)dtRoms.Rows[i]["MD5"], + sha1hash = (string)dtRoms.Rows[i]["SHA1"] + }; + FileInfo fi = new FileInfo(romPath); + + Models.Signatures_Games sig = GetFileSignature(hash, fi, romPath); + if (sig.Rom.SignatureSource != gaseous_signature_parser.models.RomSignatureObject.RomSignatureObject.Game.Rom.SignatureSourceType.None) + { + Logging.Log(Logging.LogType.Information, "Library Scan", " Update signature found for " + romPath); + + // get discovered platform + IGDB.Models.Platform determinedPlatform = Metadata.Platforms.GetPlatform(sig.Flags.IGDBPlatformId); + if (determinedPlatform == null) + { + determinedPlatform = new IGDB.Models.Platform(); + } + + IGDB.Models.Game determinedGame = SearchForGame(sig.Game.Name, sig.Flags.IGDBPlatformId); + + StoreROM(library, hash, determinedGame, determinedPlatform, sig, romPath, romId); + } + } + + if (library.IsDefaultLibrary == true) + { + if (romPath != ComputeROMPath(romId)) + { + MoveGameFile(romId); + } + } + } + else + { + // file doesn't exist where it's supposed to be! delete it from the db + Logging.Log(Logging.LogType.Warning, "Library Scan", " Deleting orphaned database entry for " + romPath); + + string deleteSql = "DELETE FROM Games_Roms WHERE Id = @id AND LibraryId = @libraryid"; + Dictionary deleteDict = new Dictionary(); + deleteDict.Add("id", romId); + deleteDict.Add("libraryid", library.Id); + db.ExecuteCMD(deleteSql, deleteDict); + } } } - } - Logging.Log(Logging.LogType.Information, "Library Scan", "Library scan completed"); + Logging.Log(Logging.LogType.Information, "Library Scan", "Library scan completed"); + } } } } diff --git a/gaseous-server/Classes/Roms.cs b/gaseous-server/Classes/Roms.cs index 2f65b40..3d86466 100644 --- a/gaseous-server/Classes/Roms.cs +++ b/gaseous-server/Classes/Roms.cs @@ -81,16 +81,19 @@ namespace gaseous_server.Classes public static void DeleteRom(long RomId) { GameRomItem rom = GetRom(RomId); - if (File.Exists(rom.Path)) + if (rom.Library.IsDefaultLibrary == true) { - File.Delete(rom.Path); - } + if (File.Exists(rom.Path)) + { + File.Delete(rom.Path); + } - Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "DELETE FROM Games_Roms WHERE Id = @id"; - Dictionary dbDict = new Dictionary(); - dbDict.Add("id", RomId); - db.ExecuteCMD(sql, dbDict); + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "DELETE FROM Games_Roms WHERE Id = @id"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", RomId); + db.ExecuteCMD(sql, dbDict); + } } private static GameRomItem BuildRom(DataRow romDR) @@ -113,7 +116,8 @@ namespace gaseous_server.Classes MediaLabel = (string)romDR["medialabel"], Path = (string)romDR["path"], Source = (gaseous_signature_parser.models.RomSignatureObject.RomSignatureObject.Game.Rom.SignatureSourceType)(Int32)romDR["metadatasource"], - SignatureSourceGameTitle = (string)Common.ReturnValueIfNull(romDR["MetadataGameName"], "") + SignatureSourceGameTitle = (string)Common.ReturnValueIfNull(romDR["MetadataGameName"], ""), + Library = GameLibrary.GetLibrary((int)romDR["LibraryId"]) }; // check for a web emulator and update the romItem @@ -153,6 +157,7 @@ namespace gaseous_server.Classes public string? Path { get; set; } public gaseous_signature_parser.models.RomSignatureObject.RomSignatureObject.Game.Rom.SignatureSourceType Source { get; set; } public string? SignatureSourceGameTitle { get; set;} + public GameLibrary.LibraryItem Library { get; set; } } } } diff --git a/gaseous-server/Classes/SignatureIngestors/XML.cs b/gaseous-server/Classes/SignatureIngestors/XML.cs index 288e8f7..6074d2a 100644 --- a/gaseous-server/Classes/SignatureIngestors/XML.cs +++ b/gaseous-server/Classes/SignatureIngestors/XML.cs @@ -212,12 +212,13 @@ namespace gaseous_server.SignatureIngestors.XML 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) VALUES (@gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @attributes, @romtype, @romtypemedia, @medialabel, @metadatasource); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + 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); diff --git a/gaseous-server/Controllers/LibraryController.cs b/gaseous-server/Controllers/LibraryController.cs new file mode 100644 index 0000000..a804113 --- /dev/null +++ b/gaseous-server/Controllers/LibraryController.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.IO.Compression; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace gaseous_server.Controllers +{ + [ApiController] + [Route("api/v1/[controller]")] + public class LibraryController : Controller + { + [HttpGet] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public ActionResult GetLibraries() + { + return Ok(GameLibrary.GetLibraries); + } + + [HttpGet("{LibraryId}")] + [ProducesResponseType(typeof(GameLibrary.LibraryItem), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GetLibrary(int LibraryId) + { + try + { + return Ok(GameLibrary.GetLibrary(LibraryId)); + } + catch + { + return NotFound(); + } + } + + [HttpPost] + [ProducesResponseType(typeof(GameLibrary.LibraryItem), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public ActionResult AddLibrary(string Name, string Path, long DefaultPlatformId) + { + try + { + return Ok(GameLibrary.AddLibrary(Name, Path, DefaultPlatformId)); + } + catch (GameLibrary.PathExists exPE) + { + return Conflict("Path already used in another library"); + } + catch (GameLibrary.PathNotFound exPNF) + { + return NotFound("Path not found"); + } + } + + [HttpDelete("{LibraryId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult DelLibrary(int LibraryId) + { + try + { + GameLibrary.DeleteLibrary(LibraryId); + return Ok(); + } + catch (GameLibrary.CannotDeleteDefaultLibrary exCDDL) + { + return BadRequest(exCDDL.ToString()); + } + catch (GameLibrary.LibraryNotFound exLNF) + { + return NotFound(exLNF.ToString()); + } + } + } +} \ No newline at end of file diff --git a/gaseous-server/Controllers/SystemController.cs b/gaseous-server/Controllers/SystemController.cs index df11c92..ee5a53f 100644 --- a/gaseous-server/Controllers/SystemController.cs +++ b/gaseous-server/Controllers/SystemController.cs @@ -24,8 +24,10 @@ namespace gaseous_server.Controllers // disk size List Disks = new List(); - //Disks.Add(GetDisk(gaseous_tools.Config.ConfigurationPath)); - Disks.Add(GetDisk(gaseous_tools.Config.LibraryConfiguration.LibraryRootDirectory)); + foreach (GameLibrary.LibraryItem libraryItem in GameLibrary.GetLibraries) + { + Disks.Add(GetDisk(libraryItem.Path)); + } ReturnValue.Paths = Disks; // database size diff --git a/gaseous-server/Support/PlatformMap.json b/gaseous-server/Support/PlatformMap.json index 48627de..e920c24 100644 --- a/gaseous-server/Support/PlatformMap.json +++ b/gaseous-server/Support/PlatformMap.json @@ -120,8 +120,20 @@ }, "retroPieDirectoryName": "amiga", "webEmulator": { - "type": "", - "core": "" + "type": "EmulatorJS", + "core": "amiga", + "availableWebEmulators": [ + { + "emulatorType": "EmulatorJS", + "availableWebEmulatorCores": [ + { + "core": "amiga", + "alternateCoreName": "puae", + "default": true + } + ] + } + ] }, "bios": [ { @@ -570,8 +582,20 @@ }, "retroPieDirectoryName": "c64", "webEmulator": { - "type": "", - "core": "" + "type": "EmulatorJS", + "core": "vice_x64", + "availableWebEmulators": [ + { + "emulatorType": "EmulatorJS", + "availableWebEmulatorCores": [ + { + "core": "c64", + "alternateCoreName": "vice_x64", + "default": true + } + ] + } + ] }, "bios": [ { diff --git a/gaseous-server/Timer.cs b/gaseous-server/Timer.cs index 8adc586..48a262c 100644 --- a/gaseous-server/Timer.cs +++ b/gaseous-server/Timer.cs @@ -80,7 +80,7 @@ namespace gaseous_server qi.Blocks.Contains(ProcessQueue.QueueItemType.All) ) { - Console.WriteLine(queueItem.ItemType.ToString() + " is blocked by " + qi.ItemType.ToString()); + //Console.WriteLine(queueItem.ItemType.ToString() + " is blocked by " + qi.ItemType.ToString()); return true; } } diff --git a/gaseous-server/wwwroot/emulators/EmulatorJS b/gaseous-server/wwwroot/emulators/EmulatorJS index 23dd448..921f7a0 160000 --- a/gaseous-server/wwwroot/emulators/EmulatorJS +++ b/gaseous-server/wwwroot/emulators/EmulatorJS @@ -1 +1 @@ -Subproject commit 23dd44825f7e66f584be788cee75e62366ad1a57 +Subproject commit 921f7a01c63b42fa40e79a3164cf80df6761f695 diff --git a/gaseous-server/wwwroot/pages/dialogs/librarydelete.html b/gaseous-server/wwwroot/pages/dialogs/librarydelete.html new file mode 100644 index 0000000..ff1e4eb --- /dev/null +++ b/gaseous-server/wwwroot/pages/dialogs/librarydelete.html @@ -0,0 +1,27 @@ +

Are you sure you want to delete this library?

+

Warning: This cannot be undone!

+
+
+ +
+
+ +
+
+ + \ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/dialogs/librarynew.html b/gaseous-server/wwwroot/pages/dialogs/librarynew.html new file mode 100644 index 0000000..f6016bc --- /dev/null +++ b/gaseous-server/wwwroot/pages/dialogs/librarynew.html @@ -0,0 +1,91 @@ +
+ New Library +
+ +
+ + + + + + + + + + + + + +
Name
Default Platform
Path
+ +
+
+ +
+
+ +
+
+
+ + \ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/dialogs/rominfo.html b/gaseous-server/wwwroot/pages/dialogs/rominfo.html index 3224387..a1c8a07 100644 --- a/gaseous-server/wwwroot/pages/dialogs/rominfo.html +++ b/gaseous-server/wwwroot/pages/dialogs/rominfo.html @@ -7,6 +7,10 @@