Collections can now have games set to be always included or excluded (#96)

* Added drop down menus to collections to select if games should be always included

* Now able to add games to a collection from the game info page
This commit is contained in:
Michael Green
2023-09-13 22:49:35 +10:00
committed by GitHub
parent e658227c04
commit b25155ef36
7 changed files with 403 additions and 85 deletions

View File

@@ -58,7 +58,7 @@ namespace gaseous_server.Classes
public static CollectionItem NewCollection(CollectionItem item)
{
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "INSERT INTO RomCollections (`Name`, Description, Platforms, Genres, Players, PlayerPerspectives, Themes, MinimumRating, MaximumRating, MaximumRomsPerPlatform, MaximumBytesPerPlatform, MaximumCollectionSizeInBytes, FolderStructure, IncludeBIOSFiles, BuiltStatus) VALUES (@name, @description, @platforms, @genres, @players, @playerperspectives, @themes, @minimumrating, @maximumrating, @maximumromsperplatform, @maximumbytesperplatform, @maximumcollectionsizeinbytes, @folderstructure, @includebiosfiles, @builtstatus); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
string sql = "INSERT INTO RomCollections (`Name`, Description, Platforms, Genres, Players, PlayerPerspectives, Themes, MinimumRating, MaximumRating, MaximumRomsPerPlatform, MaximumBytesPerPlatform, MaximumCollectionSizeInBytes, FolderStructure, IncludeBIOSFiles, AlwaysInclude, BuiltStatus) VALUES (@name, @description, @platforms, @genres, @players, @playerperspectives, @themes, @minimumrating, @maximumrating, @maximumromsperplatform, @maximumbytesperplatform, @maximumcollectionsizeinbytes, @folderstructure, @includebiosfiles, @alwaysinclude, @builtstatus); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("name", item.Name);
dbDict.Add("description", item.Description);
@@ -74,6 +74,7 @@ namespace gaseous_server.Classes
dbDict.Add("maximumcollectionsizeinbytes", Common.ReturnValueIfNull(item.MaximumCollectionSizeInBytes, -1));
dbDict.Add("folderstructure", Common.ReturnValueIfNull(item.FolderStructure, CollectionItem.FolderStructures.Gaseous));
dbDict.Add("includebiosfiles", Common.ReturnValueIfNull(item.IncludeBIOSFiles, 0));
dbDict.Add("alwaysinclude", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.AlwaysInclude, new List<CollectionItem.AlwaysIncludeItem>())));
dbDict.Add("builtstatus", CollectionItem.CollectionBuildStatus.WaitingForBuild);
DataTable romDT = db.ExecuteCMD(sql, dbDict);
long CollectionId = (long)romDT.Rows[0][0];
@@ -85,10 +86,10 @@ namespace gaseous_server.Classes
return collectionItem;
}
public static CollectionItem EditCollection(long Id, CollectionItem item)
public static CollectionItem EditCollection(long Id, CollectionItem item, bool ForceRebuild = true)
{
Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "UPDATE RomCollections SET `Name`=@name, Description=@description, Platforms=@platforms, Genres=@genres, Players=@players, PlayerPerspectives=@playerperspectives, Themes=@themes, MinimumRating=@minimumrating, MaximumRating=@maximumrating, MaximumRomsPerPlatform=@maximumromsperplatform, MaximumBytesPerPlatform=@maximumbytesperplatform, MaximumCollectionSizeInBytes=@maximumcollectionsizeinbytes, FolderStructure=@folderstructure, IncludeBIOSFiles=@includebiosfiles, BuiltStatus=@builtstatus WHERE Id=@id";
string sql = "UPDATE RomCollections SET `Name`=@name, Description=@description, Platforms=@platforms, Genres=@genres, Players=@players, PlayerPerspectives=@playerperspectives, Themes=@themes, MinimumRating=@minimumrating, MaximumRating=@maximumrating, MaximumRomsPerPlatform=@maximumromsperplatform, MaximumBytesPerPlatform=@maximumbytesperplatform, MaximumCollectionSizeInBytes=@maximumcollectionsizeinbytes, FolderStructure=@folderstructure, IncludeBIOSFiles=@includebiosfiles, AlwaysInclude=@alwaysinclude, BuiltStatus=@builtstatus WHERE Id=@id";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", Id);
dbDict.Add("name", item.Name);
@@ -105,19 +106,37 @@ namespace gaseous_server.Classes
dbDict.Add("maximumcollectionsizeinbytes", Common.ReturnValueIfNull(item.MaximumCollectionSizeInBytes, -1));
dbDict.Add("folderstructure", Common.ReturnValueIfNull(item.FolderStructure, CollectionItem.FolderStructures.Gaseous));
dbDict.Add("includebiosfiles", Common.ReturnValueIfNull(item.IncludeBIOSFiles, 0));
dbDict.Add("builtstatus", CollectionItem.CollectionBuildStatus.WaitingForBuild);
db.ExecuteCMD(sql, dbDict);
dbDict.Add("alwaysinclude", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.AlwaysInclude, new List<CollectionItem.AlwaysIncludeItem>())));
string CollectionZipFile = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + ".zip");
if (ForceRebuild == true)
{
dbDict.Add("builtstatus", CollectionItem.CollectionBuildStatus.WaitingForBuild);
if (File.Exists(CollectionZipFile))
{
Logging.Log(Logging.LogType.Warning, "Collections", "Deleting existing build of collection: " + item.Name);
File.Delete(CollectionZipFile);
}
}
else
{
if (File.Exists(CollectionZipFile))
{
dbDict.Add("builtstatus", CollectionItem.CollectionBuildStatus.Completed);
}
else
{
dbDict.Add("builtstatus", CollectionItem.CollectionBuildStatus.NoStatus);
}
}
db.ExecuteCMD(sql, dbDict);
CollectionItem collectionItem = GetCollection(Id);
if (collectionItem.BuildStatus == CollectionItem.CollectionBuildStatus.WaitingForBuild)
{
StartCollectionItemBuild(Id);
}
return collectionItem;
}
@@ -166,9 +185,32 @@ namespace gaseous_server.Classes
List<CollectionContents.CollectionPlatformItem> collectionPlatformItems = new List<CollectionContents.CollectionPlatformItem>();
// get platforms
List<long> platformids = new List<long>();
platformids.AddRange(collectionItem.Platforms);
List<long>? DynamicPlatforms = new List<long>();
DynamicPlatforms.AddRange(collectionItem.Platforms);
List<Platform> platforms = new List<Platform>();
if (collectionItem.Platforms.Count > 0) {
foreach (long PlatformId in collectionItem.Platforms) {
// add platforms with an inclusion status
foreach (CollectionItem.AlwaysIncludeItem alwaysIncludeItem in collectionItem.AlwaysInclude)
{
if (
alwaysIncludeItem.InclusionState == CollectionItem.AlwaysIncludeStatus.AlwaysInclude ||
alwaysIncludeItem.InclusionState == CollectionItem.AlwaysIncludeStatus.AlwaysExclude
)
{
if (!platformids.Contains(alwaysIncludeItem.PlatformId))
{
platformids.Add(alwaysIncludeItem.PlatformId);
}
}
}
// add dynamic platforms
if (DynamicPlatforms.Count > 0) {
foreach (long PlatformId in platformids) {
platforms.Add(Platforms.GetPlatform(PlatformId));
}
} else {
@@ -184,7 +226,20 @@ namespace gaseous_server.Classes
long TotalRomSize = 0;
long TotalGameCount = 0;
List<Game> games = GamesController.GetGames("",
bool isDynamic = false;
if (DynamicPlatforms.Contains((long)platform.Id))
{
isDynamic = true;
}
else if (DynamicPlatforms.Count == 0)
{
isDynamic = true;
}
List<Game> games = new List<Game>();
if (isDynamic == true)
{
games = GamesController.GetGames("",
platform.Id.ToString(),
string.Join(",", collectionItem.Genres),
string.Join(",", collectionItem.Players),
@@ -193,11 +248,45 @@ namespace gaseous_server.Classes
collectionItem.MinimumRating,
collectionItem.MaximumRating
);
}
CollectionContents.CollectionPlatformItem collectionPlatformItem = new CollectionContents.CollectionPlatformItem(platform);
collectionPlatformItem.Games = new List<CollectionContents.CollectionPlatformItem.CollectionGameItem>();
// add titles with an inclusion status
foreach (CollectionItem.AlwaysIncludeItem alwaysIncludeItem in collectionItem.AlwaysInclude)
{
if (
(
alwaysIncludeItem.InclusionState == CollectionItem.AlwaysIncludeStatus.AlwaysInclude ||
alwaysIncludeItem.InclusionState == CollectionItem.AlwaysIncludeStatus.AlwaysExclude
) && alwaysIncludeItem.PlatformId == platform.Id
)
{
Game AlwaysIncludeGame = Games.GetGame(alwaysIncludeItem.GameId, false, false, false);
CollectionContents.CollectionPlatformItem.CollectionGameItem gameItem = new CollectionContents.CollectionPlatformItem.CollectionGameItem(AlwaysIncludeGame);
gameItem.InclusionStatus = new CollectionItem.AlwaysIncludeItem();
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);
collectionPlatformItem.Games.Add(gameItem);
}
}
foreach (Game game in games) {
bool gameAlreadyInList = false;
foreach (CollectionContents.CollectionPlatformItem.CollectionGameItem existingGame in collectionPlatformItem.Games)
{
if (existingGame.Id == game.Id)
{
gameAlreadyInList = true;
}
}
if (gameAlreadyInList == false)
{
CollectionContents.CollectionPlatformItem.CollectionGameItem collectionGameItem = new CollectionContents.CollectionPlatformItem.CollectionGameItem(game);
List<Roms.GameRomItem> gameRoms = Roms.GetRoms((long)game.Id, (long)platform.Id);
@@ -241,6 +330,9 @@ namespace gaseous_server.Classes
}
}
}
}
collectionPlatformItem.Games.Sort((x, y) => x.Name.CompareTo(y.Name));
if (collectionPlatformItem.Games.Count > 0)
{
@@ -264,6 +356,8 @@ namespace gaseous_server.Classes
}
}
collectionPlatformItems.Sort((x, y) => x.Name.CompareTo(y.Name));
CollectionContents collectionContents = new CollectionContents();
collectionContents.Collection = collectionPlatformItems;
@@ -360,6 +454,21 @@ namespace gaseous_server.Classes
}
foreach (CollectionContents.CollectionPlatformItem.CollectionGameItem collectionGameItem in collectionPlatformItem.Games)
{
bool includeGame = false;
if (collectionGameItem.InclusionStatus == null)
{
includeGame = true;
}
else
{
if (collectionGameItem.InclusionStatus.InclusionState == CollectionItem.AlwaysIncludeStatus.AlwaysInclude)
{
includeGame = true;
}
}
if (includeGame == true)
{
string ZipGamePath = "";
switch (collectionItem.FolderStructure)
@@ -389,6 +498,7 @@ namespace gaseous_server.Classes
}
}
}
}
// compress to zip
Logging.Log(Logging.LogType.Information, "Collections", "Compressing collection");
@@ -434,6 +544,7 @@ namespace gaseous_server.Classes
string strPlayers = (string)Common.ReturnValueIfNull(row["Players"], "[ ]");
string strPlayerPerspectives = (string)Common.ReturnValueIfNull(row["PlayerPerspectives"], "[ ]");
string strThemes = (string)Common.ReturnValueIfNull(row["Themes"], "[ ]");
string strAlwaysInclude = (string)Common.ReturnValueIfNull(row["AlwaysInclude"], "[ ]");
CollectionItem item = new CollectionItem();
item.Id = (long)row["Id"];
@@ -451,6 +562,7 @@ namespace gaseous_server.Classes
item.MaximumCollectionSizeInBytes = (long)Common.ReturnValueIfNull(row["MaximumCollectionSizeInBytes"], (long)-1);
item.FolderStructure = (CollectionItem.FolderStructures)(int)Common.ReturnValueIfNull(row["FolderStructure"], 0);
item.IncludeBIOSFiles = (bool)row["IncludeBIOSFiles"];
item.AlwaysInclude = Newtonsoft.Json.JsonConvert.DeserializeObject<List<CollectionItem.AlwaysIncludeItem>>(strAlwaysInclude);
item.BuildStatus = (CollectionItem.CollectionBuildStatus)(int)Common.ReturnValueIfNull(row["BuiltStatus"], 0);
return item;
@@ -478,6 +590,7 @@ namespace gaseous_server.Classes
public long? MaximumCollectionSizeInBytes { get; set; }
public FolderStructures FolderStructure { get; set; } = FolderStructures.Gaseous;
public bool IncludeBIOSFiles { get; set; } = true;
public List<AlwaysIncludeItem> AlwaysInclude { get; set; }
[JsonIgnore]
public CollectionBuildStatus BuildStatus
@@ -546,6 +659,20 @@ namespace gaseous_server.Classes
Gaseous = 0,
RetroPie = 1
}
public class AlwaysIncludeItem
{
public long PlatformId { get; set; }
public long GameId { get; set; }
public AlwaysIncludeStatus InclusionState { get; set; }
}
public enum AlwaysIncludeStatus
{
None = 0,
AlwaysInclude = 1,
AlwaysExclude = 2
}
}
public class CollectionContents {
@@ -661,6 +788,8 @@ namespace gaseous_server.Classes
public string Slug { get; set; }
public long Cover { get; set;}
public CollectionItem.AlwaysIncludeItem InclusionStatus { get; set; }
public List<Roms.GameRomItem> Roms { get; set; }
public long RomSize {

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using gaseous_server.Classes;
using Microsoft.AspNetCore.Mvc;
namespace gaseous_server.Controllers
@@ -82,14 +83,14 @@ namespace gaseous_server.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetCollectionRomsPreview(Classes.Collections.CollectionItem Item)
{
//try
//{
try
{
return Ok(Classes.Collections.GetCollectionContent(Item));
//}
//catch (Exception ex)
//{
// return NotFound(ex);
//}
}
catch (Exception ex)
{
return NotFound(ex);
}
}
/// <summary>
@@ -159,7 +160,43 @@ namespace gaseous_server.Controllers
{
try
{
return Ok(Classes.Collections.EditCollection(CollectionId, Item));
return Ok(Classes.Collections.EditCollection(CollectionId, Item, true));
}
catch
{
return NotFound();
}
}
/// <summary>
/// Edits an existing collection
/// </summary>
/// <param name="CollectionId"></param>
/// <param name="Item"></param>
/// <returns></returns>
[HttpPatch]
[Route("{CollectionId}/AlwaysInclude")]
[ProducesResponseType(typeof(Classes.Collections.CollectionItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult EditCollectionAlwaysInclude(long CollectionId, [FromQuery]bool Rebuild, [FromBody]Collections.CollectionItem.AlwaysIncludeItem Inclusion)
{
try
{
Collections.CollectionItem collectionItem = Classes.Collections.GetCollection(CollectionId);
bool ItemFound = false;
foreach (Collections.CollectionItem.AlwaysIncludeItem includeItem in collectionItem.AlwaysInclude)
{
if (includeItem.PlatformId == Inclusion.PlatformId && includeItem.GameId == Inclusion.GameId)
{
ItemFound = true;
}
}
if (ItemFound == false)
{
collectionItem.AlwaysInclude.Add(Inclusion);
}
return Ok(Classes.Collections.EditCollection(CollectionId, collectionItem, Rebuild));
}
catch
{

View File

@@ -0,0 +1,74 @@
<p>
Select collection to add game to:
</p>
<table style="width: 100%;">
<tr>
<td colspan="2">
<select id="collection_addgame" style="width: 100%;"></select>
</td>
</tr>
<tr>
<td style="padding-top: 15px;">
<input type="checkbox" id="collection_rebuild" style="margin-right: 5px;" /><label for="collection_rebuild">Rebuild Collection</label>
</td>
<td style="text-align: right; padding-top: 15px;">
<button id="collection_cancelbtn" value="Cancel" onclick="closeSubDialog();">Cancel</button>
<button id="collection_addbtn" value="Add" onclick="AddToCollection();">Add</button>
</td>
</tr>
</table>
<script type="text/javascript">
document.getElementById('collection_addgame').innerHTML = "<option value='0' selected='selected'>Select collection</option>";
$('#collection_addgame').select2({
ajax: {
url: '/api/v1/Collections',
placeholder: 'Select collection',
processResults: function (data) {
var arr = [];
for (var i = 0; i < data.length; i++) {
arr.push({
id: data[i].id,
text: data[i].name
});
}
return {
results: arr
};
}
}
});
function AddToCollection() {
var CollectionId = Number(document.getElementById('collection_addgame').value);
var PlatformId = modalVariables;
var GameId = getQueryString('id', 'int');
var RebuildCollection = '';
if (document.getElementById('collection_rebuild').checked == true) {
RebuildCollection = '?Rebuild=true';
}
var responseBody = {
"PlatformId": PlatformId,
"GameId": GameId,
"InclusionState": "AlwaysInclude"
};
if (CollectionId != 0) {
ajaxCall(
'/api/v1/Collections/' + CollectionId + '/AlwaysInclude' + RebuildCollection,
'PATCH',
function (result) {
closeSubDialog();
},
function (error) {
console.log(JSON.stringify(error));
},
JSON.stringify(responseBody)
);
}
}
</script>

View File

@@ -254,6 +254,8 @@
$('#collection_includebios').select2();
if (modalVariables) {
var modalAlwaysInclude = [];
// edit mode
ajaxCall(
'/api/v1/Collections/' + modalVariables,
@@ -270,6 +272,8 @@
$('#collection_directorylayout').val(result.folderStructure).trigger('change');
}
$('#collection_includebios').val(result.includeBIOSFiles.toString()).trigger('change');
modalAlwaysInclude = result.alwaysInclude;
console.log(JSON.stringify(modalAlwaysInclude));
// fill select2 controls
$.ajax(
@@ -372,6 +376,25 @@
var players = GetDropDownIds('#collection_players');
var playerperspectives = GetDropDownIds('#collection_playerperspectives');
var themes = GetDropDownIds('#collection_themes');
var alwaysInclude = [];
var alwaysIncludeSelections = document.getElementsByName('collections_preview_always');
if (alwaysIncludeSelections.length > 0) {
for (var i = 0; i < alwaysIncludeSelections.length; i++) {
if (alwaysIncludeSelections[i].value != "None") {
var alwaysIncludeItem = {
"platformId": Number(alwaysIncludeSelections[i].getAttribute('data-platform')),
"gameId": Number(alwaysIncludeSelections[i].getAttribute('data-game')),
"inclusionState": alwaysIncludeSelections[i].value
};
alwaysInclude.push(alwaysIncludeItem);
}
}
} else {
alwaysInclude = modalAlwaysInclude;
}
modalAlwaysInclude = alwaysInclude;
console.log(JSON.stringify(modalAlwaysInclude));
var item = {
"name": document.getElementById('collection_name').value,
@@ -387,7 +410,8 @@
"maximumBytesPerPlatform": GetNumberFieldValue('collection_maxplatformsize'),
"maximumCollectionSizeInBytes": GetNumberFieldValue('collection_maxcollectionsize'),
"folderStructure": GetNumberFieldValue('collection_directorylayout', "Standard"),
"includeBIOSFiles": GetBooleanFieldValue('collection_includebios')
"includeBIOSFiles": GetBooleanFieldValue('collection_includebios'),
"alwaysInclude": alwaysInclude
}
return item;
@@ -451,6 +475,7 @@
}
function DisplayPreview(data, targetDiv) {
console.log(JSON.stringify(data));
var container = document.getElementById(targetDiv);
container.innerHTML = '';
@@ -494,7 +519,48 @@
var gameTitleCell = document.createElement('th');
gameTitleCell.setAttribute('colspan', 2);
gameTitleCell.className = 'collections_preview_gametitlecell';
gameTitleCell.innerHTML = gameItem.name;
// game always include popup
var gameTitleInclusion = document.createElement('select');
gameTitleInclusion.id = 'collections_preview_always_' + platformItem.id + '_' + gameItem.id;
gameTitleInclusion.name = 'collections_preview_always';
gameTitleInclusion.setAttribute('data-platform', platformItem.id);
gameTitleInclusion.setAttribute('data-game', gameItem.id);
gameTitleInclusion.style.float = 'right';
var gameTitleInclusionOptions = [
{
"value": "None",
"text": "Automatic"
},
{
"value": "AlwaysInclude",
"text": "Always Include"
},
{
"value": "AlwaysExclude",
"text": "Always Exclude"
}
];
for (var i = 0; i < gameTitleInclusionOptions.length; i++) {
var gameTitleInclusionOption = document.createElement('option');
gameTitleInclusionOption.value = gameTitleInclusionOptions[i].value;
gameTitleInclusionOption.innerHTML = gameTitleInclusionOptions[i].text;
if (gameItem.inclusionStatus) {
if (gameItem.inclusionStatus.inclusionState == gameTitleInclusionOptions[i].value) {
gameTitleInclusionOption.selected = 'selected';
}
}
gameTitleInclusion.appendChild(gameTitleInclusionOption);
}
gameTitleCell.appendChild(gameTitleInclusion);
// game title label
var gameTitleLabel = document.createElement('span');
gameTitleLabel.innerHTML = gameItem.name;
gameTitleCell.appendChild(gameTitleLabel);
gameItemRow.appendChild(gameTitleCell);
@@ -541,7 +607,7 @@
container.appendChild(collectionTable);
var collectionSize = document.getElementById('collectionedit_previewbox_size');
collectionSize.innerHTML = "Collection size: " + formatBytes(data.collectionProjectedSizeBytes);
collectionSize.innerHTML = "Estimated uncompressed collection size: " + formatBytes(data.collectionProjectedSizeBytes);
}
}

View File

@@ -368,7 +368,7 @@
var platformRow = document.createElement('tr');
var platformHeader = document.createElement('th');
platformHeader.setAttribute('colspan', 6);
platformHeader.innerHTML = result[i].platform.name;
platformHeader.innerHTML = '<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;
platformRow.appendChild(platformHeader);
newTable.appendChild(platformRow);
}
@@ -666,4 +666,9 @@
submodal.style.display = "none";
}
function ShowCollectionDialog(platformId) {
modalVariables = platformId;
showSubDialog("collectionaddgame");
}
</script>

View File

@@ -143,6 +143,12 @@ h3 {
filter: invert(100%);
}
.banner_button_image_smaller {
height: 16px;
width: 16px;
margin: unset;
}
#banner_header {
background-color: rgba(0, 22, 56, 0.8);
backdrop-filter: blur(8px);

View File

@@ -12,4 +12,5 @@ ADD COLUMN `MetadataVersion` INT NULL DEFAULT 1;
ALTER TABLE `RomCollections`
ADD COLUMN `FolderStructure` INT NULL DEFAULT 0 AFTER `MaximumCollectionSizeInBytes`,
ADD COLUMN `IncludeBIOSFiles` BOOLEAN NULL DEFAULT 0 AFTER `FolderStructure`;
ADD COLUMN `IncludeBIOSFiles` BOOLEAN NULL DEFAULT 0 AFTER `FolderStructure`,
ADD COLUMN `AlwaysInclude` JSON NULL AFTER `IncludeBIOSFiles`;