Compare commits

...

1 Commits

Author SHA1 Message Date
Michael Green
dc2a6b4638 feat: add an upload button to ease adding game files (#29)
* feat: API support for uploading ROM’s

* fix: downloads of files larger than approx 300MB would cause an out of memory error

* fix: resolved broken bios path

* feat: added an upload button
2023-07-22 00:46:26 +10:00
15 changed files with 275 additions and 21 deletions

View File

@@ -768,21 +768,9 @@ namespace gaseous_server.Controllers
string romFilePath = rom.Path; string romFilePath = rom.Path;
if (System.IO.File.Exists(romFilePath)) if (System.IO.File.Exists(romFilePath))
{ {
string filename = Path.GetFileName(romFilePath); FileStream content = new FileStream(romFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
string filepath = romFilePath; FileStreamResult response = File(content, "application/octet-stream", rom.Name);
byte[] filedata = System.IO.File.ReadAllBytes(filepath); return response;
string contentType = "application/octet-stream";
var cd = new System.Net.Mime.ContentDisposition
{
FileName = filename,
Inline = false,
};
Response.Headers.Add("Content-Disposition", cd.ToString());
Response.Headers.Add("Cache-Control", "public, max-age=604800");
return File(filedata, contentType);
} }
else else
{ {

View File

@@ -0,0 +1,80 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using gaseous_server.Classes.Metadata;
using gaseous_tools;
using IGDB.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting;
using Org.BouncyCastle.Asn1.X509;
using static gaseous_server.Classes.Metadata.AgeRatings;
namespace gaseous_server.Controllers
{
[Route("api/v1/[controller]")]
[ApiController]
public class RomsController : ControllerBase
{
[HttpPost]
[ProducesResponseType(typeof(List<IFormFile>), StatusCodes.Status200OK)]
[RequestSizeLimit(long.MaxValue)]
[DisableRequestSizeLimit, RequestFormLimits(MultipartBodyLengthLimit = long.MaxValue, ValueLengthLimit = int.MaxValue)]
public async Task<IActionResult> UploadRom(List<IFormFile> files)
{
Guid sessionid = Guid.NewGuid();
string workPath = Path.Combine(Config.LibraryConfiguration.LibraryUploadDirectory, sessionid.ToString());
long size = files.Sum(f => f.Length);
List<Dictionary<string, object>> UploadedFiles = new List<Dictionary<string, object>>();
foreach (IFormFile formFile in files)
{
if (formFile.Length > 0)
{
Guid FileId = Guid.NewGuid();
string filePath = Path.Combine(workPath, Path.GetFileName(formFile.FileName));
if (!Directory.Exists(workPath))
{
Directory.CreateDirectory(workPath);
}
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
Dictionary<string, object> UploadedFile = new Dictionary<string, object>();
UploadedFile.Add("id", FileId.ToString());
UploadedFile.Add("originalname", Path.GetFileName(formFile.FileName));
UploadedFile.Add("fullpath", filePath);
UploadedFiles.Add(UploadedFile);
}
}
}
// Process uploaded files
// Don't rely on or trust the FileName property without validation.
foreach (Dictionary<string, object> UploadedFile in UploadedFiles)
{
Classes.ImportGame.ImportGameFile((string)UploadedFile["fullpath"]);
}
if (Directory.Exists(workPath))
{
Directory.Delete(workPath, true);
}
return Ok(new { count = files.Count, size });
}
}
}

View File

@@ -1,7 +1,9 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using gaseous_server; using gaseous_server;
using gaseous_tools; using gaseous_tools;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Server.Kestrel.Core;
Logging.Log(Logging.LogType.Information, "Startup", "Starting Gaseous Server"); Logging.Log(Logging.LogType.Information, "Startup", "Starting Gaseous Server");
@@ -63,6 +65,22 @@ builder.Services.AddControllers(options =>
}); });
}); });
// set max upload size
builder.Services.Configure<IISServerOptions>(options =>
{
options.MaxRequestBodySize = int.MaxValue;
});
builder.Services.Configure<KestrelServerOptions>(options =>
{
options.Limits.MaxRequestBodySize = int.MaxValue;
});
builder.Services.Configure<FormOptions>(options =>
{
options.ValueLengthLimit = int.MaxValue;
options.MultipartBodyLengthLimit = int.MaxValue;
options.MultipartHeadersLengthLimit = int.MaxValue;
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<path d="M230.4,358.4V76.8c0-14.13,11.48-25.6,25.6-25.6c14.15,0,25.6,11.47,25.6,25.6v281.6h25.6c14.15,0,25.6,11.48,25.6,25.6
c0,14.15-11.45,25.6-25.6,25.6h-25.6v25.6c0,14.15-11.45,25.6-25.6,25.6c-14.13,0-25.6-11.45-25.6-25.6v-25.6h-25.6
c-14.13,0-25.6-11.45-25.6-25.6c0-14.13,11.48-25.6,25.6-25.6H230.4z M409.6,102.4V76.8c0-14.13,11.48-25.6,25.6-25.6
c14.15,0,25.6,11.47,25.6,25.6v25.6h25.6c14.15,0,25.6,11.47,25.6,25.6c0,14.15-11.45,25.6-25.6,25.6h-25.6v281.6
c0,14.15-11.45,25.6-25.6,25.6c-14.13,0-25.6-11.45-25.6-25.6V153.6H384c-14.13,0-25.6-11.45-25.6-25.6
c0-14.13,11.48-25.6,25.6-25.6H409.6z M102.4,179.2H128c14.15,0,25.6,11.48,25.6,25.6c0,14.15-11.45,25.6-25.6,25.6h-25.6v204.8
c0,14.15-11.45,25.6-25.6,25.6c-14.13,0-25.6-11.45-25.6-25.6V230.4H25.6C11.48,230.4,0,218.95,0,204.8
c0-14.13,11.48-25.6,25.6-25.6h25.6V76.8c0-14.13,11.47-25.6,25.6-25.6c14.15,0,25.6,11.47,25.6,25.6V179.2z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<path d="M509,261.1c-3.3-12.7-9.3-24-16.9-33.4c-5.7-7.1-12.4-13.2-19.6-18.4c-10.8-7.8-22.8-13.6-35-17.7
c-7.8-2.5-15.7-4.4-23.5-5.6c-3-18-8.4-34.6-15.7-49.6c-13.6-27.9-34-50.1-58.7-65.3C315,55.8,286.2,47.8,256,47.8
c-19,0-36.6,3.2-52.4,8.8c-11.9,4.2-22.7,9.8-32.5,16.4c-14.7,9.9-26.9,22-36.7,35.2c-7.5,10.2-13.6,21.1-18.1,32.2
c-12.9,2-25.3,5.3-37.1,10c-11.1,4.5-21.6,10.2-31.1,17.1c-14.3,10.4-26.4,23.7-34.8,39.5c-4.3,8-7.6,16.5-9.9,25.6
C1.2,241.7,0,251.4,0,261.4c0,17.9,3.9,34.8,11,49.8c5.3,11.3,12.4,21.5,20.9,30.4c12.7,13.4,28.5,23.9,46.2,31.1
c17.7,7.2,37.4,11,57.9,11h56c8.8,0,16-7.2,16-16s-7.2-16-16-16h-56c-14.7,0-28.7-2.4-41.2-6.9c-9.4-3.3-18.1-7.8-25.7-13.2
c-11.5-8.1-20.7-18.3-27.1-30.1c-6.4-11.8-9.9-25.2-10-40.2c0-13.4,2.8-25.1,7.6-35.4c3.6-7.8,8.4-14.8,14.3-21
c8.8-9.4,20-17.2,32.9-23c12.9-5.8,27.5-9.6,42.7-11c6.4-0.6,11.8-4.9,13.7-11c3.2-9.8,8-19.9,14.4-29.3
c4.8-7.1,10.5-13.8,17.1-19.9c9.8-9.1,21.5-16.8,35-22.3c13.5-5.4,28.9-8.7,46.3-8.7c16.4,0,32,2.9,46.4,8.4
c21.6,8.2,40.3,22.2,54.6,41.3s24.1,43.5,27.1,72.8c0.8,7.8,7.3,13.9,15.1,14.3c9.2,0.5,19.3,2.3,28.9,5.5
c7.2,2.4,14.2,5.6,20.5,9.5c4.7,2.9,9.1,6.2,12.9,9.8c5.8,5.5,10.3,11.6,13.5,18.7c3.2,7,5,14.9,5,24.2c0,5.7-0.6,10.9-1.7,15.7
c-1.9,8.4-5.2,15.6-9.8,21.8c-3.4,4.7-7.6,8.9-12.4,12.6c-7.3,5.5-16.2,9.9-26.4,12.9s-21.6,4.6-33.7,4.6h-76c-8.8,0-16,7.2-16,16
s7.2,16,16,16h76c15.2,0,29.8-2,43.4-6.1c10.2-3.1,19.8-7.3,28.6-12.8c6.6-4.1,12.6-8.9,18-14.4c8.1-8.3,14.7-18.1,19.1-29.3
c4.5-11.2,6.8-23.6,6.8-37C511.9,276.1,510.9,268.4,509,261.1z"/>
<path d="M308.7,267.1c6.2,6.2,16.4,6.2,22.6,0c6.3-6.2,6.3-16.4,0-22.6l-64-64c-6.2-6.2-16.4-6.2-22.6,0l-64,64
c-6.2,6.2-6.2,16.4,0,22.6c6.2,6.2,16.4,6.2,22.6,0l36.7-36.7v217.8c0,8.8,7.2,16,16,16s16-7.2,16-16V230.4L308.7,267.1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -6,7 +6,9 @@
<script src="/scripts/jquery-3.6.0.min.js"></script> <script src="/scripts/jquery-3.6.0.min.js"></script>
<script src="/scripts/moment.js"></script> <script src="/scripts/moment.js"></script>
<link href="/styles/select2.min.css" rel="stylesheet" /> <link href="/styles/select2.min.css" rel="stylesheet" />
<link rel="stylesheet" href="/styles/dropzone.min.css" type="text/css" />
<script src="/scripts/select2.min.js"></script> <script src="/scripts/select2.min.js"></script>
<script src="/scripts/dropzone.min.js"></script>
<script src="/scripts/main.js" type="text/javascript"></script> <script src="/scripts/main.js" type="text/javascript"></script>
<script src="/scripts/filterformating.js" type="text/javascript"></script> <script src="/scripts/filterformating.js" type="text/javascript"></script>
<script src="/scripts/gamesformating.js" type="text/javascript"></script> <script src="/scripts/gamesformating.js" type="text/javascript"></script>
@@ -23,8 +25,12 @@
<div id="banner_header"> <div id="banner_header">
<div id="banner_header_label">Gaseous Games</div> <div id="banner_header_label">Gaseous Games</div>
</div> </div>
<div id="banner_upload" onclick="showDialog('upload');">
<img src="/images/upload.svg" alt="Upload" title="Upload" id="banner_upload_image" />
<span id="banner_upload_label">Upload Games</span>
</div>
<div id="banner_cog" onclick="window.location.href = '/index.html?page=settings';"> <div id="banner_cog" onclick="window.location.href = '/index.html?page=settings';">
<img src="/images/cog.jpg" alt="System" id="banner_system_image" /> <img src="/images/settings.svg" alt="Settings" title="Settings" id="banner_system_image" />
</div> </div>
<div id="content"> <div id="content">

View File

@@ -20,6 +20,8 @@
// Path to the data directory // Path to the data directory
EJS_pathtodata = '/emulators/EmulatorJS/data/'; EJS_pathtodata = '/emulators/EmulatorJS/data/';
EJS_DEBUG_XX = true; EJS_DEBUG_XX = false;
EJS_startOnLoaded = false;
</script> </script>
<script src='/emulators/EmulatorJS/data/loader.js'></script> <script src='/emulators/EmulatorJS/data/loader.js'></script>

View File

@@ -85,7 +85,8 @@
</div> </div>
</div> </div>
<script type="text/javascript">document.getElementById('modal-heading').innerHTML = "Properties"; <script type="text/javascript">
document.getElementById('modal-heading').innerHTML = "Properties";
var gameId = urlParams.get('id'); var gameId = urlParams.get('id');

View File

@@ -0,0 +1,72 @@
<!-- The Modal -->
<div id="myModalSub" class="modal">
<!-- Modal content -->
<div class="modal-content-sub">
<span id="modal-close-sub" class="close">&times;</span>
<div id="modal-content-sub">Some text in the Modal..</div>
</div>
</div>
<div>
<div id="upload_target" class="dropzone"></div>
</div>
<script type="text/javascript">
document.getElementById('modal-heading').innerHTML = "Upload";
var myDropzone = new Dropzone("div#upload_target", {
url: "/api/v1/Roms",
autoProcessQueue: true,
uploadMultiple: true,
paramName: myParamName,
maxFilesize: 60000,
createImageThumbnails: false,
disablePreviews: false
});
function myParamName() {
return "files";
}
function showSubDialog(dialogPage, variables) {
// Get the modal
var submodal = document.getElementById("myModalSub");
// Get the modal content
var subModalContent = document.getElementById("modal-content-sub");
// Get the button that opens the modal
var subbtn = document.getElementById("romDelete");
// Get the <span> element that closes the modal
var subspan = document.getElementById("modal-close-sub");
// When the user clicks on the button, open the modal
submodal.style.display = "block";
// When the user clicks on <span> (x), close the modal
subspan.onclick = function () {
submodal.style.display = "none";
subModalContent.innerHTML = "";
subModalVariables = null;
}
subModalVariables = modalVariables;
$('#modal-content-sub').load('/pages/dialogs/' + dialogPage + '.html');
}
function closeSubDialog() {
// Get the modal
var submodal = document.getElementById("myModalSub");
// Get the modal content
var subModalContent = document.getElementById("modal-content-sub");
submodal.style.display = "none";
subModalContent.innerHTML = "";
subModalVariables = null;
}
</script>

View File

@@ -86,7 +86,7 @@
function loadEmulator() { function loadEmulator() {
if (availableEmuBios.length > 0) { if (availableEmuBios.length > 0) {
var emulatorbiosselect = document.getElementById('emulatorbiosselector'); var emulatorbiosselect = document.getElementById('emulatorbiosselector');
emuBios = emulatorbiosselect.value; emuBios = '/api/v1/Bios/' + platformId + '/' + emulatorbiosselect.value;
} else { } else {
emuBios = ''; emuBios = '';
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -105,6 +105,28 @@ h3 {
height: 30px; height: 30px;
} }
#banner_upload {
background-color: white;
position: fixed;
top: 0px;
right: 41px;
height: 40px;
align-items: center;
justify-content: center;
padding-left: 10px;
padding-right: 10px;
padding-top: 0px;
padding-bottom: 0px;
margin: 0px;
display: flex;
color: black;
}
#banner_upload:hover {
cursor: pointer;
background-color: lightgrey;
}
#banner_cog { #banner_cog {
background-color: white; background-color: white;
position: fixed; position: fixed;
@@ -121,11 +143,18 @@ h3 {
#banner_cog:hover { #banner_cog:hover {
cursor: pointer; cursor: pointer;
background-color: lightgrey;
} }
#banner_system_image { #banner_system_image {
height: 30px; height: 20px;
width: 30px; width: 20px;
}
#banner_upload_image {
height: 20px;
width: 20px;
margin-right: 5px;
} }
#banner_header { #banner_header {
@@ -668,3 +697,12 @@ button:disabled {
-webkit-backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
padding: 5px; padding: 5px;
} }
#upload_target {
background-color: #383838;
color: white;
border: none;
height: 310px;
margin-top: 10px;
overflow: auto;
}

