Multiple Updates (#251)

* Added more error logging to zip expansion

* Added more logging

* More logging, and archive contents can now be seen in rom info

* Bug fixes and caching enhancements

* Import path now cleaned after import
This commit is contained in:
Michael Green
2024-01-07 01:07:10 +11:00
committed by GitHub
parent ce9ab91e5b
commit 7d5419d33c
13 changed files with 392 additions and 112 deletions

View File

@@ -1,7 +1,9 @@
using System.IO.Compression; using System.IO.Compression;
using HasheousClient.Models; using HasheousClient.Models;
using SevenZip;
using SharpCompress.Archives; using SharpCompress.Archives;
using SharpCompress.Archives.Rar; using SharpCompress.Archives.Rar;
using SharpCompress.Archives.Zip;
using SharpCompress.Common; using SharpCompress.Common;
namespace gaseous_server.Classes 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) 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(); gaseous_server.Models.Signatures_Games discoveredSignature = new gaseous_server.Models.Signatures_Games();
discoveredSignature = _GetFileSignature(hash, fi, GameFileImportPath, false); discoveredSignature = _GetFileSignature(hash, fi, GameFileImportPath, false);
@@ -20,81 +23,152 @@ namespace gaseous_server.Classes
{ {
// file is a zip and less than 1 GiB // file is a zip and less than 1 GiB
// extract the zip file and search the contents // 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()); 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); } if (!Directory.Exists(ExtractPath)) { Directory.CreateDirectory(ExtractPath); }
try try
{ {
switch(ImportedFileExtension) switch(ImportedFileExtension)
{ {
case ".zip": 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; break;
case ".rar": 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, Logging.Log(Logging.LogType.Information, "Get Signature", "Extracting file: " + entry.Key);
Overwrite = true entry.WriteToDirectory(ExtractPath, new ExtractionOptions()
}); {
ExtractFullPath = true,
Overwrite = true
});
}
} }
} }
catch (Exception zipEx)
{
Logging.Log(Logging.LogType.Warning, "Get Signature", "Unrar error", zipEx);
throw;
}
break; break;
case ".7z": 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, Logging.Log(Logging.LogType.Information, "Get Signature", "Extracting file: " + entry.Key);
Overwrite = true entry.WriteToDirectory(ExtractPath, new ExtractionOptions()
}); {
ExtractFullPath = true,
Overwrite = true
});
}
} }
} }
catch (Exception zipEx)
{
Logging.Log(Logging.LogType.Warning, "Get Signature", "7z error", zipEx);
throw;
}
break; break;
} }
Logging.Log(Logging.LogType.Information, "Get Signature", "Processing decompressed files for signature matches");
// loop through contents until we find the first signature match // loop through contents until we find the first signature match
List<ArchiveData> archiveFiles = new List<ArchiveData>();
bool signatureFound = false;
foreach (string file in Directory.GetFiles(ExtractPath, "*.*", SearchOption.AllDirectories)) foreach (string file in Directory.GetFiles(ExtractPath, "*.*", SearchOption.AllDirectories))
{ {
FileInfo zfi = new FileInfo(file); if (File.Exists(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 ( FileInfo zfi = new FileInfo(file);
zDiscoveredSignature.Rom.SignatureSource == gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType.MAMEArcade || Common.hashObject zhash = new Common.hashObject(file);
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;
break; 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, 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<string, object>(
"ZipContents", Newtonsoft.Json.JsonConvert.SerializeObject(archiveFiles)
));
} }
catch (Exception ex) 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; return discoveredSignature;
@@ -110,6 +184,7 @@ namespace gaseous_server.Classes
if (dbSignature != null) if (dbSignature != null)
{ {
// local signature found // local signature found
Logging.Log(Logging.LogType.Information, "Import Game", "Signature found in local database for game: " + dbSignature.Game.Name);
discoveredSignature = dbSignature; discoveredSignature = dbSignature;
} }
else else
@@ -120,12 +195,16 @@ namespace gaseous_server.Classes
if (dbSignature != null) if (dbSignature != null)
{ {
// signature retrieved from Hasheous // signature retrieved from Hasheous
Logging.Log(Logging.LogType.Information, "Import Game", "Signature retrieved from Hasheous for game: " + dbSignature.Game.Name);
discoveredSignature = dbSignature; discoveredSignature = dbSignature;
} }
else else
{ {
// construct a signature from file data // construct a signature from file data
dbSignature = _GetFileSignatureFromFileData(hash, fi, GameFileImportPath); dbSignature = _GetFileSignatureFromFileData(hash, fi, GameFileImportPath);
Logging.Log(Logging.LogType.Information, "Import Game", "Signature generated from provided file for game: " + dbSignature.Game.Name);
discoveredSignature = dbSignature; discoveredSignature = dbSignature;
} }
} }
@@ -282,5 +361,14 @@ namespace gaseous_server.Classes
return discoveredSignature; 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; }
}
} }
} }

