diff --git a/.gitignore b/.gitignore index ded541e..aec56e9 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ create-schematic-gen-site-test-* *.obj *.lst *.jar +extracts/ \ No newline at end of file diff --git a/dub.json b/dub.json index ce30453..469ea33 100644 --- a/dub.json +++ b/dub.json @@ -4,7 +4,7 @@ ], "copyright": "Copyright © 2023, Andrew Lalis", "dependencies": { - "handy-httpd": "~>7.6.1", + "handy-httpd": "~>7.6.3", "slf4d": "~>2.4.1" }, "description": "HTTP server for generating schematic materials lists.", diff --git a/dub.selections.json b/dub.selections.json index cda082b..d350906 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -1,7 +1,7 @@ { "fileVersion": 1, "versions": { - "handy-httpd": "7.6.1", + "handy-httpd": "7.6.3", "httparsed": "1.2.1", "slf4d": "2.4.1", "streams": "3.5.0" diff --git a/site/files.js b/site/files.js index 3139f49..13a018d 100644 --- a/site/files.js +++ b/site/files.js @@ -1,6 +1,8 @@ const form = document.getElementById("schematic-form"); +const resultContainer = document.getElementById("result-container"); form.onsubmit = async (e) => { e.preventDefault(); + resultContainer.innerHTML = ""; console.log(e); const data = new FormData(form); console.log(data); @@ -10,9 +12,12 @@ form.onsubmit = async (e) => { body: data }); const result = await response.json(); - console.log("Success:", result); + const extractId = result.extractId; + const url = window.location.origin + "/extracts/" + extractId; + resultContainer.innerHTML = `

Copy this URL, and provide it to the computer: ${url}

`; form.reset(); } catch (error) { console.error("Error: " + error); + resultContainer.innerHTML = `

An error occurred: ${error}

`; } }; \ No newline at end of file diff --git a/site/index.html b/site/index.html index 5cc0148..d287d61 100644 --- a/site/index.html +++ b/site/index.html @@ -14,6 +14,9 @@ +
+ +
diff --git a/source/app.d b/source/app.d index c46acbf..84af2fc 100644 --- a/source/app.d +++ b/source/app.d @@ -3,6 +3,12 @@ import handy_httpd.handlers.path_delegating_handler; import handy_httpd.handlers.file_resolving_handler; import slf4d; import slf4d.default_provider; +import std.path; +import std.file; + +const EXTRACTS_DIR = "extracts"; +const EXTRACT_FILENAME = "__EXTRACT__.json"; +const EXTRACT_COMMAND = ["java", "-jar", "materials-extractor-v1.0.0.jar"]; void main() { auto provider = new shared DefaultProvider(true, Levels.INFO); @@ -15,7 +21,7 @@ void main() { PathDelegatingHandler handler = new PathDelegatingHandler(); handler.addMapping("POST", "/extracts", &handleExtract); - handler.addMapping("GET", "/extracts/{extractId:uint}", &getExtract); + handler.addMapping("GET", "/extracts/{extractId}", &getExtract); FileResolvingHandler fileHandler = new FileResolvingHandler("site", DirectoryResolutionStrategies.serveIndexFiles); handler.addMapping("/**", fileHandler); @@ -23,15 +29,46 @@ void main() { } void handleExtract(ref HttpRequestContext ctx) { - MultipartFormData data = ctx.request.readBodyAsMultipartFormData(); - infoF!"Read %d files: "(data.elements.length); - import std.json; - JSONValue result = JSONValue.emptyObject; - result.object["extractId"] = JSONValue(42); - ctx.response.writeBodyString(result.toJSON(), "application/json"); + import std.uuid; + import std.process; + import std.stdio; + + immutable UUID extractId = randomUUID(); + MultipartFormData data = ctx.request.readBodyAsMultipartFormData(); + // TODO: Validate data (unique filenames, no non-file elements, etc.) + const extractDir = buildPath(EXTRACTS_DIR, extractId.toString()); + if (!exists(extractDir)) { + mkdirRecurse(extractDir); + } + string[] filenames; + foreach (MultipartElement element; data.elements) { + if (element.filename.isNull) continue; + const filePath = buildPath(extractDir, element.filename.get()); + std.file.write(filePath, element.content); + filenames ~= filePath; + } + const extractJsonPath = buildPath(extractDir, EXTRACT_FILENAME); + infoF!"Running extract process on files: %s"(filenames); + Pid pid = spawnProcess(EXTRACT_COMMAND ~ filenames, std.stdio.stdin, File(extractJsonPath, "w")); + int exitCode = wait(pid); + infoF!"Exit code: %d"(exitCode); + if (exitCode != 0) { + ctx.response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + ctx.response.writeBodyString(readText(extractJsonPath)); + } else { + JSONValue result = JSONValue.emptyObject; + result.object["extractId"] = JSONValue(extractId.toString()); + ctx.response.writeBodyString(result.toJSON(), "application/json"); + // Remove schematic files after we are done. + foreach (string schematicFile; filenames) { + std.file.remove(schematicFile); + } + } } void getExtract(ref HttpRequestContext ctx) { - infoF!"Getting extract: %d"(ctx.request.getPathParamAs!uint("extractId")); + string extractId = ctx.request.getPathParamAs!string("extractId"); + const extractFile = buildPath(EXTRACTS_DIR, extractId, EXTRACT_FILENAME); + fileResponse(ctx.response, extractFile, "application/json"); }