View File

@@ -343,6 +343,14 @@ namespace gaseous_tools
} }
} }
public string LibraryUploadDirectory
{
get
{
return Path.Combine(LibraryRootDirectory, "Upload");
}
}
public string LibraryMetadataDirectory public string LibraryMetadataDirectory
{ {
get get
@@ -394,6 +402,7 @@ namespace gaseous_tools
if (!Directory.Exists(LibraryImportDirectory)) { Directory.CreateDirectory(LibraryImportDirectory); } if (!Directory.Exists(LibraryImportDirectory)) { Directory.CreateDirectory(LibraryImportDirectory); }
if (!Directory.Exists(LibraryDataDirectory)) { Directory.CreateDirectory(LibraryDataDirectory); } if (!Directory.Exists(LibraryDataDirectory)) { Directory.CreateDirectory(LibraryDataDirectory); }
if (!Directory.Exists(LibraryBIOSDirectory)) { Directory.CreateDirectory(LibraryBIOSDirectory); } if (!Directory.Exists(LibraryBIOSDirectory)) { Directory.CreateDirectory(LibraryBIOSDirectory); }
if (!Directory.Exists(LibraryUploadDirectory)) { Directory.CreateDirectory(LibraryUploadDirectory); }
if (!Directory.Exists(LibraryMetadataDirectory)) { Directory.CreateDirectory(LibraryMetadataDirectory); } if (!Directory.Exists(LibraryMetadataDirectory)) { Directory.CreateDirectory(LibraryMetadataDirectory); }
if (!Directory.Exists(LibrarySignatureImportDirectory)) { Directory.CreateDirectory(LibrarySignatureImportDirectory); } if (!Directory.Exists(LibrarySignatureImportDirectory)) { Directory.CreateDirectory(LibrarySignatureImportDirectory); }
if (!Directory.Exists(LibrarySignatureImportDirectory_TOSEC)) { Directory.CreateDirectory(LibrarySignatureImportDirectory_TOSEC); } if (!Directory.Exists(LibrarySignatureImportDirectory_TOSEC)) { Directory.CreateDirectory(LibrarySignatureImportDirectory_TOSEC); }