View File

@@ -16,44 +16,37 @@ using HasheousClient.Models;
namespace gaseous_server.Classes namespace gaseous_server.Classes
{ {
public class ImportGames : QueueItemStatus public class ImportGame : QueueItemStatus
{ {
public ImportGames(string ImportPath) public void ProcessDirectory(string ImportPath)
{ {
if (Directory.Exists(ImportPath)) if (Directory.Exists(ImportPath))
{ {
string[] importContents_Files = Directory.GetFiles(ImportPath); string[] importContents = Directory.GetFiles(ImportPath, "*.*", SearchOption.AllDirectories);
string[] importContents_Directories = Directory.GetDirectories(ImportPath);
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; int importCount = 1;
foreach (string importContent in importContents_Files) { foreach (string importContent in importContents) {
SetStatus(importCount, importContents_Files.Length, "Importing file: " + importContent); SetStatus(importCount, importContents.Length, "Importing file: " + importContent);
ImportGame.ImportGameFile(importContent, null); ImportGameFile(importContent, null);
importCount += 1; importCount += 1;
} }
ClearStatus(); ClearStatus();
// import sub directories DeleteOrphanedDirectories(ImportPath);
foreach (string importDir in importContents_Directories) {
Classes.ImportGames importGames = new Classes.ImportGames(importDir);
}
} }
else else
{ {
Logging.Log(Logging.LogType.Critical, "Import Games", "The import directory " + ImportPath + " does not exist."); Logging.Log(Logging.LogType.Critical, "Import Games", "The import directory " + ImportPath + " does not exist.");
throw new DirectoryNotFoundException("Invalid path: " + ImportPath); throw new DirectoryNotFoundException("Invalid path: " + ImportPath);
} }
} }
public void ImportGameFile(string GameFileImportPath, IGDB.Models.Platform? OverridePlatform)
}
public class ImportGame : QueueItemStatus
{
public static void ImportGameFile(string GameFileImportPath, IGDB.Models.Platform? OverridePlatform)
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = ""; 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"); 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)) foreach (var directory in Directory.GetDirectories(startLocation))
{ {
DeleteOrphanedDirectories(directory); 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); Directory.Delete(directory, false);
} }
@@ -621,13 +618,13 @@ namespace gaseous_server.Classes
if (romFound == false) if (romFound == false)
{ {
// file is not in database - process it // 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); Common.hashObject hash = new Common.hashObject(LibraryFile);
FileInfo fi = new FileInfo(LibraryFile); FileInfo fi = new FileInfo(LibraryFile);
gaseous_server.Models.Signatures_Games sig = GetFileSignature(hash, fi, 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 // get discovered platform
IGDB.Models.Platform determinedPlatform = Metadata.Platforms.GetPlatform(sig.Flags.IGDBPlatformId); IGDB.Models.Platform determinedPlatform = Metadata.Platforms.GetPlatform(sig.Flags.IGDBPlatformId);
@@ -687,8 +684,13 @@ namespace gaseous_server.Classes
{ {
if (romPath != ComputeROMPath(romId)) if (romPath != ComputeROMPath(romId))
{ {
Logging.Log(Logging.LogType.Information, "Library Scan", "ROM at path " + romPath + " found, but needs to be moved");
MoveGameFile(romId); MoveGameFile(romId);
} }
else
{
Logging.Log(Logging.LogType.Information, "Library Scan", "ROM at path " + romPath + " found");
}
} }
} }
else else

View File

@@ -1,4 +1,5 @@
using System.ComponentModel; using System.ComponentModel;
using System.Data;
using System.Drawing; using System.Drawing;
using System.Net; using System.Net;
using Humanizer; using Humanizer;
@@ -422,6 +423,56 @@ namespace gaseous_server.Classes.Metadata
return returnPath; return returnPath;
} }
public static T? GetSearchCache<T>(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<string, object> dbDict = new Dictionary<string, object>
{
{ "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<T>(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<T>(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<string, object> dbDict = new Dictionary<string, object>
{
{ "searchfields", SearchFields },
{ "searchstring", SearchString },
{ "content", Newtonsoft.Json.JsonConvert.SerializeObject(SearchResult) },
{ "lastsearch", DateTime.UtcNow }
};
db.ExecuteNonQuery(sql, dbDict);
}
/// <summary> /// <summary>
/// See https://api-docs.igdb.com/?javascript#images for more information about the image url structure /// See https://api-docs.igdb.com/?javascript#images for more information about the image url structure
/// </summary> /// </summary>

View File

@@ -477,16 +477,26 @@ namespace gaseous_server.Classes.Metadata
break; break;
} }
// check search cache
List<Game>? games = Communications.GetSearchCache<List<Game>>(searchFields, searchBody);
// get Game metadata if (games == null)
Communications comms = new Communications();
Game[]? results = new Game[0];
if (allowSearch == true)
{ {
results = await comms.APIComm<Game>(IGDBClient.Endpoints.Games, searchFields, searchBody); // cache miss
} // get Game metadata
Communications comms = new Communications();
Game[]? results = new Game[0];
if (allowSearch == true)
{
results = await comms.APIComm<Game>(IGDBClient.Endpoints.Games, searchFields, searchBody);
}
return results; return results;
}
else
{
return games.ToArray();
}
} }
public enum SearchType public enum SearchType

