Add m3u support for multi image games (#171)
* Parse media type info * Completed M3U support
This commit is contained in:
@@ -266,7 +266,7 @@ namespace gaseous_server.Classes
|
|||||||
gameItem.InclusionStatus.PlatformId = alwaysIncludeItem.PlatformId;
|
gameItem.InclusionStatus.PlatformId = alwaysIncludeItem.PlatformId;
|
||||||
gameItem.InclusionStatus.GameId = alwaysIncludeItem.GameId;
|
gameItem.InclusionStatus.GameId = alwaysIncludeItem.GameId;
|
||||||
gameItem.InclusionStatus.InclusionState = alwaysIncludeItem.InclusionState;
|
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);
|
collectionPlatformItem.Games.Add(gameItem);
|
||||||
}
|
}
|
||||||
@@ -286,7 +286,7 @@ namespace gaseous_server.Classes
|
|||||||
{
|
{
|
||||||
CollectionContents.CollectionPlatformItem.CollectionGameItem collectionGameItem = new CollectionContents.CollectionPlatformItem.CollectionGameItem(game);
|
CollectionContents.CollectionPlatformItem.CollectionGameItem collectionGameItem = new CollectionContents.CollectionPlatformItem.CollectionGameItem(game);
|
||||||
|
|
||||||
List<Roms.GameRomItem> gameRoms = Roms.GetRoms((long)game.Id, (long)platform.Id);
|
List<Roms.GameRomItem> gameRoms = Roms.GetRoms((long)game.Id, (long)platform.Id).GameRomItems;
|
||||||
|
|
||||||
bool AddGame = false;
|
bool AddGame = false;
|
||||||
|
|
||||||
|
@@ -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(
|
private static IGDBClient igdb = new IGDBClient(
|
||||||
// Found in Twitch Developer portal for your app
|
// Found in Twitch Developer portal for your app
|
||||||
Config.IGDB.ClientId,
|
Config.IGDB.ClientId,
|
||||||
|
415
gaseous-server/Classes/RomMediaGroup.cs
Normal file
415
gaseous-server/Classes/RomMediaGroup.cs
Normal file
@@ -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<long> 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<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
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<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
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<GameRomMediaGroupItem> GetMediaGroupsFromGameId(long GameId)
|
||||||
|
{
|
||||||
|
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "SELECT * FROM RomMediaGroup WHERE GameId=@gameid;";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
dbDict.Add("gameid", GameId);
|
||||||
|
|
||||||
|
DataTable dataTable = db.ExecuteCMD(sql, dbDict);
|
||||||
|
|
||||||
|
List<GameRomMediaGroupItem> mediaGroupItems = new List<GameRomMediaGroupItem>();
|
||||||
|
|
||||||
|
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<long> RomIds)
|
||||||
|
{
|
||||||
|
GameRomMediaGroupItem mg = GetMediaGroup(Id);
|
||||||
|
|
||||||
|
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
// 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<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
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<long>();
|
||||||
|
|
||||||
|
// get members
|
||||||
|
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
|
string sql = "SELECT * FROM RomMediaGroup_Members WHERE GroupId=@id;";
|
||||||
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
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<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
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<string, object> dbDict = new Dictionary<string, object>();
|
||||||
|
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<Roms.GameRomItem> romItems = new List<Roms.GameRomItem>();
|
||||||
|
List<string> M3UFileContents = new List<string>();
|
||||||
|
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<long> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,13 +1,24 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using gaseous_tools;
|
using gaseous_tools;
|
||||||
|
using gaseous_signature_parser.models.RomSignatureObject;
|
||||||
|
using static gaseous_server.Classes.RomMediaGroup;
|
||||||
|
using gaseous_server.Classes.Metadata;
|
||||||
|
|
||||||
namespace gaseous_server.Classes
|
namespace gaseous_server.Classes
|
||||||
{
|
{
|
||||||
public class Roms
|
public class Roms
|
||||||
{
|
{
|
||||||
public static List<GameRomItem> 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);
|
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||||
string sql = "";
|
string sql = "";
|
||||||
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||||
@@ -23,17 +34,19 @@ namespace gaseous_server.Classes
|
|||||||
|
|
||||||
if (romDT.Rows.Count > 0)
|
if (romDT.Rows.Count > 0)
|
||||||
{
|
{
|
||||||
List<GameRomItem> romItems = new List<GameRomItem>();
|
|
||||||
foreach (DataRow romDR in romDT.Rows)
|
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
|
else
|
||||||
{
|
{
|
||||||
throw new Exception("Unknown Game Id");
|
throw new Games.InvalidGameId(GameId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +66,7 @@ namespace gaseous_server.Classes
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new Exception("Unknown ROM Id");
|
throw new InvalidRomId(RomId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +148,12 @@ namespace gaseous_server.Classes
|
|||||||
return romItem;
|
return romItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class GameRomObject
|
||||||
|
{
|
||||||
|
public List<GameRomMediaGroupItem> MediaGroups { get; set; } = new List<GameRomMediaGroupItem>();
|
||||||
|
public List<GameRomItem> GameRomItems { get; set; } = new List<GameRomItem>();
|
||||||
|
}
|
||||||
|
|
||||||
public class GameRomItem
|
public class GameRomItem
|
||||||
{
|
{
|
||||||
public long Id { get; set; }
|
public long Id { get; set; }
|
||||||
@@ -153,12 +172,112 @@ namespace gaseous_server.Classes
|
|||||||
public List<KeyValuePair<string, object>>? Attributes { get; set;}
|
public List<KeyValuePair<string, object>>? Attributes { get; set;}
|
||||||
public int RomType { get; set; }
|
public int RomType { get; set; }
|
||||||
public string? RomTypeMedia { 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? MediaLabel { get; set; }
|
||||||
public string? Path { 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 string? SignatureSourceGameTitle { get; set;}
|
||||||
public GameLibrary.LibraryItem Library { 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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -739,7 +739,7 @@ namespace gaseous_server.Controllers
|
|||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("{GameId}/roms")]
|
[Route("{GameId}/roms")]
|
||||||
[ProducesResponseType(typeof(List<Classes.Roms.GameRomItem>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(Classes.Roms.GameRomObject), StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
//[ResponseCache(CacheProfileName = "5Minute")]
|
//[ResponseCache(CacheProfileName = "5Minute")]
|
||||||
public ActionResult GameRom(long GameId)
|
public ActionResult GameRom(long GameId)
|
||||||
@@ -748,9 +748,7 @@ namespace gaseous_server.Controllers
|
|||||||
{
|
{
|
||||||
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
|
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
|
||||||
|
|
||||||
List<Classes.Roms.GameRomItem> roms = Classes.Roms.GetRoms(GameId);
|
return Ok(Classes.Roms.GetRoms(GameId));
|
||||||
|
|
||||||
return Ok(roms);
|
|
||||||
}
|
}
|
||||||
catch
|
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<long> 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<long> 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]
|
[HttpGet]
|
||||||
[Route("search")]
|
[Route("search")]
|
||||||
[ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)]
|
||||||
|
@@ -4,6 +4,7 @@ using System.Data;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using gaseous_signature_parser.models.RomSignatureObject;
|
||||||
using gaseous_tools;
|
using gaseous_tools;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
@@ -92,7 +93,7 @@ namespace gaseous_server.Controllers
|
|||||||
Sha1 = ((string)sigDbRow["SHA1"]).ToLower(),
|
Sha1 = ((string)sigDbRow["SHA1"]).ToLower(),
|
||||||
DevelopmentStatus = (string)sigDbRow["DevelopmentStatus"],
|
DevelopmentStatus = (string)sigDbRow["DevelopmentStatus"],
|
||||||
Attributes = Newtonsoft.Json.JsonConvert.DeserializeObject<List<KeyValuePair<string, object>>>((string)Common.ReturnValueIfNull(sigDbRow["Attributes"], "[]")),
|
Attributes = Newtonsoft.Json.JsonConvert.DeserializeObject<List<KeyValuePair<string, object>>>((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"],
|
RomTypeMedia = (string)sigDbRow["RomTypeMedia"],
|
||||||
MediaLabel = (string)sigDbRow["MediaLabel"],
|
MediaLabel = (string)sigDbRow["MediaLabel"],
|
||||||
SignatureSource = (gaseous_signature_parser.models.RomSignatureObject.RomSignatureObject.Game.Rom.SignatureSourceType)(Int32)sigDbRow["MetadataSource"]
|
SignatureSource = (gaseous_signature_parser.models.RomSignatureObject.RomSignatureObject.Game.Rom.SignatureSourceType)(Int32)sigDbRow["MetadataSource"]
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using gaseous_signature_parser.models.RomSignatureObject;
|
||||||
|
|
||||||
namespace gaseous_server.Models
|
namespace gaseous_server.Models
|
||||||
{
|
{
|
||||||
@@ -128,49 +129,11 @@ namespace gaseous_server.Models
|
|||||||
|
|
||||||
public List<KeyValuePair<string, object>> Attributes { get; set; } = new List<KeyValuePair<string, object>>();
|
public List<KeyValuePair<string, object>> Attributes { get; set; } = new List<KeyValuePair<string, object>>();
|
||||||
|
|
||||||
public RomTypes RomType { get; set; }
|
public RomSignatureObject.Game.Rom.RomTypes RomType { get; set; }
|
||||||
public string? RomTypeMedia { get; set; }
|
public string? RomTypeMedia { get; set; }
|
||||||
public string? MediaLabel { get; set; }
|
public string? MediaLabel { get; set; }
|
||||||
|
|
||||||
public gaseous_signature_parser.models.RomSignatureObject.RomSignatureObject.Game.Rom.SignatureSourceType SignatureSource { get; set; }
|
public RomSignatureObject.Game.Rom.SignatureSourceType SignatureSource { get; set; }
|
||||||
|
|
||||||
public enum RomTypes
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Media type is unknown
|
|
||||||
/// </summary>
|
|
||||||
Unknown = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Optical media
|
|
||||||
/// </summary>
|
|
||||||
Disc = 1,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Magnetic media
|
|
||||||
/// </summary>
|
|
||||||
Disk = 2,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Individual files
|
|
||||||
/// </summary>
|
|
||||||
File = 3,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Individual pars
|
|
||||||
/// </summary>
|
|
||||||
Part = 4,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tape base media
|
|
||||||
/// </summary>
|
|
||||||
Tape = 5,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Side of the media
|
|
||||||
/// </summary>
|
|
||||||
Side = 6
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public int Score
|
public int Score
|
||||||
|
@@ -159,6 +159,11 @@ namespace gaseous_server
|
|||||||
Classes.Collections.CompileCollections((long)Options);
|
Classes.Collections.CompileCollections((long)Options);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case QueueItemType.MediaGroupCompiler:
|
||||||
|
Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Media Group Compiler");
|
||||||
|
Classes.RomMediaGroup.CompileMediaGroup((long)Options);
|
||||||
|
break;
|
||||||
|
|
||||||
case QueueItemType.BackgroundDatabaseUpgrade:
|
case QueueItemType.BackgroundDatabaseUpgrade:
|
||||||
Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Background Upgrade");
|
Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Background Upgrade");
|
||||||
gaseous_tools.DatabaseMigration.UpgradeScriptBackgroundTasks();
|
gaseous_tools.DatabaseMigration.UpgradeScriptBackgroundTasks();
|
||||||
@@ -245,6 +250,11 @@ namespace gaseous_server
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
CollectionCompiler,
|
CollectionCompiler,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds media groups - set the options attribute to the id of the media group to build
|
||||||
|
/// </summary>
|
||||||
|
MediaGroupCompiler,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs and post database upgrade scripts that can be processed as a background task
|
/// Performs and post database upgrade scripts that can be processed as a background task
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
27
gaseous-server/wwwroot/pages/dialogs/mediagroupdelete.html
Normal file
27
gaseous-server/wwwroot/pages/dialogs/mediagroupdelete.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<p>Are you sure you want to delete this media group?</p>
|
||||||
|
<p><strong>Warning:</strong> This cannot be undone!</p>
|
||||||
|
<div style="width: 100%; text-align: center;">
|
||||||
|
<div style="display: inline-block; margin-right: 20px;">
|
||||||
|
<button class="redbutton" value="Delete" onclick="deleteCollection();">Delete</button>
|
||||||
|
</div>
|
||||||
|
<div style="display: inline-block; margin-left: 20px;">
|
||||||
|
<button value="Cancel" onclick="closeSubDialog();">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
function deleteCollection() {
|
||||||
|
ajaxCall(
|
||||||
|
'/api/v1/Games/' + gameData.id + '/romgroup/' + subModalVariables,
|
||||||
|
'DELETE',
|
||||||
|
function (result) {
|
||||||
|
loadRoms();
|
||||||
|
closeSubDialog();
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
loadRoms();
|
||||||
|
closeSubDialog();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</script>
|
@@ -66,6 +66,9 @@
|
|||||||
|
|
||||||
<p id="gamesummarytext_label_button_contract" class="text_link" style="display: none;" onclick="document.querySelector('#gamesummarytext_label').classList.add('line-clamp-4'); document.querySelector('#gamesummarytext_label_button_expand').setAttribute('style', ''); document.querySelector('#gamesummarytext_label_button_contract').setAttribute('style', 'display: none;');">Read less...</p>
|
<p id="gamesummarytext_label_button_contract" class="text_link" style="display: none;" onclick="document.querySelector('#gamesummarytext_label').classList.add('line-clamp-4'); document.querySelector('#gamesummarytext_label_button_expand').setAttribute('style', ''); document.querySelector('#gamesummarytext_label_button_contract').setAttribute('style', 'display: none;');">Read less...</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="gamesummarymediagroups">
|
||||||
|
<h3>Media Groups</h3>
|
||||||
|
</div>
|
||||||
<div id="gamesummaryroms">
|
<div id="gamesummaryroms">
|
||||||
<span id="rom_edit" class="romlink" onclick="DisplayROMCheckboxes(true);">Edit</span>
|
<span id="rom_edit" class="romlink" onclick="DisplayROMCheckboxes(true);">Edit</span>
|
||||||
<h3>ROM's/Images</h3>
|
<h3>ROM's/Images</h3>
|
||||||
@@ -76,6 +79,7 @@
|
|||||||
<select id="rom_edit_fixplatform" style="width: 150px;"></select>
|
<select id="rom_edit_fixplatform" style="width: 150px;"></select>
|
||||||
<select id="rom_edit_fixgame" style="width: 300px;"></select>
|
<select id="rom_edit_fixgame" style="width: 300px;"></select>
|
||||||
<button id="rom_edit_update" onclick="remapTitles();">Update</button>
|
<button id="rom_edit_update" onclick="remapTitles();">Update</button>
|
||||||
|
<button id="rom_edit_creategroup" onclick="createMgGroup();" disabled="disabled">Create Media Group</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -350,46 +354,56 @@
|
|||||||
existingTable.remove();
|
existingTable.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var existingMgTable = document.getElementById('mediagrouptable');
|
||||||
|
if (existingMgTable) {
|
||||||
|
existingMgTable.remove();
|
||||||
|
}
|
||||||
|
|
||||||
var gameRoms = document.getElementById('gamesummaryroms');
|
var gameRoms = document.getElementById('gamesummaryroms');
|
||||||
ajaxCall('/api/v1/Games/' + gameId + '/roms', 'GET', function (result) {
|
ajaxCall('/api/v1/Games/' + gameId + '/roms', 'GET', function (result) {
|
||||||
if (result) {
|
if (result.gameRomItems) {
|
||||||
result.sort((a, b) => a.platform.name.charCodeAt(0) - b.platform.name.charCodeAt(0));
|
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');
|
var newTable = document.createElement('table');
|
||||||
newTable.id = 'romtable';
|
newTable.id = 'romtable';
|
||||||
newTable.className = 'romtable';
|
newTable.className = 'romtable';
|
||||||
newTable.setAttribute('cellspacing', 0);
|
newTable.setAttribute('cellspacing', 0);
|
||||||
newTable.appendChild(createTableRow(true, [['<input id="rom_mastercheck" type="checkbox" onclick="selectAllChecks();"/>', 'rom_checkbox_box_hidden', 'rom_edit_checkbox'], 'Name', 'Size', 'Media', '', '', '']));
|
newTable.appendChild(createTableRow(true, [['<input id="rom_mastercheck" type="checkbox" onclick="selectAllChecks(); handleChecks();"/>', 'rom_checkbox_box_hidden', 'rom_edit_checkbox'], 'Name', 'Size', 'Media', '', '', '']));
|
||||||
|
|
||||||
var lastPlatform = '';
|
var lastPlatform = '';
|
||||||
for (var i = 0; i < result.length; i++) {
|
for (var i = 0; i < gameRomItems.length; i++) {
|
||||||
if (result[i].platform.name != lastPlatform) {
|
if (gameRomItems[i].platform.name != lastPlatform) {
|
||||||
lastPlatform = result[i].platform.name;
|
lastPlatform = gameRomItems[i].platform.name;
|
||||||
var platformRow = document.createElement('tr');
|
var platformRow = document.createElement('tr');
|
||||||
var platformHeader = document.createElement('th');
|
var platformHeader = document.createElement('th');
|
||||||
platformHeader.setAttribute('colspan', 6);
|
platformHeader.setAttribute('colspan', 6);
|
||||||
platformHeader.innerHTML = '<a href="#" onclick="ShowPlatformMappingDialog(' + result[i].platform.id + ');" style="float: right; text-decoration: none;" class="romlink"><img src="/images/map.svg" class="banner_button_image banner_button_image_smaller" alt="Edit platform mapping" title="Edit platform mapping" /></a><a href="#" onclick="ShowCollectionDialog(' + result[i].platform.id + ');" style="float: right; text-decoration: none;" class="romlink"><img src="/images/collections.svg" class="banner_button_image banner_button_image_smaller" alt="Add to collection" title="Add to collection" /></a>' + result[i].platform.name;
|
platformHeader.innerHTML = '<a href="#" onclick="ShowPlatformMappingDialog(' + gameRomItems[i].platform.id + ');" style="float: right; text-decoration: none;" class="romlink"><img src="/images/map.svg" class="banner_button_image banner_button_image_smaller" alt="Edit platform mapping" title="Edit platform mapping" /></a><a href="#" onclick="ShowCollectionDialog(' + gameRomItems[i].platform.id + ');" style="float: right; text-decoration: none;" class="romlink"><img src="/images/collections.svg" class="banner_button_image banner_button_image_smaller" alt="Add to collection" title="Add to collection" /></a>' + gameRomItems[i].platform.name;
|
||||||
platformRow.appendChild(platformHeader);
|
platformRow.appendChild(platformHeader);
|
||||||
newTable.appendChild(platformRow);
|
newTable.appendChild(platformRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var launchButton = '';
|
var launchButton = '';
|
||||||
if (result[i].emulator) {
|
if (result.gameRomItems[i].emulator) {
|
||||||
if (result[i].emulator.type) {
|
if (gameRomItems[i].emulator.type) {
|
||||||
if (result[i].emulator.type.length > 0) {
|
if (gameRomItems[i].emulator.type.length > 0) {
|
||||||
launchButton = '<a href="/index.html?page=emulator&engine=' + result[i].emulator.type + '&core=' + result[i].emulator.core + '&platformid=' + result[i].platform.id + '&gameid=' + gameId + '&rompath=' + encodeURIComponent('/api/v1/Games/' + gameId + '/roms/' + result[i].id + '/' + encodeURIComponent(result[i].name)) + '" class="romstart">Launch</a>';
|
launchButton = '<a href="/index.html?page=emulator&engine=' + gameRomItems[i].emulator.type + '&core=' + gameRomItems[i].emulator.core + '&platformid=' + gameRomItems[i].platform.id + '&gameid=' + gameId + '&rompath=' + encodeURIComponent('/api/v1/Games/' + gameId + '/roms/' + gameRomItems[i].id + '/' + encodeURIComponent(gameRomItems[i].name)) + '" class="romstart">Launch</a>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var newRow = [
|
var newRow = [
|
||||||
['<input type="checkbox" name="rom_checkbox" data-romid="' + result[i].id + '" />', 'rom_checkbox_box_hidden', 'rom_edit_checkbox'],
|
['<input type="checkbox" name="rom_checkbox" data-gameid="' + gameData.id + '" data-platformid="' + gameRomItems[i].platformId + '" data-romid="' + gameRomItems[i].id + '" onclick="handleChecks();" />', 'rom_checkbox_box_hidden', 'rom_edit_checkbox'],
|
||||||
'<a href="/api/v1/Games/' + gameId + '/roms/' + result[i].id + '/' + encodeURIComponent(result[i].name) + '" class="romlink">' + result[i].name + '</a>',
|
'<a href="/api/v1/Games/' + gameId + '/roms/' + gameRomItems[i].id + '/' + encodeURIComponent(gameRomItems[i].name) + '" class="romlink">' + gameRomItems[i].name + '</a>',
|
||||||
formatBytes(result[i].size, 2),
|
formatBytes(gameRomItems[i].size, 2),
|
||||||
result[i].romTypeMedia,
|
gameRomItems[i].romTypeMedia,
|
||||||
result[i].mediaLabel,
|
gameRomItems[i].mediaLabel,
|
||||||
launchButton,
|
launchButton,
|
||||||
'<div class="properties_button" onclick="showDialog(\'rominfo\', ' + result[i].id + ');">i</div>'
|
'<div class="properties_button" onclick="showDialog(\'rominfo\', ' + gameRomItems[i].id + ');">i</div>'
|
||||||
];
|
];
|
||||||
newTable.appendChild(createTableRow(false, newRow, 'romrow', 'romcell'));
|
newTable.appendChild(createTableRow(false, newRow, 'romrow', 'romcell'));
|
||||||
}
|
}
|
||||||
@@ -399,6 +413,96 @@
|
|||||||
if (displayCheckboxes == true) {
|
if (displayCheckboxes == true) {
|
||||||
DisplayROMCheckboxes(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 = '<a href="/index.html?page=emulator&engine=' + gameRomItem.emulator.type + '&core=' + gameRomItem.emulator.core + '&platformid=' + gameRomItem.platform.id + '&gameid=' + gameId + '&rompath=' + encodeURIComponent('/api/v1/Games/' + gameId + '/romgroup/' + mediaGroup.id + '/' + gameData.name + '(' + mediaGroup.id + ')' + '.zip') + '" class="romstart">Launch</a>';
|
||||||
|
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 = '<a href="/api/v1/Games/' + gameId + '/romgroup/' + mediaGroup.id + '/' + gameData.name + '.zip" class="romlink"><img src="/images/download.svg" class="banner_button_image" alt="Download" title="Download" /></a>';
|
||||||
|
packageSize = formatBytes(mediaGroup.size);
|
||||||
|
break;
|
||||||
|
case "Failed":
|
||||||
|
statusText = 'Build error';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
statusText = result[i].buildStatus;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var deleteButton = '<a href="#" onclick="showSubDialog(\'mediagroupdelete\', ' + mediaGroup.id + ');" class="romlink"><img src="/images/delete.svg" class="banner_button_image" alt="Delete" title="Delete" /></a>';
|
||||||
|
|
||||||
|
var newRow = [
|
||||||
|
mediaGroup.platformName,
|
||||||
|
mediaGroup.romIds.length,
|
||||||
|
packageSize,
|
||||||
|
statusText,
|
||||||
|
launchButton,
|
||||||
|
'<div style="text-align: right;">' + downloadLink + deleteButton + '</div>'
|
||||||
|
]
|
||||||
|
|
||||||
|
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("<br />");
|
||||||
|
|
||||||
|
mgRomRow.appendChild(mgRomCell);
|
||||||
|
mgTable.appendChild(mgRomRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaGroupDiv.appendChild(mgTable);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
gameRoms.setAttribute('style', 'display: none;');
|
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({
|
$('#rom_edit_fixplatform').select2({
|
||||||
minimumInputLength: 3,
|
minimumInputLength: 3,
|
||||||
placeholder: "Platform",
|
placeholder: "Platform",
|
||||||
@@ -675,4 +821,31 @@
|
|||||||
modalVariables = platformId;
|
modalVariables = platformId;
|
||||||
showSubDialog("collectionaddgame");
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
@@ -160,6 +160,7 @@ h3 {
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
z-index: 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
#banner_header_label {
|
#banner_header_label {
|
||||||
@@ -201,7 +202,7 @@ h3 {
|
|||||||
.filter_panel_box {
|
.filter_panel_box {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
z-index: -1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='text'], input[type='number'] {
|
input[type='text'], input[type='number'] {
|
||||||
@@ -721,6 +722,7 @@ button:hover {
|
|||||||
|
|
||||||
button:disabled {
|
button:disabled {
|
||||||
background-color: #555;
|
background-color: #555;
|
||||||
|
color: #888;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -957,3 +959,7 @@ button:disabled {
|
|||||||
border-color: darkslategray;
|
border-color: darkslategray;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.romGroupTitles {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
@@ -410,6 +410,14 @@ namespace gaseous_tools
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string LibraryMediaGroupDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(LibraryRootDirectory, "Media Groups");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string LibraryMetadataDirectory_Platform(Platform platform)
|
public string LibraryMetadataDirectory_Platform(Platform platform)
|
||||||
{
|
{
|
||||||
string MetadataPath = Path.Combine(LibraryMetadataDirectory, "Platforms", platform.Slug);
|
string MetadataPath = Path.Combine(LibraryMetadataDirectory, "Platforms", platform.Slug);
|
||||||
|
@@ -40,3 +40,15 @@ CREATE TABLE `Relation_Game_Themes` (
|
|||||||
|
|
||||||
ALTER TABLE `Games_Roms`
|
ALTER TABLE `Games_Roms`
|
||||||
ADD COLUMN `LastMatchAttemptDate` DATETIME NULL AFTER `LibraryId`;
|
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`));
|
||||||
|
Reference in New Issue
Block a user