diff --git a/gaseous-server/Classes/FileSignature.cs b/gaseous-server/Classes/FileSignature.cs index d4d7673..e080765 100644 --- a/gaseous-server/Classes/FileSignature.cs +++ b/gaseous-server/Classes/FileSignature.cs @@ -1,7 +1,9 @@ using System.IO.Compression; using HasheousClient.Models; +using SevenZip; using SharpCompress.Archives; using SharpCompress.Archives.Rar; +using SharpCompress.Archives.Zip; using SharpCompress.Common; namespace gaseous_server.Classes @@ -10,6 +12,7 @@ namespace gaseous_server.Classes { public static gaseous_server.Models.Signatures_Games GetFileSignature(Common.hashObject hash, FileInfo fi, string GameFileImportPath) { + Logging.Log(Logging.LogType.Information, "Get Signature", "Getting signature for file: " + GameFileImportPath); gaseous_server.Models.Signatures_Games discoveredSignature = new gaseous_server.Models.Signatures_Games(); discoveredSignature = _GetFileSignature(hash, fi, GameFileImportPath, false); @@ -20,81 +23,152 @@ namespace gaseous_server.Classes { // file is a zip and less than 1 GiB // extract the zip file and search the contents - Logging.Log(Logging.LogType.Information, "Get Signature", "Decompressing " + GameFileImportPath + " to examine contents"); - string ExtractPath = Path.Combine(Config.LibraryConfiguration.LibraryTempDirectory, Path.GetRandomFileName()); + Logging.Log(Logging.LogType.Information, "Get Signature", "Decompressing " + GameFileImportPath + " to " + ExtractPath + " examine contents"); if (!Directory.Exists(ExtractPath)) { Directory.CreateDirectory(ExtractPath); } try { switch(ImportedFileExtension) { case ".zip": - ZipFile.ExtractToDirectory(GameFileImportPath, ExtractPath); + Logging.Log(Logging.LogType.Information, "Get Signature", "Decompressing using zip"); + try + { + using (var archive = SharpCompress.Archives.Zip.ZipArchive.Open(GameFileImportPath)) + { + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + { + Logging.Log(Logging.LogType.Information, "Get Signature", "Extracting file: " + entry.Key); + entry.WriteToDirectory(ExtractPath, new ExtractionOptions() + { + ExtractFullPath = true, + Overwrite = true + }); + } + } + } + catch (Exception zipEx) + { + Logging.Log(Logging.LogType.Warning, "Get Signature", "Unzip error", zipEx); + throw; + } break; case ".rar": - using (var archive = RarArchive.Open(GameFileImportPath)) + Logging.Log(Logging.LogType.Information, "Get Signature", "Decompressing using rar"); + try { - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + using (var archive = RarArchive.Open(GameFileImportPath)) { - entry.WriteToDirectory(ExtractPath, new ExtractionOptions() + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - ExtractFullPath = true, - Overwrite = true - }); + Logging.Log(Logging.LogType.Information, "Get Signature", "Extracting file: " + entry.Key); + entry.WriteToDirectory(ExtractPath, new ExtractionOptions() + { + ExtractFullPath = true, + Overwrite = true + }); + } } } + catch (Exception zipEx) + { + Logging.Log(Logging.LogType.Warning, "Get Signature", "Unrar error", zipEx); + throw; + } break; case ".7z": - using (var archive = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(GameFileImportPath)) + Logging.Log(Logging.LogType.Information, "Get Signature", "Decompressing using 7z"); + try { - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + using (var archive = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(GameFileImportPath)) { - entry.WriteToDirectory(ExtractPath, new ExtractionOptions() + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - ExtractFullPath = true, - Overwrite = true - }); + Logging.Log(Logging.LogType.Information, "Get Signature", "Extracting file: " + entry.Key); + entry.WriteToDirectory(ExtractPath, new ExtractionOptions() + { + ExtractFullPath = true, + Overwrite = true + }); + } } } + catch (Exception zipEx) + { + Logging.Log(Logging.LogType.Warning, "Get Signature", "7z error", zipEx); + throw; + } 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; foreach (string file in Directory.GetFiles(ExtractPath, "*.*", SearchOption.AllDirectories)) { - FileInfo zfi = new FileInfo(file); - Common.hashObject zhash = new Common.hashObject(file); - - gaseous_server.Models.Signatures_Games zDiscoveredSignature = _GetFileSignature(zhash, zfi, file, true); - zDiscoveredSignature.Rom.Name = Path.ChangeExtension(zDiscoveredSignature.Rom.Name, ImportedFileExtension); - - if (zDiscoveredSignature.Score > discoveredSignature.Score) + if (File.Exists(file)) { - if ( - zDiscoveredSignature.Rom.SignatureSource == gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType.MAMEArcade || - zDiscoveredSignature.Rom.SignatureSource == gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType.MAMEMess - ) - { - zDiscoveredSignature.Rom.Name = zDiscoveredSignature.Game.Description + ImportedFileExtension; - } - zDiscoveredSignature.Rom.Crc = discoveredSignature.Rom.Crc; - zDiscoveredSignature.Rom.Md5 = discoveredSignature.Rom.Md5; - zDiscoveredSignature.Rom.Sha1 = discoveredSignature.Rom.Sha1; - zDiscoveredSignature.Rom.Size = discoveredSignature.Rom.Size; - discoveredSignature = zDiscoveredSignature; + 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); - break; + 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, file, true); + zDiscoveredSignature.Rom.Name = Path.ChangeExtension(zDiscoveredSignature.Rom.Name, ImportedFileExtension); + + 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.MAMEMess + ) + { + zDiscoveredSignature.Rom.Name = zDiscoveredSignature.Game.Description + ImportedFileExtension; + } + zDiscoveredSignature.Rom.Crc = discoveredSignature.Rom.Crc; + zDiscoveredSignature.Rom.Md5 = discoveredSignature.Rom.Md5; + zDiscoveredSignature.Rom.Sha1 = discoveredSignature.Rom.Sha1; + zDiscoveredSignature.Rom.Size = discoveredSignature.Rom.Size; + discoveredSignature = zDiscoveredSignature; + + signatureFound = true; + } + } + } } } + + discoveredSignature.Rom.Attributes.Add(new KeyValuePair( + "ZipContents", Newtonsoft.Json.JsonConvert.SerializeObject(archiveFiles) + )); } catch (Exception ex) { - Logging.Log(Logging.LogType.Critical, "Get Signature", "Error processing zip file: " + GameFileImportPath, ex); + Logging.Log(Logging.LogType.Critical, "Get Signature", "Error processing compressed file: " + GameFileImportPath, ex); } - if (Directory.Exists(ExtractPath)) { Directory.Delete(ExtractPath, true); } + if (Directory.Exists(ExtractPath)) + { + Logging.Log(Logging.LogType.Information, "Get Signature", "Deleting temporary decompress folder: " + ExtractPath); + + Directory.Delete(ExtractPath, true); + } } return discoveredSignature; @@ -110,6 +184,7 @@ namespace gaseous_server.Classes if (dbSignature != null) { // local signature found + Logging.Log(Logging.LogType.Information, "Import Game", "Signature found in local database for game: " + dbSignature.Game.Name); discoveredSignature = dbSignature; } else @@ -120,12 +195,16 @@ namespace gaseous_server.Classes if (dbSignature != null) { // signature retrieved from Hasheous + Logging.Log(Logging.LogType.Information, "Import Game", "Signature retrieved from Hasheous for game: " + dbSignature.Game.Name); + discoveredSignature = dbSignature; } else { // construct a signature from file data dbSignature = _GetFileSignatureFromFileData(hash, fi, GameFileImportPath); + Logging.Log(Logging.LogType.Information, "Import Game", "Signature generated from provided file for game: " + dbSignature.Game.Name); + discoveredSignature = dbSignature; } } @@ -282,5 +361,14 @@ namespace gaseous_server.Classes return discoveredSignature; } } + + public class ArchiveData + { + public string FileName { get; set; } + public string FilePath { get; set; } + public long Size { get; set; } + public string MD5 { get; set; } + public string SHA1 { get; set; } + } } } \ No newline at end of file diff --git a/gaseous-server/Classes/ImportGames.cs b/gaseous-server/Classes/ImportGames.cs index b4fd70e..1314494 100644 --- a/gaseous-server/Classes/ImportGames.cs +++ b/gaseous-server/Classes/ImportGames.cs @@ -16,44 +16,37 @@ using HasheousClient.Models; namespace gaseous_server.Classes { - public class ImportGames : QueueItemStatus + public class ImportGame : QueueItemStatus { - public ImportGames(string ImportPath) - { - if (Directory.Exists(ImportPath)) + public void ProcessDirectory(string ImportPath) + { + if (Directory.Exists(ImportPath)) { - string[] importContents_Files = Directory.GetFiles(ImportPath); - string[] importContents_Directories = Directory.GetDirectories(ImportPath); + 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 int importCount = 1; - foreach (string importContent in importContents_Files) { - SetStatus(importCount, importContents_Files.Length, "Importing file: " + importContent); + foreach (string importContent in importContents) { + SetStatus(importCount, importContents.Length, "Importing file: " + importContent); - ImportGame.ImportGameFile(importContent, null); + ImportGameFile(importContent, null); importCount += 1; } ClearStatus(); - // import sub directories - foreach (string importDir in importContents_Directories) { - Classes.ImportGames importGames = new Classes.ImportGames(importDir); - } + DeleteOrphanedDirectories(ImportPath); } else { Logging.Log(Logging.LogType.Critical, "Import Games", "The import directory " + ImportPath + " does not exist."); throw new DirectoryNotFoundException("Invalid path: " + ImportPath); } - } + } - - } - - public class ImportGame : QueueItemStatus - { - public static void ImportGameFile(string GameFileImportPath, IGDB.Models.Platform? OverridePlatform) + public void ImportGameFile(string GameFileImportPath, IGDB.Models.Platform? OverridePlatform) { Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); string sql = ""; @@ -443,7 +436,7 @@ namespace gaseous_server.Classes } } - public static void OrganiseLibrary() + public void OrganiseLibrary() { Logging.Log(Logging.LogType.Information, "Organise Library", "Starting default library organisation"); @@ -477,8 +470,12 @@ namespace gaseous_server.Classes foreach (var directory in Directory.GetDirectories(startLocation)) { DeleteOrphanedDirectories(directory); - if (Directory.GetFiles(directory).Length == 0 && - Directory.GetDirectories(directory).Length == 0) + + string[] files = Directory.GetFiles(directory); + string[] directories = Directory.GetDirectories(directory); + + if (files.Length == 0 && + directories.Length == 0) { Directory.Delete(directory, false); } @@ -621,16 +618,16 @@ namespace gaseous_server.Classes if (romFound == false) { // 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); gaseous_server.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(); try { @@ -687,8 +684,13 @@ namespace gaseous_server.Classes { if (romPath != ComputeROMPath(romId)) { + Logging.Log(Logging.LogType.Information, "Library Scan", "ROM at path " + romPath + " found, but needs to be moved"); MoveGameFile(romId); } + else + { + Logging.Log(Logging.LogType.Information, "Library Scan", "ROM at path " + romPath + " found"); + } } } else diff --git a/gaseous-server/Classes/Metadata/Communications.cs b/gaseous-server/Classes/Metadata/Communications.cs index 285ec8f..d3adc16 100644 --- a/gaseous-server/Classes/Metadata/Communications.cs +++ b/gaseous-server/Classes/Metadata/Communications.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Data; using System.Drawing; using System.Net; using Humanizer; @@ -422,6 +423,56 @@ namespace gaseous_server.Classes.Metadata return returnPath; } + public static T? GetSearchCache(string SearchFields, string SearchString) + { + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT * FROM SearchCache WHERE SearchFields = @searchfields AND SearchString = @searchstring;"; + Dictionary dbDict = new Dictionary + { + { "searchfields", SearchFields }, + { "searchstring", SearchString } + }; + DataTable data = db.ExecuteCMD(sql, dbDict); + if (data.Rows.Count > 0) + { + // cache hit + string rawString = data.Rows[0]["Content"].ToString(); + T ReturnValue = Newtonsoft.Json.JsonConvert.DeserializeObject(rawString); + if (ReturnValue != null) + { + Logging.Log(Logging.LogType.Information, "Search Cache", "Found search result in cache. Search string: " + SearchString); + return ReturnValue; + } + else + { + Logging.Log(Logging.LogType.Information, "Search Cache", "Search result not found in cache."); + return default; + } + } + else + { + // cache miss + Logging.Log(Logging.LogType.Information, "Search Cache", "Search result not found in cache."); + return default; + } + } + + public static void SetSearchCache(string SearchFields, string SearchString, T SearchResult) + { + Logging.Log(Logging.LogType.Information, "Search Cache", "Storing search results in cache. Search string: " + SearchString); + + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "INSERT INTO SearchCache (SearchFields, SearchString, Content, LastSearch) VALUES (@searchfields, @searchstring, @content, @lastsearch);"; + Dictionary dbDict = new Dictionary + { + { "searchfields", SearchFields }, + { "searchstring", SearchString }, + { "content", Newtonsoft.Json.JsonConvert.SerializeObject(SearchResult) }, + { "lastsearch", DateTime.UtcNow } + }; + db.ExecuteNonQuery(sql, dbDict); + } + /// /// See https://api-docs.igdb.com/?javascript#images for more information about the image url structure /// diff --git a/gaseous-server/Classes/Metadata/Games.cs b/gaseous-server/Classes/Metadata/Games.cs index 0ce44ff..35b8760 100644 --- a/gaseous-server/Classes/Metadata/Games.cs +++ b/gaseous-server/Classes/Metadata/Games.cs @@ -477,16 +477,26 @@ namespace gaseous_server.Classes.Metadata break; } + // check search cache + List? games = Communications.GetSearchCache>(searchFields, searchBody); - // get Game metadata - Communications comms = new Communications(); - Game[]? results = new Game[0]; - if (allowSearch == true) - { - results = await comms.APIComm(IGDBClient.Endpoints.Games, searchFields, searchBody); + if (games == null) + { + // cache miss + // get Game metadata + Communications comms = new Communications(); + Game[]? results = new Game[0]; + if (allowSearch == true) + { + results = await comms.APIComm(IGDBClient.Endpoints.Games, searchFields, searchBody); + } + + return results; + } + else + { + return games.ToArray(); } - - return results; } public enum SearchType diff --git a/gaseous-server/Controllers/V1.0/RomsController.cs b/gaseous-server/Controllers/V1.0/RomsController.cs index 167604e..792df54 100644 --- a/gaseous-server/Controllers/V1.0/RomsController.cs +++ b/gaseous-server/Controllers/V1.0/RomsController.cs @@ -77,7 +77,8 @@ namespace gaseous_server.Controllers // Process uploaded files foreach (Dictionary UploadedFile in UploadedFiles) { - Classes.ImportGame.ImportGameFile((string)UploadedFile["fullpath"], OverridePlatform); + Classes.ImportGame uploadImport = new ImportGame(); + uploadImport.ImportGameFile((string)UploadedFile["fullpath"], OverridePlatform); } if (Directory.Exists(workPath)) diff --git a/gaseous-server/Controllers/V1.0/SearchController.cs b/gaseous-server/Controllers/V1.0/SearchController.cs index a3b506f..8db4e5b 100644 --- a/gaseous-server/Controllers/V1.0/SearchController.cs +++ b/gaseous-server/Controllers/V1.0/SearchController.cs @@ -1,13 +1,16 @@ using System; using System.Collections.Generic; +using System.Data; using System.Linq; using System.Threading.Tasks; using gaseous_server.Classes; using gaseous_server.Classes.Metadata; +using gaseous_server.Models; using IGDB; using IGDB.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using NuGet.Common; using static gaseous_server.Classes.Metadata.Games; @@ -37,36 +40,88 @@ namespace gaseous_server.Controllers string searchFields = "fields abbreviation,alternative_name,category,checksum,created_at,generation,name,platform_family,platform_logo,slug,summary,updated_at,url,versions,websites; "; searchBody += "where name ~ *\"" + SearchString + "\"*;"; - // get Platform metadata - Communications comms = new Communications(); - var results = await comms.APIComm(IGDBClient.Endpoints.Platforms, searchFields, searchBody); + List? searchCache = Communications.GetSearchCache>(searchFields, searchBody); - return results.ToList(); + if (searchCache == null) + { + // cache miss + // get Platform metadata from data source + Communications comms = new Communications(); + var results = await comms.APIComm(IGDBClient.Endpoints.Platforms, searchFields, searchBody); + + Communications.SetSearchCache>(searchFields, searchBody, results.ToList()); + + return results.ToList(); + } + else + { + return searchCache; + } } [MapToApiVersion("1.0")] [MapToApiVersion("1.1")] [HttpGet] [Route("Game")] - [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] public async Task SearchGame(long PlatformId, string SearchString) { - List RetVal = await _SearchForGame(PlatformId, SearchString); + List RetVal = await _SearchForGame(PlatformId, SearchString); return Ok(RetVal); } - private static async Task> _SearchForGame(long PlatformId, string SearchString) + private static async Task> _SearchForGame(long PlatformId, string SearchString) { string searchBody = ""; - string searchFields = "fields cover.*,first_release_date,name,platforms,slug; "; + string searchFields = "fields cover,first_release_date,name,platforms,slug; "; searchBody += "search \"" + SearchString + "\";"; searchBody += "where platforms = (" + PlatformId + ");"; - // get Platform metadata - Communications comms = new Communications(); - var results = await comms.APIComm(IGDBClient.Endpoints.Games, searchFields, searchBody); + List? searchCache = Communications.GetSearchCache>(searchFields, searchBody); - return results.ToList(); + if (searchCache == null) + { + // cache miss + // get Game metadata from data source + Communications comms = new Communications(); + var results = await comms.APIComm(IGDBClient.Endpoints.Games, searchFields, searchBody); + + List games = new List(); + foreach (Game game in results.ToList()) + { + Storage.CacheStatus cacheStatus = Storage.GetCacheStatus("Game", (long)game.Id); + switch(cacheStatus) + { + case Storage.CacheStatus.NotPresent: + Storage.NewCacheValue(game, false); + break; + + case Storage.CacheStatus.Expired: + Storage.NewCacheValue(game, true); + break; + + } + + games.Add(new GaseousGame(game)); + } + + Communications.SetSearchCache>(searchFields, searchBody, games); + + return games; + } + else + { + // get full version of results from database + // this is a hacky workaround due to the readonly nature of IGDB.Model.Game IdentityOrValue fields + List gamesToReturn = new List(); + foreach (GaseousGame game in searchCache) + { + Game tempGame = Games.GetGame((long)game.Id, false, false, false); + gamesToReturn.Add(new GaseousGame(tempGame)); + } + + return gamesToReturn; + } } } } diff --git a/gaseous-server/Models/GaseousGame.cs b/gaseous-server/Models/GaseousGame.cs index e68e21d..9a97f6a 100644 --- a/gaseous-server/Models/GaseousGame.cs +++ b/gaseous-server/Models/GaseousGame.cs @@ -1,7 +1,7 @@ using System.Reflection; using gaseous_server.Classes; using gaseous_server.Classes.Metadata; -using IGDB; +using Swashbuckle.AspNetCore.SwaggerGen; namespace gaseous_server.Models { diff --git a/gaseous-server/ProcessQueue.cs b/gaseous-server/ProcessQueue.cs index 28659c9..8cfd7c2 100644 --- a/gaseous-server/ProcessQueue.cs +++ b/gaseous-server/ProcessQueue.cs @@ -144,10 +144,11 @@ namespace gaseous_server case QueueItemType.TitleIngestor: Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Title Ingestor"); - Classes.ImportGames importGames = new Classes.ImportGames(Config.LibraryConfiguration.LibraryImportDirectory) + Classes.ImportGame import = new ImportGame { CallingQueueItem = this }; + import.ProcessDirectory(Config.LibraryConfiguration.LibraryImportDirectory); // clean up Classes.ImportGame.DeleteOrphanedDirectories(Config.LibraryConfiguration.LibraryImportDirectory); @@ -170,7 +171,11 @@ namespace gaseous_server case QueueItemType.OrganiseLibrary: Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Library Organiser"); - Classes.ImportGame.OrganiseLibrary(); + Classes.ImportGame importLibraryOrg = new ImportGame + { + CallingQueueItem = this + }; + importLibraryOrg.OrganiseLibrary(); _SaveLastRunTime = true; @@ -178,11 +183,11 @@ namespace gaseous_server case QueueItemType.LibraryScan: Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Library Scanners"); - Classes.ImportGame import = new ImportGame + Classes.ImportGame libScan = new ImportGame { CallingQueueItem = this }; - import.LibraryScan(); + libScan.LibraryScan(); _SaveLastRunTime = true; diff --git a/gaseous-server/Support/Database/MySQL/gaseous-1012.sql b/gaseous-server/Support/Database/MySQL/gaseous-1012.sql new file mode 100644 index 0000000..ef0e58b --- /dev/null +++ b/gaseous-server/Support/Database/MySQL/gaseous-1012.sql @@ -0,0 +1,5 @@ +ALTER TABLE `Games_Roms` +ADD INDEX `id_IdAndLibraryId` (`Id` ASC, `LibraryId` ASC) VISIBLE; + +ALTER TABLE `ServerLogs` +ADD INDEX `idx_EventDate` (`EventTime` ASC) VISIBLE; \ No newline at end of file diff --git a/gaseous-server/Support/Database/MySQL/gaseous-1013.sql b/gaseous-server/Support/Database/MySQL/gaseous-1013.sql new file mode 100644 index 0000000..ee2e1eb --- /dev/null +++ b/gaseous-server/Support/Database/MySQL/gaseous-1013.sql @@ -0,0 +1,9 @@ +CREATE TABLE `SearchCache` ( + `SearchFields` varchar(384) NOT NULL, + `SearchString` varchar(128) NOT NULL, + `Content` longtext DEFAULT NULL, + `LastSearch` datetime DEFAULT NULL, + PRIMARY KEY (`SearchFields`,`SearchString`), + KEY `idx_SearchString` (`SearchFields`,`SearchString`), + KEY `idx_LastSearch` (`LastSearch`) +); \ No newline at end of file diff --git a/gaseous-server/gaseous-server.csproj b/gaseous-server/gaseous-server.csproj index 823a99e..41fcf66 100644 --- a/gaseous-server/gaseous-server.csproj +++ b/gaseous-server/gaseous-server.csproj @@ -56,6 +56,8 @@ + + @@ -89,5 +91,7 @@ + + diff --git a/gaseous-server/wwwroot/pages/dialogs/rominfo.html b/gaseous-server/wwwroot/pages/dialogs/rominfo.html index fd4d1e6..c79928f 100644 --- a/gaseous-server/wwwroot/pages/dialogs/rominfo.html +++ b/gaseous-server/wwwroot/pages/dialogs/rominfo.html @@ -1,6 +1,7 @@ 
General
-
Attributes
+ +
Title Match
@@ -53,6 +54,10 @@ + + @@ -120,7 +125,6 @@ ajaxCall('/api/v1.1/Games/' + gameId + '/roms/' + modalVariables, 'GET', function (result) { romData = result; - console.log(romData); document.getElementById('modal-heading').innerHTML = result.name; document.getElementById('rominfo_library').innerHTML = result.library.name; document.getElementById('rominfo_platform').innerHTML = result.platform; @@ -142,8 +146,7 @@ if (result.attributes.length > 0) { document.getElementById('properties_bodypanel_attributes').appendChild(BuildAttributesTable(result.attributes, result.source)); - } else { - document.getElementById('properties_toc_attributes').style.display = 'none'; + document.getElementById('properties_bodypanel_archive_content').appendChild(BuildArchiveTable(result.attributes, result.source)); } }); @@ -199,12 +202,9 @@ $('#properties_fixplatform').on('select2:select', function (e) { var platformData = e.params.data; - console.log(platformData); var gameValue = $('#properties_fixgame').select2('data'); if (gameValue) { - console.log(gameValue[0]); - setFixGameDropDown(); } }); @@ -264,23 +264,75 @@ aTable.style.width = '100%'; for (var i = 0; i < attributes.length; i++) { - var aRow = document.createElement('tr'); + if (attributes[i].key != "ZipContents") { + // show attributes button + document.getElementById('properties_toc_attributes').style.display = ''; + var aRow = document.createElement('tr'); - var aTitleCell = document.createElement('th'); - aTitleCell.width = "25%"; - if (sourceName == "TOSEC") { - aTitleCell.innerHTML = ConvertTOSECAttributeName(attributes[i].key); - } else { - aTitleCell.innerHTML = attributes[i].key; + var aTitleCell = document.createElement('th'); + aTitleCell.width = "25%"; + if (sourceName == "TOSEC") { + aTitleCell.innerHTML = ConvertTOSECAttributeName(attributes[i].key); + } else { + aTitleCell.innerHTML = attributes[i].key; + } + aRow.appendChild(aTitleCell); + + var aValueCell = document.createElement('td'); + aValueCell.width = "75%"; + aValueCell.innerHTML = attributes[i].value; + aRow.appendChild(aValueCell); + + aTable.appendChild(aRow); } - aRow.appendChild(aTitleCell); + } - var aValueCell = document.createElement('td'); - aValueCell.width = "75%"; - aValueCell.innerHTML = attributes[i].value; - aRow.appendChild(aValueCell); + return aTable; + } - aTable.appendChild(aRow); + function BuildArchiveTable(attributes, sourceName) { + for (var i = 0; i < attributes.length; i++) { + if (attributes[i].key == "ZipContents") { + var archiveContent = JSON.parse(attributes[i].value); + + // show archive button + document.getElementById('properties_toc_archive').style.display = ''; + + var aTable = document.createElement('table'); + aTable.className = 'romtable'; + aTable.setAttribute('cellspacing', 0); + aTable.style.width = '100%'; + + for (var r = 0; r < archiveContent.length; r++) { + var aBody = document.createElement('tbody'); + aBody.className = 'romrow'; + + var aRow = document.createElement('tr'); + + var aNameCell = document.createElement('th'); + aNameCell.className = 'romcell'; + aNameCell.innerHTML = archiveContent[r].FilePath + '/' + archiveContent[r].FileName; + aRow.appendChild(aNameCell); + + var aSizeCell = document.createElement('td'); + aSizeCell.className = 'romcell'; + aSizeCell.innerHTML = formatBytes(archiveContent[r].Size); + aRow.appendChild(aSizeCell); + + aBody.appendChild(aRow); + + var hRow = document.createElement('tr'); + + var aHashCell = document.createElement('td'); + aHashCell.setAttribute('colspan', 2); + aHashCell.style.paddingLeft = '20px'; + aHashCell.innerHTML = "MD5: " + archiveContent[r].MD5 + "
SHA1: " + archiveContent[r].SHA1; + hRow.appendChild(aHashCell); + aBody.appendChild(hRow); + + aTable.appendChild(aBody); + } + } } return aTable; diff --git a/gaseous-server/wwwroot/scripts/main.js b/gaseous-server/wwwroot/scripts/main.js index 7538ab6..707b66c 100644 --- a/gaseous-server/wwwroot/scripts/main.js +++ b/gaseous-server/wwwroot/scripts/main.js @@ -245,8 +245,6 @@ function intToRGB(i) { } function DropDownRenderGameOption(state) { - console.log(state); - if (state.loading) { return state; } @@ -260,7 +258,7 @@ function DropDownRenderGameOption(state) { if (state.cover) { response = $( - '' + '' ); } else { response = $(