View File

@@ -77,7 +77,8 @@ namespace gaseous_server.Controllers
// Process uploaded files // Process uploaded files
foreach (Dictionary<string, object> UploadedFile in UploadedFiles) foreach (Dictionary<string, object> 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)) if (Directory.Exists(workPath))

View File

@@ -1,13 +1,16 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using gaseous_server.Classes; using gaseous_server.Classes;
using gaseous_server.Classes.Metadata; using gaseous_server.Classes.Metadata;
using gaseous_server.Models;
using IGDB; using IGDB;
using IGDB.Models; using IGDB.Models;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NuGet.Common;
using static gaseous_server.Classes.Metadata.Games; 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; "; 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 + "\"*;"; searchBody += "where name ~ *\"" + SearchString + "\"*;";
// get Platform metadata List<Platform>? searchCache = Communications.GetSearchCache<List<Platform>>(searchFields, searchBody);
Communications comms = new Communications();
var results = await comms.APIComm<Platform>(IGDBClient.Endpoints.Platforms, 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<Platform>(IGDBClient.Endpoints.Platforms, searchFields, searchBody);
Communications.SetSearchCache<List<Platform>>(searchFields, searchBody, results.ToList());
return results.ToList();
}
else
{
return searchCache;
}
} }
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
[HttpGet] [HttpGet]
[Route("Game")] [Route("Game")]
[ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<GaseousGame>), StatusCodes.Status200OK)]
public async Task<ActionResult> SearchGame(long PlatformId, string SearchString) public async Task<ActionResult> SearchGame(long PlatformId, string SearchString)
{ {
List<Game> RetVal = await _SearchForGame(PlatformId, SearchString); List<GaseousGame> RetVal = await _SearchForGame(PlatformId, SearchString);
return Ok(RetVal); return Ok(RetVal);
} }
private static async Task<List<Game>> _SearchForGame(long PlatformId, string SearchString) private static async Task<List<GaseousGame>> _SearchForGame(long PlatformId, string SearchString)
{ {
string searchBody = ""; 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 += "search \"" + SearchString + "\";";
searchBody += "where platforms = (" + PlatformId + ");"; searchBody += "where platforms = (" + PlatformId + ");";
// get Platform metadata List<GaseousGame>? searchCache = Communications.GetSearchCache<List<GaseousGame>>(searchFields, searchBody);
Communications comms = new Communications();
var results = await comms.APIComm<Game>(IGDBClient.Endpoints.Games, 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<Game>(IGDBClient.Endpoints.Games, searchFields, searchBody);
List<GaseousGame> games = new List<GaseousGame>();
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<List<GaseousGame>>(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<GaseousGame> gamesToReturn = new List<GaseousGame>();
foreach (GaseousGame game in searchCache)
{
Game tempGame = Games.GetGame((long)game.Id, false, false, false);
gamesToReturn.Add(new GaseousGame(tempGame));
}
return gamesToReturn;
}
} }
} }
} }

