diff --git a/gaseous-server/Classes/Collections.cs b/gaseous-server/Classes/Collections.cs index c0ea2b6..6f53637 100644 --- a/gaseous-server/Classes/Collections.cs +++ b/gaseous-server/Classes/Collections.cs @@ -266,7 +266,7 @@ namespace gaseous_server.Classes gameItem.InclusionStatus.PlatformId = alwaysIncludeItem.PlatformId; gameItem.InclusionStatus.GameId = alwaysIncludeItem.GameId; gameItem.InclusionStatus.InclusionState = alwaysIncludeItem.InclusionState; - gameItem.Roms = Roms.GetRoms((long)gameItem.Id, (long)platform.Id); + gameItem.Roms = Roms.GetRoms((long)gameItem.Id, (long)platform.Id).GameRomItems; collectionPlatformItem.Games.Add(gameItem); } @@ -286,7 +286,7 @@ namespace gaseous_server.Classes { CollectionContents.CollectionPlatformItem.CollectionGameItem collectionGameItem = new CollectionContents.CollectionPlatformItem.CollectionGameItem(game); - List gameRoms = Roms.GetRoms((long)game.Id, (long)platform.Id); + List gameRoms = Roms.GetRoms((long)game.Id, (long)platform.Id).GameRomItems; bool AddGame = false; diff --git a/gaseous-server/Classes/Metadata/Games.cs b/gaseous-server/Classes/Metadata/Games.cs index f1eb574..c89b4b9 100644 --- a/gaseous-server/Classes/Metadata/Games.cs +++ b/gaseous-server/Classes/Metadata/Games.cs @@ -15,6 +15,12 @@ namespace gaseous_server.Classes.Metadata } + public class InvalidGameId : Exception + { + public InvalidGameId(long Id) : base("Unable to find Game by id " + Id) + {} + } + private static IGDBClient igdb = new IGDBClient( // Found in Twitch Developer portal for your app Config.IGDB.ClientId, diff --git a/gaseous-server/Classes/RomMediaGroup.cs b/gaseous-server/Classes/RomMediaGroup.cs new file mode 100644 index 0000000..706d3d2 --- /dev/null +++ b/gaseous-server/Classes/RomMediaGroup.cs @@ -0,0 +1,415 @@ +using System; +using System.Data; +using gaseous_tools; +using gaseous_signature_parser.models.RomSignatureObject; +using Microsoft.VisualBasic; +using IGDB.Models; +using gaseous_server.Classes.Metadata; +using System.IO.Compression; + +namespace gaseous_server.Classes +{ + public class RomMediaGroup + { + public class InvalidMediaGroupId : Exception + { + public InvalidMediaGroupId(long Id) : base("Unable to find media group by id " + Id) + {} + } + + public static GameRomMediaGroupItem CreateMediaGroup(long GameId, long PlatformId, List RomIds) + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "INSERT INTO RomMediaGroup (Status, PlatformId, GameId) VALUES (@status, @platformid, @gameid); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("status", GameRomMediaGroupItem.GroupBuildStatus.WaitingForBuild); + dbDict.Add("gameid", GameId); + dbDict.Add("platformid", PlatformId); + DataTable mgInsert = db.ExecuteCMD(sql, dbDict); + long mgId = (long)mgInsert.Rows[0][0]; + foreach (long RomId in RomIds) + { + try + { + Roms.GameRomItem gameRomItem = Roms.GetRom(RomId); + if (gameRomItem.PlatformId == PlatformId) + { + sql = "INSERT INTO RomMediaGroup_Members (GroupId, RomId) VALUES (@groupid, @romid);"; + dbDict.Clear(); + dbDict.Add("groupid", mgId); + dbDict.Add("romid", RomId); + db.ExecuteCMD(sql, dbDict); + } + else + { + Logging.Log(Logging.LogType.Warning, "Media Group", "Unable to add ROM id " + RomId + " to group. ROM platform is different from group platform."); + } + } + catch (Roms.InvalidRomId irid) + { + Logging.Log(Logging.LogType.Warning, "Media Group", "Unable to add ROM id " + RomId + " to group. ROM doesn't exist", irid); + } + } + + StartMediaGroupBuild(mgId); + + return GetMediaGroup(mgId); + } + + public static GameRomMediaGroupItem GetMediaGroup(long Id) + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT * FROM RomMediaGroup WHERE Id=@id;"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", Id); + + DataTable dataTable = db.ExecuteCMD(sql, dbDict); + + if (dataTable.Rows.Count == 0) + { + throw new InvalidMediaGroupId(Id); + } + else + { + GameRomMediaGroupItem mediaGroupItem = BuildMediaGroupFromRow(dataTable.Rows[0]); + return mediaGroupItem; + } + } + + public static List GetMediaGroupsFromGameId(long GameId) + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT * FROM RomMediaGroup WHERE GameId=@gameid;"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("gameid", GameId); + + DataTable dataTable = db.ExecuteCMD(sql, dbDict); + + List mediaGroupItems = new List(); + + foreach (DataRow row in dataTable.Rows) + { + mediaGroupItems.Add(BuildMediaGroupFromRow(row)); + } + + mediaGroupItems.Sort((x, y) => x.PlatformName.CompareTo(y.PlatformName)); + + return mediaGroupItems; + } + + public static GameRomMediaGroupItem EditMediaGroup(long Id, List RomIds) + { + GameRomMediaGroupItem mg = GetMediaGroup(Id); + + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = ""; + Dictionary dbDict = new Dictionary(); + + // delete roms from group + sql = "DELETE FROM RomMediaGroup_Members WHERE GroupId=@groupid;"; + dbDict.Clear(); + dbDict.Add("groupid", Id); + db.ExecuteCMD(sql, dbDict); + + // add roms to group + foreach (long RomId in RomIds) + { + try + { + Roms.GameRomItem gameRomItem = Roms.GetRom(RomId); + if (gameRomItem.PlatformId == mg.PlatformId) + { + sql = "INSERT INTO RomMediaGroup_Members (GroupId, RomId) VALUES (@groupid, @romid);"; + dbDict.Clear(); + dbDict.Add("groupid", Id); + dbDict.Add("romid", RomId); + db.ExecuteCMD(sql, dbDict); + } + else + { + Logging.Log(Logging.LogType.Warning, "Media Group", "Unable to add ROM id " + RomId + " to group. ROM platform is different from group platform."); + } + } + catch (Roms.InvalidRomId irid) + { + Logging.Log(Logging.LogType.Warning, "Media Group", "Unable to add ROM id " + RomId + " to group. ROM doesn't exist", irid); + } + } + + // set group to rebuild + sql = "UPDATE RomMediaGroup SET Status=1 WHERE GroupId=@groupid;"; + dbDict.Clear(); + dbDict.Add("groupid", Id); + db.ExecuteCMD(sql, dbDict); + + string MediaGroupZipPath = Path.Combine(Config.LibraryConfiguration.LibraryMediaGroupDirectory, Id + ".zip"); + if (File.Exists(MediaGroupZipPath)) + { + File.Delete(MediaGroupZipPath); + } + + StartMediaGroupBuild(Id); + + // return to caller + return GetMediaGroup(Id); + } + + public static void DeleteMediaGroup(long Id) + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "DELETE FROM RomMediaGroup WHERE Id=@id;"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", Id); + db.ExecuteCMD(sql, dbDict); + + string MediaGroupZipPath = Path.Combine(Config.LibraryConfiguration.LibraryMediaGroupDirectory, Id + ".zip"); + if (File.Exists(MediaGroupZipPath)) + { + File.Delete(MediaGroupZipPath); + } + } + + internal static GameRomMediaGroupItem BuildMediaGroupFromRow(DataRow row) + { + GameRomMediaGroupItem mediaGroupItem = new GameRomMediaGroupItem(); + mediaGroupItem.Id = (long)row["Id"]; + mediaGroupItem.Status = (GameRomMediaGroupItem.GroupBuildStatus)row["Status"]; + mediaGroupItem.PlatformId = (long)row["PlatformId"]; + mediaGroupItem.GameId = (long)row["GameId"]; + mediaGroupItem.RomIds = new List(); + + // get members + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT * FROM RomMediaGroup_Members WHERE GroupId=@id;"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", mediaGroupItem.Id); + DataTable data = db.ExecuteCMD(sql, dbDict); + foreach (DataRow dataRow in data.Rows) + { + mediaGroupItem.RomIds.Add((long)dataRow["RomId"]); + } + + return mediaGroupItem; + } + + public static void StartMediaGroupBuild(long Id) + { + GameRomMediaGroupItem mediaGroupItem = GetMediaGroup(Id); + + if (mediaGroupItem.Status != GameRomMediaGroupItem.GroupBuildStatus.Building) + { + // set collection item to waitingforbuild + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "UPDATE RomMediaGroup SET Status=@bs WHERE Id=@id"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", Id); + dbDict.Add("bs", GameRomMediaGroupItem.GroupBuildStatus.WaitingForBuild); + db.ExecuteCMD(sql, dbDict); + + // start background task + ProcessQueue.QueueItem queueItem = new ProcessQueue.QueueItem(ProcessQueue.QueueItemType.MediaGroupCompiler, 1, false, true); + queueItem.Options = Id; + queueItem.ForceExecute(); + ProcessQueue.QueueItems.Add(queueItem); + } + } + + public static void CompileMediaGroup(long Id) + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + + GameRomMediaGroupItem mediaGroupItem = GetMediaGroup(Id); + if (mediaGroupItem.Status == GameRomMediaGroupItem.GroupBuildStatus.WaitingForBuild) + { + Game GameObject = Games.GetGame(mediaGroupItem.GameId, false, false, false); + Platform PlatformObject = Platforms.GetPlatform(mediaGroupItem.PlatformId, false); + + Logging.Log(Logging.LogType.Information, "Media Group", "Beginning build of media group: " + GameObject.Name + " for platform " + PlatformObject.Name); + + // set starting + string sql = "UPDATE RomMediaGroup SET Status=@bs WHERE Id=@id"; + Dictionary dbDict = new Dictionary(); + dbDict.Add("id", mediaGroupItem.Id); + dbDict.Add("bs", GameRomMediaGroupItem.GroupBuildStatus.Building); + db.ExecuteCMD(sql, dbDict); + + string ZipFilePath = Path.Combine(Config.LibraryConfiguration.LibraryMediaGroupDirectory, mediaGroupItem.Id + ".zip"); + string ZipFileTempPath = Path.Combine(Config.LibraryConfiguration.LibraryTempDirectory, mediaGroupItem.Id.ToString()); + + try + { + // clean up if needed + if (File.Exists(ZipFilePath)) + { + Logging.Log(Logging.LogType.Warning, "Media Group", "Deleting existing build of media group: " + GameObject.Name + " for platform " + PlatformObject.Name); + File.Delete(ZipFilePath); + } + + if (Directory.Exists(ZipFileTempPath)) + { + Directory.Delete(ZipFileTempPath, true); + } + + // gather media group files + Directory.CreateDirectory(ZipFileTempPath); + List romItems = new List(); + List M3UFileContents = new List(); + foreach (long RomId in mediaGroupItem.RomIds) + { + Roms.GameRomItem rom = Roms.GetRom(RomId); + if (File.Exists(rom.Path)) + { + Logging.Log(Logging.LogType.Information, "Media Group", "Copying ROM: " + rom.Name); + File.Copy(rom.Path, Path.Combine(ZipFileTempPath, Path.GetFileName(rom.Path))); + + romItems.Add(rom); + } + } + + // build m3u + romItems.Sort((a, b) => + { + var firstCompare = a.MediaDetail.Number.ToString().CompareTo(b.MediaDetail.Number.ToString()); + return firstCompare != 0 ? firstCompare : a.MediaDetail.Side.CompareTo(b.MediaDetail.Side); + } + ); + foreach (Roms.GameRomItem romItem in romItems) + { + string M3UFileContent = ""; + M3UFileContent += romItem.Name; + if (romItem.MediaLabel != null) + { + if (romItem.MediaLabel.Length > 0) + { + M3UFileContent += "|" + romItem.MediaLabel; + } + } + M3UFileContents.Add(M3UFileContent); + } + + File.WriteAllText(Path.Combine(ZipFileTempPath, GameObject.Name + ".m3u"), String.Join(Environment.NewLine, M3UFileContents)); + + // compress to zip + Logging.Log(Logging.LogType.Information, "Media Group", "Compressing media group"); + if (!Directory.Exists(Config.LibraryConfiguration.LibraryMediaGroupDirectory)) + { + Directory.CreateDirectory(Config.LibraryConfiguration.LibraryMediaGroupDirectory); + } + ZipFile.CreateFromDirectory(ZipFileTempPath, ZipFilePath, CompressionLevel.SmallestSize, false); + + // clean up + if (Directory.Exists(ZipFileTempPath)) + { + Logging.Log(Logging.LogType.Information, "Media Group", "Cleaning up"); + Directory.Delete(ZipFileTempPath, true); + } + + // set completed + dbDict["bs"] = GameRomMediaGroupItem.GroupBuildStatus.Completed; + db.ExecuteCMD(sql, dbDict); + } + catch (Exception ex) + { + // clean up + if (Directory.Exists(ZipFileTempPath)) + { + Directory.Delete(ZipFileTempPath, true); + } + + if (File.Exists(ZipFilePath)) + { + File.Delete(ZipFilePath); + } + + // set failed + dbDict["bs"] = GameRomMediaGroupItem.GroupBuildStatus.Failed; + db.ExecuteCMD(sql, dbDict); + + Logging.Log(Logging.LogType.Critical, "Media Group", "Media Group building has failed", ex); + } + } + } + + public class GameRomMediaGroupItem + { + public long Id { get; set; } + public long GameId { get; set; } + public long PlatformId { get; set; } + public string PlatformName { + get + { + try + { + return Platforms.GetPlatform(PlatformId, false).Name; + } + catch + { + return "Unknown"; + } + } + } + public List RomIds { get; set; } + private GroupBuildStatus _Status { get; set; } + public GroupBuildStatus Status { + get + { + if (_Status == GroupBuildStatus.Completed) + { + if (File.Exists(MediaGroupZipPath)) + { + return GroupBuildStatus.Completed; + } + else + { + return GroupBuildStatus.NoStatus; + } + } + else + { + return _Status; + } + } + set + { + _Status = value; + } + } + public long? Size { + get + { + if (Status == GroupBuildStatus.Completed) + { + if (File.Exists(MediaGroupZipPath)) + { + FileInfo fi = new FileInfo(MediaGroupZipPath); + return fi.Length; + } + else + { + return 0; + } + } + else + { + return 0; + } + } + } + internal string MediaGroupZipPath + { + get + { + return Path.Combine(Config.LibraryConfiguration.LibraryMediaGroupDirectory, Id + ".zip"); + } + } + public enum GroupBuildStatus + { + NoStatus = 0, + WaitingForBuild = 1, + Building = 2, + Completed = 3, + Failed = 4 + } + } + } +} \ No newline at end of file diff --git a/gaseous-server/Classes/Roms.cs b/gaseous-server/Classes/Roms.cs index 3d86466..a5a7a53 100644 --- a/gaseous-server/Classes/Roms.cs +++ b/gaseous-server/Classes/Roms.cs @@ -1,13 +1,24 @@ using System; using System.Data; using gaseous_tools; +using gaseous_signature_parser.models.RomSignatureObject; +using static gaseous_server.Classes.RomMediaGroup; +using gaseous_server.Classes.Metadata; namespace gaseous_server.Classes { public class Roms { - public static List GetRoms(long GameId, long PlatformId = -1) + public class InvalidRomId : Exception + { + public InvalidRomId(long Id) : base("Unable to find ROM by id " + Id) + {} + } + + public static GameRomObject GetRoms(long GameId, long PlatformId = -1) { + GameRomObject GameRoms = new GameRomObject(); + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); string sql = ""; Dictionary dbDict = new Dictionary(); @@ -23,17 +34,19 @@ namespace gaseous_server.Classes if (romDT.Rows.Count > 0) { - List romItems = new List(); foreach (DataRow romDR in romDT.Rows) { - romItems.Add(BuildRom(romDR)); + GameRoms.GameRomItems.Add(BuildRom(romDR)); } - return romItems; + // get rom media groups + GameRoms.MediaGroups = Classes.RomMediaGroup.GetMediaGroupsFromGameId(GameId); + + return GameRoms; } else { - throw new Exception("Unknown Game Id"); + throw new Games.InvalidGameId(GameId); } } @@ -53,7 +66,7 @@ namespace gaseous_server.Classes } else { - throw new Exception("Unknown ROM Id"); + throw new InvalidRomId(RomId); } } @@ -135,6 +148,12 @@ namespace gaseous_server.Classes return romItem; } + public class GameRomObject + { + public List MediaGroups { get; set; } = new List(); + public List GameRomItems { get; set; } = new List(); + } + public class GameRomItem { public long Id { get; set; } @@ -153,12 +172,112 @@ namespace gaseous_server.Classes public List>? Attributes { get; set;} public int RomType { get; set; } public string? RomTypeMedia { get; set; } + public MediaType? MediaDetail { + get + { + if (RomTypeMedia != null) + { + return new MediaType(Source, RomTypeMedia); + } + else + { + return null; + } + } + } public string? MediaLabel { get; set; } public string? Path { get; set; } - public gaseous_signature_parser.models.RomSignatureObject.RomSignatureObject.Game.Rom.SignatureSourceType Source { get; set; } + public RomSignatureObject.Game.Rom.SignatureSourceType Source { get; set; } public string? SignatureSourceGameTitle { get; set;} public GameLibrary.LibraryItem Library { get; set; } } + + public class MediaType + { + public MediaType(RomSignatureObject.Game.Rom.SignatureSourceType Source, string MediaTypeString) + { + switch (Source) + { + case RomSignatureObject.Game.Rom.SignatureSourceType.TOSEC: + string[] typeString = MediaTypeString.Split(" "); + + string inType = ""; + foreach (string typeStringVal in typeString) + { + if (inType == "") + { + switch (typeStringVal.ToLower()) + { + case "disk": + Media = RomSignatureObject.Game.Rom.RomTypes.Disk; + + inType = typeStringVal; + break; + case "disc": + Media = RomSignatureObject.Game.Rom.RomTypes.Disc; + + inType = typeStringVal; + break; + case "file": + Media = RomSignatureObject.Game.Rom.RomTypes.File; + + inType = typeStringVal; + break; + case "part": + Media = RomSignatureObject.Game.Rom.RomTypes.Part; + + inType = typeStringVal; + break; + case "tape": + Media = RomSignatureObject.Game.Rom.RomTypes.Tape; + + inType = typeStringVal; + break; + case "of": + inType = typeStringVal; + break; + case "side": + inType = typeStringVal; + break; + } + } + else { + switch (inType.ToLower()) + { + case "disk": + case "disc": + case "file": + case "part": + case "tape": + Number = int.Parse(typeStringVal); + break; + case "of": + Count = int.Parse(typeStringVal); + break; + case "side": + Side = typeStringVal; + break; + } + inType = ""; + } + } + + break; + + default: + break; + + } + } + + public RomSignatureObject.Game.Rom.RomTypes? Media { get; set; } + + public int? Number { get; set; } + + public int? Count { get; set; } + + public string? Side { get; set; } + } } } diff --git a/gaseous-server/Controllers/GamesController.cs b/gaseous-server/Controllers/GamesController.cs index 0c08cb8..696626c 100644 --- a/gaseous-server/Controllers/GamesController.cs +++ b/gaseous-server/Controllers/GamesController.cs @@ -739,7 +739,7 @@ namespace gaseous_server.Controllers [HttpGet] [Route("{GameId}/roms")] - [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(Classes.Roms.GameRomObject), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] //[ResponseCache(CacheProfileName = "5Minute")] public ActionResult GameRom(long GameId) @@ -748,9 +748,7 @@ namespace gaseous_server.Controllers { Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); - List roms = Classes.Roms.GetRoms(GameId); - - return Ok(roms); + return Ok(Classes.Roms.GetRoms(GameId)); } catch { @@ -909,6 +907,158 @@ namespace gaseous_server.Controllers } } + [HttpGet] + [Route("{GameId}/romgroup/{RomGroupId}")] + [ProducesResponseType(typeof(Classes.RomMediaGroup.GameRomMediaGroupItem), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + //[ResponseCache(CacheProfileName = "5Minute")] + public ActionResult GameRomGroup(long GameId, long RomGroupId) + { + try + { + Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); + + Classes.RomMediaGroup.GameRomMediaGroupItem rom = Classes.RomMediaGroup.GetMediaGroup(RomGroupId); + if (rom.GameId == GameId) + { + return Ok(rom); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpPost] + [Route("{GameId}/romgroup")] + [ProducesResponseType(typeof(Classes.RomMediaGroup.GameRomMediaGroupItem), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult NewGameRomGroup(long GameId, long PlatformId, [FromBody] List RomIds) + { + try + { + Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); + + try + { + Classes.RomMediaGroup.GameRomMediaGroupItem rom = Classes.RomMediaGroup.CreateMediaGroup(GameId, PlatformId, RomIds); + return Ok(rom); + } + catch + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpPatch] + [Route("{GameId}/romgroup/{RomId}")] + [ProducesResponseType(typeof(Classes.RomMediaGroup.GameRomMediaGroupItem), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GameRomGroupMembers(long GameId, long RomGroupId, [FromBody] List RomIds) + { + try + { + Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); + + Classes.RomMediaGroup.GameRomMediaGroupItem rom = Classes.RomMediaGroup.GetMediaGroup(RomGroupId); + if (rom.GameId == GameId) + { + rom = Classes.RomMediaGroup.EditMediaGroup(RomGroupId, RomIds); + return Ok(rom); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpDelete] + [Route("{GameId}/romgroup/{RomGroupId}")] + [ProducesResponseType(typeof(Classes.RomMediaGroup.GameRomMediaGroupItem), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GameRomGroupDelete(long GameId, long RomGroupId) + { + try + { + Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); + + Classes.RomMediaGroup.GameRomMediaGroupItem rom = Classes.RomMediaGroup.GetMediaGroup(RomGroupId); + if (rom.GameId == GameId) + { + Classes.RomMediaGroup.DeleteMediaGroup(RomGroupId); + return Ok(rom); + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + + [HttpGet] + [HttpHead] + [Route("{GameId}/romgroup/{RomGroupId}/file")] + [Route("{GameId}/romgroup/{RomGroupId}/{filename}")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GameRomGroupFile(long GameId, long RomGroupId, string filename = "") + { + try + { + IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); + + Classes.RomMediaGroup.GameRomMediaGroupItem rom = Classes.RomMediaGroup.GetMediaGroup(RomGroupId); + if (rom.GameId != GameId) + { + return NotFound(); + } + + string romFilePath = Path.Combine(Config.LibraryConfiguration.LibraryMediaGroupDirectory, RomGroupId + ".zip"); + if (System.IO.File.Exists(romFilePath)) + { + FileStream content = new FileStream(romFilePath, FileMode.Open, FileAccess.Read, FileShare.Read); + string returnFileName = ""; + if (filename == "") + { + returnFileName = gameObject.Name + ".zip"; + } + else + { + returnFileName = filename; + } + FileStreamResult response = File(content, "application/octet-stream", returnFileName); + return response; + } + else + { + return NotFound(); + } + } + catch + { + return NotFound(); + } + } + [HttpGet] [Route("search")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] diff --git a/gaseous-server/Controllers/SignaturesController.cs b/gaseous-server/Controllers/SignaturesController.cs index e9bab60..557aa4b 100644 --- a/gaseous-server/Controllers/SignaturesController.cs +++ b/gaseous-server/Controllers/SignaturesController.cs @@ -4,6 +4,7 @@ using System.Data; using System.Linq; using System.Security.Cryptography; using System.Threading.Tasks; +using gaseous_signature_parser.models.RomSignatureObject; using gaseous_tools; using Microsoft.AspNetCore.Mvc; @@ -92,7 +93,7 @@ namespace gaseous_server.Controllers Sha1 = ((string)sigDbRow["SHA1"]).ToLower(), DevelopmentStatus = (string)sigDbRow["DevelopmentStatus"], Attributes = Newtonsoft.Json.JsonConvert.DeserializeObject>>((string)Common.ReturnValueIfNull(sigDbRow["Attributes"], "[]")), - RomType = (Models.Signatures_Games.RomItem.RomTypes)(int)sigDbRow["RomType"], + RomType = (RomSignatureObject.Game.Rom.RomTypes)(int)sigDbRow["RomType"], RomTypeMedia = (string)sigDbRow["RomTypeMedia"], MediaLabel = (string)sigDbRow["MediaLabel"], SignatureSource = (gaseous_signature_parser.models.RomSignatureObject.RomSignatureObject.Game.Rom.SignatureSourceType)(Int32)sigDbRow["MetadataSource"] diff --git a/gaseous-server/Models/Signatures_Games.cs b/gaseous-server/Models/Signatures_Games.cs index 87ff858..9aadb21 100644 --- a/gaseous-server/Models/Signatures_Games.cs +++ b/gaseous-server/Models/Signatures_Games.cs @@ -1,5 +1,6 @@ using System; using System.Text.Json.Serialization; +using gaseous_signature_parser.models.RomSignatureObject; namespace gaseous_server.Models { @@ -128,49 +129,11 @@ namespace gaseous_server.Models public List> Attributes { get; set; } = new List>(); - public RomTypes RomType { get; set; } + public RomSignatureObject.Game.Rom.RomTypes RomType { get; set; } public string? RomTypeMedia { get; set; } public string? MediaLabel { get; set; } - public gaseous_signature_parser.models.RomSignatureObject.RomSignatureObject.Game.Rom.SignatureSourceType SignatureSource { get; set; } - - public enum RomTypes - { - /// - /// Media type is unknown - /// - Unknown = 0, - - /// - /// Optical media - /// - Disc = 1, - - /// - /// Magnetic media - /// - Disk = 2, - - /// - /// Individual files - /// - File = 3, - - /// - /// Individual pars - /// - Part = 4, - - /// - /// Tape base media - /// - Tape = 5, - - /// - /// Side of the media - /// - Side = 6 - } + public RomSignatureObject.Game.Rom.SignatureSourceType SignatureSource { get; set; } [JsonIgnore] public int Score diff --git a/gaseous-server/ProcessQueue.cs b/gaseous-server/ProcessQueue.cs index 5c5f76b..406cc2c 100644 --- a/gaseous-server/ProcessQueue.cs +++ b/gaseous-server/ProcessQueue.cs @@ -159,6 +159,11 @@ namespace gaseous_server Classes.Collections.CompileCollections((long)Options); break; + case QueueItemType.MediaGroupCompiler: + Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Media Group Compiler"); + Classes.RomMediaGroup.CompileMediaGroup((long)Options); + break; + case QueueItemType.BackgroundDatabaseUpgrade: Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Background Upgrade"); gaseous_tools.DatabaseMigration.UpgradeScriptBackgroundTasks(); @@ -245,6 +250,11 @@ namespace gaseous_server /// CollectionCompiler, + /// + /// Builds media groups - set the options attribute to the id of the media group to build + /// + MediaGroupCompiler, + /// /// Performs and post database upgrade scripts that can be processed as a background task /// diff --git a/gaseous-server/wwwroot/pages/dialogs/mediagroupdelete.html b/gaseous-server/wwwroot/pages/dialogs/mediagroupdelete.html new file mode 100644 index 0000000..ec6d9d0 --- /dev/null +++ b/gaseous-server/wwwroot/pages/dialogs/mediagroupdelete.html @@ -0,0 +1,27 @@ +

Are you sure you want to delete this media group?

+

Warning: This cannot be undone!

+
+
+ +
+
+ +
+
+ + \ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/game.html b/gaseous-server/wwwroot/pages/game.html index 1302f51..1bf8752 100644 --- a/gaseous-server/wwwroot/pages/game.html +++ b/gaseous-server/wwwroot/pages/game.html @@ -66,6 +66,9 @@ +
+

Media Groups

+
Edit

ROM's/Images

@@ -76,6 +79,7 @@ +
@@ -350,46 +354,56 @@ existingTable.remove(); } + var existingMgTable = document.getElementById('mediagrouptable'); + if (existingMgTable) { + existingMgTable.remove(); + } + var gameRoms = document.getElementById('gamesummaryroms'); ajaxCall('/api/v1/Games/' + gameId + '/roms', 'GET', function (result) { - if (result) { - result.sort((a, b) => a.platform.name.charCodeAt(0) - b.platform.name.charCodeAt(0)); + if (result.gameRomItems) { + var gameRomItems = result.gameRomItems; + var mediaGroups = result.mediaGroups; + + gameRomItems.sort((a, b) => a.platform.name.charCodeAt(0) - b.platform.name.charCodeAt(0)); var newTable = document.createElement('table'); newTable.id = 'romtable'; newTable.className = 'romtable'; newTable.setAttribute('cellspacing', 0); - newTable.appendChild(createTableRow(true, [['', 'rom_checkbox_box_hidden', 'rom_edit_checkbox'], 'Name', 'Size', 'Media', '', '', ''])); + newTable.appendChild(createTableRow(true, [['', 'rom_checkbox_box_hidden', 'rom_edit_checkbox'], 'Name', 'Size', 'Media', '', '', ''])); var lastPlatform = ''; - for (var i = 0; i < result.length; i++) { - if (result[i].platform.name != lastPlatform) { - lastPlatform = result[i].platform.name; + for (var i = 0; i < gameRomItems.length; i++) { + if (gameRomItems[i].platform.name != lastPlatform) { + lastPlatform = gameRomItems[i].platform.name; var platformRow = document.createElement('tr'); var platformHeader = document.createElement('th'); platformHeader.setAttribute('colspan', 6); - platformHeader.innerHTML = '' + result[i].platform.name; + platformHeader.innerHTML = '' + gameRomItems[i].platform.name; platformRow.appendChild(platformHeader); newTable.appendChild(platformRow); } + + var launchButton = ''; - if (result[i].emulator) { - if (result[i].emulator.type) { - if (result[i].emulator.type.length > 0) { - launchButton = 'Launch'; + if (result.gameRomItems[i].emulator) { + if (gameRomItems[i].emulator.type) { + if (gameRomItems[i].emulator.type.length > 0) { + launchButton = 'Launch'; } } } var newRow = [ - ['', 'rom_checkbox_box_hidden', 'rom_edit_checkbox'], - '' + result[i].name + '', - formatBytes(result[i].size, 2), - result[i].romTypeMedia, - result[i].mediaLabel, + ['', 'rom_checkbox_box_hidden', 'rom_edit_checkbox'], + '' + gameRomItems[i].name + '', + formatBytes(gameRomItems[i].size, 2), + gameRomItems[i].romTypeMedia, + gameRomItems[i].mediaLabel, launchButton, - '
i
' + '
i
' ]; newTable.appendChild(createTableRow(false, newRow, 'romrow', 'romcell')); } @@ -399,6 +413,96 @@ if (displayCheckboxes == true) { DisplayROMCheckboxes(true); } + + var mediaGroupDiv = document.getElementById('gamesummarymediagroups'); + if (mediaGroups.length == 0) { + mediaGroupDiv.style.display = 'none'; + } else { + mediaGroupDiv.style.display = ''; + var mgTable = document.createElement('table'); + mgTable.id = 'mediagrouptable'; + mgTable.className = 'romtable'; + mgTable.setAttribute('cellspacing', 0); + mgTable.appendChild(createTableRow(true, ['Platform', 'Images', 'Size', '', '', ''])); + + lastPlatform = ''; + for (var i = 0; i < mediaGroups.length; i++) { + var mediaGroup = mediaGroups[i]; + + // get rom details including emulator and friendly platform name + var launchButton = ''; + for (var r = 0; r < gameRomItems.length; r++) { + var gameRomItem = gameRomItems[r]; + if (gameRomItem.platformId == mediaGroup.platformId) { + if (gameRomItem.emulator) { + if (gameRomItem.emulator.type.length > 0) { + launchButton = 'Launch'; + break; + } + } + } + } + + var statusText = mediaGroup.status; + var downloadLink = ''; + var packageSize = '-'; + switch (mediaGroup.status) { + case 'NoStatus': + statusText = '-'; + break; + case "WaitingForBuild": + statusText = 'Build pending'; + break; + case "Building": + statusText = 'Building'; + break; + case "Completed": + statusText = 'Available'; + downloadLink = ''; + packageSize = formatBytes(mediaGroup.size); + break; + case "Failed": + statusText = 'Build error'; + break; + default: + statusText = result[i].buildStatus; + break; + } + + var deleteButton = ''; + + var newRow = [ + mediaGroup.platformName, + mediaGroup.romIds.length, + packageSize, + statusText, + launchButton, + '
' + downloadLink + deleteButton + '
' + ] + + mgTable.appendChild(createTableRow(false, newRow, 'romrow', 'romcell')); + + var mgRomRow = document.createElement('tr'); + var mgRomCell = document.createElement('td'); + mgRomCell.setAttribute('colspan', 6); + mgRomCell.className = 'romGroupTitles'; + // iterate the group members + var groupMemberNames = []; + for (var r = 0; r < mediaGroup.romIds.length; r++) { + for (var x = 0; x < gameRomItems.length; x++) { + if (mediaGroup.romIds[r] == gameRomItems[x].id) { + groupMemberNames.push(gameRomItems[x].name); + } + } + } + mgRomCell.innerHTML = groupMemberNames.join("
"); + + mgRomRow.appendChild(mgRomCell); + mgTable.appendChild(mgRomRow); + } + + mediaGroupDiv.appendChild(mgTable); + } } else { gameRoms.setAttribute('style', 'display: none;'); } @@ -518,6 +622,48 @@ } } + function handleChecks() { + var masterCheck = document.getElementById('rom_mastercheck'); + + var checkboxes = document.getElementsByName('rom_checkbox'); + + var firstPlatformId = undefined; + var includesDifferentPlatforms = false; + var checkCount = 0; + for (var i = 0; i < checkboxes.length; i++) { + if (checkboxes[i].checked == true) { + checkCount += 1; + if (firstPlatformId == undefined) { + // set our comparison platform + firstPlatformId = checkboxes[i].getAttribute('data-platformid'); + } else if (firstPlatformId != checkboxes[i].getAttribute('data-platformid')) { + includesDifferentPlatforms = true; + } + } + } + + if (checkCount == checkboxes.length) { + masterCheck.checked = true; + } else { + masterCheck.checked = false; + } + + if (firstPlatformId == undefined) { + includesDifferentPlatforms = true; + } + + if (checkCount < 2) { + includesDifferentPlatforms = true; + } + + var creategroupButton = document.getElementById('rom_edit_creategroup'); + if (includesDifferentPlatforms == false) { + creategroupButton.removeAttribute('disabled'); + } else { + creategroupButton.setAttribute('disabled', 'disabled'); + } + } + $('#rom_edit_fixplatform').select2({ minimumInputLength: 3, placeholder: "Platform", @@ -675,4 +821,31 @@ modalVariables = platformId; showSubDialog("collectionaddgame"); } + + function createMgGroup() { + var checkboxes = document.getElementsByName('rom_checkbox'); + + var platformId = undefined; + var romIds = []; + for (var i = 0; i < checkboxes.length; i++) { + if (checkboxes[i].checked == true) { + if (platformId == undefined) { + platformId = checkboxes[i].getAttribute('data-platformid'); + } + romIds.push(checkboxes[i].getAttribute('data-romid')); + } + } + + ajaxCall( + '/api/v1/Games/' + gameId + '/romgroup?PlatformId=' + platformId, + 'POST', + function (result) { + loadRoms(false); + }, + function (error) { + loadRoms(false); + }, + JSON.stringify(romIds) + ); + } \ No newline at end of file diff --git a/gaseous-server/wwwroot/styles/style.css b/gaseous-server/wwwroot/styles/style.css index d99eeea..2e727ed 100644 --- a/gaseous-server/wwwroot/styles/style.css +++ b/gaseous-server/wwwroot/styles/style.css @@ -160,6 +160,7 @@ h3 { height: 40px; right: 0px; align-items: center; + z-index: 50; } #banner_header_label { @@ -201,7 +202,7 @@ h3 { .filter_panel_box { position: relative; padding: 10px; - z-index: -1; + z-index: 1; } input[type='text'], input[type='number'] { @@ -721,6 +722,7 @@ button:hover { button:disabled { background-color: #555; + color: #888; cursor: not-allowed; } @@ -956,4 +958,8 @@ button:disabled { border-radius: 5px; border-color: darkslategray; border-style: solid; +} + +.romGroupTitles { + padding-left: 20px; } \ No newline at end of file diff --git a/gaseous-tools/Config.cs b/gaseous-tools/Config.cs index 8043d1f..dc1a282 100644 --- a/gaseous-tools/Config.cs +++ b/gaseous-tools/Config.cs @@ -410,6 +410,14 @@ namespace gaseous_tools } } + public string LibraryMediaGroupDirectory + { + get + { + return Path.Combine(LibraryRootDirectory, "Media Groups"); + } + } + public string LibraryMetadataDirectory_Platform(Platform platform) { string MetadataPath = Path.Combine(LibraryMetadataDirectory, "Platforms", platform.Slug); diff --git a/gaseous-tools/Database/MySQL/gaseous-1004.sql b/gaseous-tools/Database/MySQL/gaseous-1004.sql index 8542378..50b484a 100644 --- a/gaseous-tools/Database/MySQL/gaseous-1004.sql +++ b/gaseous-tools/Database/MySQL/gaseous-1004.sql @@ -39,4 +39,16 @@ CREATE TABLE `Relation_Game_Themes` ( ); ALTER TABLE `Games_Roms` -ADD COLUMN `LastMatchAttemptDate` DATETIME NULL AFTER `LibraryId`; \ No newline at end of file +ADD COLUMN `LastMatchAttemptDate` DATETIME NULL AFTER `LibraryId`; + +CREATE TABLE `RomMediaGroup` ( + `Id` BIGINT NOT NULL AUTO_INCREMENT, + `Status` INT NULL, + `PlatformId` BIGINT NULL, + `GameId` BIGINT NULL, + PRIMARY KEY (`Id`)); + +CREATE TABLE `RomMediaGroup_Members` ( + `GroupId` BIGINT NOT NULL, + `RomId` BIGINT NOT NULL, + PRIMARY KEY (`GroupId`, `RomId`));