View File

@@ -1,7 +1,7 @@
using System.Reflection; using System.Reflection;
using gaseous_server.Classes; using gaseous_server.Classes;
using gaseous_server.Classes.Metadata; using gaseous_server.Classes.Metadata;
using IGDB; using Swashbuckle.AspNetCore.SwaggerGen;
namespace gaseous_server.Models namespace gaseous_server.Models
{ {

View File

@@ -144,10 +144,11 @@ namespace gaseous_server
case QueueItemType.TitleIngestor: case QueueItemType.TitleIngestor:
Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Title Ingestor"); 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 CallingQueueItem = this
}; };
import.ProcessDirectory(Config.LibraryConfiguration.LibraryImportDirectory);
// clean up // clean up
Classes.ImportGame.DeleteOrphanedDirectories(Config.LibraryConfiguration.LibraryImportDirectory); Classes.ImportGame.DeleteOrphanedDirectories(Config.LibraryConfiguration.LibraryImportDirectory);
@@ -170,7 +171,11 @@ namespace gaseous_server
case QueueItemType.OrganiseLibrary: case QueueItemType.OrganiseLibrary:
Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Library Organiser"); Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Library Organiser");
Classes.ImportGame.OrganiseLibrary(); Classes.ImportGame importLibraryOrg = new ImportGame
{
CallingQueueItem = this
};
importLibraryOrg.OrganiseLibrary();
_SaveLastRunTime = true; _SaveLastRunTime = true;
@@ -178,11 +183,11 @@ namespace gaseous_server
case QueueItemType.LibraryScan: case QueueItemType.LibraryScan:
Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Library Scanners"); Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Library Scanners");
Classes.ImportGame import = new ImportGame Classes.ImportGame libScan = new ImportGame
{ {
CallingQueueItem = this CallingQueueItem = this
}; };
import.LibraryScan(); libScan.LibraryScan();
_SaveLastRunTime = true; _SaveLastRunTime = true;

View File

@@ -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;

View File

@@ -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`)
);

View File

@@ -56,6 +56,8 @@
<None Remove="Support\Database\MySQL\gaseous-1009.sql" /> <None Remove="Support\Database\MySQL\gaseous-1009.sql" />
<None Remove="Support\Database\MySQL\gaseous-1010.sql" /> <None Remove="Support\Database\MySQL\gaseous-1010.sql" />
<None Remove="Support\Database\MySQL\gaseous-1011.sql" /> <None Remove="Support\Database\MySQL\gaseous-1011.sql" />
<None Remove="Support\Database\MySQL\gaseous-1012.sql" />
<None Remove="Support\Database\MySQL\gaseous-1013.sql" />
<None Remove="Classes\Metadata\" /> <None Remove="Classes\Metadata\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -89,5 +91,7 @@
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1009.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1009.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1010.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1010.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1011.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1011.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1012.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1013.sql" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,6 +1,7 @@
<div id="properties_toc"> <div id="properties_toc">
<div id="properties_toc_general" name="properties_toc_item" onclick="SelectTab('general');">General</div> <div id="properties_toc_general" name="properties_toc_item" onclick="SelectTab('general');">General</div>
<div id="properties_toc_attributes" name="properties_toc_item" onclick="SelectTab('attributes');">Attributes</div> <div id="properties_toc_archive" name="properties_toc_item" onclick="SelectTab('archive');" style="display: none;">Archive Contents</div>
<div id="properties_toc_attributes" name="properties_toc_item" onclick="SelectTab('attributes');" style="display: none;">Attributes</div>
<div id="properties_toc_match" name="properties_toc_item" onclick="SelectTab('match');">Title Match</div> <div id="properties_toc_match" name="properties_toc_item" onclick="SelectTab('match');">Title Match</div>
<!--<div id="properties_toc_manage" name="properties_toc_item" onclick="SelectTab('manage');">Manage</div>--> <!--<div id="properties_toc_manage" name="properties_toc_item" onclick="SelectTab('manage');">Manage</div>-->
</div> </div>
@@ -53,6 +54,10 @@
</table> </table>
</div> </div>
<div id="properties_bodypanel_archive" name="properties_tab" style="display: none;">
<div id="properties_bodypanel_archive_content" style="height: 315px; overflow-x: scroll;"></div>
</div>
<div id="properties_bodypanel_attributes" name="properties_tab" style="display: none;"> <div id="properties_bodypanel_attributes" name="properties_tab" style="display: none;">
</div> </div>
@@ -120,7 +125,6 @@
ajaxCall('/api/v1.1/Games/' + gameId + '/roms/' + modalVariables, 'GET', function (result) { ajaxCall('/api/v1.1/Games/' + gameId + '/roms/' + modalVariables, 'GET', function (result) {
romData = result; romData = result;
console.log(romData);
document.getElementById('modal-heading').innerHTML = result.name; document.getElementById('modal-heading').innerHTML = result.name;
document.getElementById('rominfo_library').innerHTML = result.library.name; document.getElementById('rominfo_library').innerHTML = result.library.name;
document.getElementById('rominfo_platform').innerHTML = result.platform; document.getElementById('rominfo_platform').innerHTML = result.platform;
@@ -142,8 +146,7 @@
if (result.attributes.length > 0) { if (result.attributes.length > 0) {
document.getElementById('properties_bodypanel_attributes').appendChild(BuildAttributesTable(result.attributes, result.source)); document.getElementById('properties_bodypanel_attributes').appendChild(BuildAttributesTable(result.attributes, result.source));
} else { document.getElementById('properties_bodypanel_archive_content').appendChild(BuildArchiveTable(result.attributes, result.source));
document.getElementById('properties_toc_attributes').style.display = 'none';
} }
}); });
@@ -199,12 +202,9 @@
$('#properties_fixplatform').on('select2:select', function (e) { $('#properties_fixplatform').on('select2:select', function (e) {
var platformData = e.params.data; var platformData = e.params.data;
console.log(platformData);
var gameValue = $('#properties_fixgame').select2('data'); var gameValue = $('#properties_fixgame').select2('data');
if (gameValue) { if (gameValue) {
console.log(gameValue[0]);
setFixGameDropDown(); setFixGameDropDown();
} }
}); });
@@ -264,23 +264,75 @@
aTable.style.width = '100%'; aTable.style.width = '100%';
for (var i = 0; i < attributes.length; i++) { 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'); var aTitleCell = document.createElement('th');
aTitleCell.width = "25%"; aTitleCell.width = "25%";
if (sourceName == "TOSEC") { if (sourceName == "TOSEC") {
aTitleCell.innerHTML = ConvertTOSECAttributeName(attributes[i].key); aTitleCell.innerHTML = ConvertTOSECAttributeName(attributes[i].key);
} else { } else {
aTitleCell.innerHTML = attributes[i].key; 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'); return aTable;
aValueCell.width = "75%"; }
aValueCell.innerHTML = attributes[i].value;
aRow.appendChild(aValueCell);
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 + "<br />SHA1: " + archiveContent[r].SHA1;
hRow.appendChild(aHashCell);
aBody.appendChild(hRow);
aTable.appendChild(aBody);
}
}
} }
return aTable; return aTable;

View File

@@ -245,8 +245,6 @@ function intToRGB(i) {
} }
function DropDownRenderGameOption(state) { function DropDownRenderGameOption(state) {
console.log(state);
if (state.loading) { if (state.loading) {
return state; return state;
} }
@@ -260,7 +258,7 @@ function DropDownRenderGameOption(state) {
if (state.cover) { if (state.cover) {
response = $( response = $(
'<table class="dropdown-div"><tr><td class="dropdown-cover"><img src="/api/v1.1/Games/' + state.id + '/cover/image/cover_small/' + state.cover.value.imageId + '.jpg" /></td><td class="dropdown-label"><span class="dropdown-title">' + state.text + '</span><span class="dropdown-releasedate">' + releaseDate + '</span></td></tr></table>' '<table class="dropdown-div"><tr><td class="dropdown-cover"><img src="/api/v1.1/Games/' + state.id + '/cover/image/cover_small/' + state.cover.imageId + '.jpg" /></td><td class="dropdown-label"><span class="dropdown-title">' + state.text + '</span><span class="dropdown-releasedate">' + releaseDate + '</span></td></tr></table>'
); );
} else { } else {
response = $( response = $(