124 lines
4.2 KiB
D
124 lines
4.2 KiB
D
|
module csgs.extract;
|
||
|
|
||
|
import handy_httpd;
|
||
|
import slf4d;
|
||
|
|
||
|
import std.typecons;
|
||
|
import std.file;
|
||
|
import std.path;
|
||
|
|
||
|
const EXTRACTS_DIR = "extracts";
|
||
|
const EXTRACT_FILENAME = "__EXTRACT__.json";
|
||
|
const EXTRACT_COMMAND = ["java", "-jar", "materials-extractor-v1.0.0.jar"];
|
||
|
|
||
|
bool validateExtractRequest(ref MultipartFormData data, ref HttpResponse response) {
|
||
|
if (data.elements.length < 2) {
|
||
|
response.setStatus(HttpStatus.BAD_REQUEST);
|
||
|
response.writeBodyString("Requires at least 2 form data elements (schematic file and count).");
|
||
|
return false;
|
||
|
}
|
||
|
uint nextElementIdx = 0;
|
||
|
while (nextElementIdx < data.elements.length) {
|
||
|
MultipartElement element = data.elements[nextElementIdx++];
|
||
|
if (element.name == "schematics") {
|
||
|
if (element.filename.isNull || element.filename.get().length < 5 || element.content.length < 10) {
|
||
|
response.setStatus(HttpStatus.BAD_REQUEST);
|
||
|
response.writeBodyString("Invalid or missing schematic file.");
|
||
|
return false;
|
||
|
}
|
||
|
const string filename = element.filename.get();
|
||
|
if (nextElementIdx == data.elements.length) {
|
||
|
response.setStatus(HttpStatus.BAD_REQUEST);
|
||
|
response.writeBodyString("Missing count element for schematic: " ~ filename);
|
||
|
return false;
|
||
|
}
|
||
|
MultipartElement countElement = data.elements[nextElementIdx++];
|
||
|
import std.conv;
|
||
|
try {
|
||
|
immutable uint count = countElement.content.to!uint;
|
||
|
if (count < 1 || count > 1000) throw new Exception("out of range: count should be between 1 and 1000, inclusive.");
|
||
|
} catch (Exception e) {
|
||
|
response.setStatus(HttpStatus.BAD_REQUEST);
|
||
|
response.writeBodyString("Invalid count element: " ~ e.msg);
|
||
|
return false;
|
||
|
}
|
||
|
} else if (element.name == "processing-terminal") {
|
||
|
// TODO: Check processing-terminal format.
|
||
|
} else {
|
||
|
response.setStatus(HttpStatus.BAD_REQUEST);
|
||
|
response.writeBodyString("Unknown element: " ~ element.name);
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
string doExtract(ref MultipartFormData data) {
|
||
|
import std.json;
|
||
|
import std.uuid;
|
||
|
import std.process;
|
||
|
|
||
|
immutable UUID extractId = randomUUID();
|
||
|
immutable string extractIdStr = extractId.toString();
|
||
|
immutable string extractDir = buildPath(EXTRACTS_DIR, extractIdStr);
|
||
|
if (!exists(extractDir)) {
|
||
|
mkdirRecurse(extractDir);
|
||
|
}
|
||
|
string[] filenames;
|
||
|
uint[] counts;
|
||
|
foreach (MultipartElement element; data.elements) {
|
||
|
if (element.name == "schematics") {
|
||
|
const filePath = buildPath(extractDir, element.filename.get());
|
||
|
std.file.write(filePath, element.content);
|
||
|
filenames ~= filePath;
|
||
|
} else if (element.name == "counts") {
|
||
|
import std.conv;
|
||
|
immutable uint count = element.content.to!uint;
|
||
|
counts ~= count;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
infoF!"Running extract process on files: %s"(filenames);
|
||
|
Nullable!string extractorPath = getExtractorProgramPath();
|
||
|
if (extractorPath.isNull) {
|
||
|
throw new HttpStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Could not find extractor program.");
|
||
|
}
|
||
|
auto extractionResult = execute(["java", "-jar", extractorPath.get()] ~ filenames);
|
||
|
immutable int exitCode = extractionResult.status;
|
||
|
infoF!"Exit code: %d"(exitCode);
|
||
|
rmdirRecurse(extractDir);
|
||
|
if (exitCode != 0) {
|
||
|
throw new HttpStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Extraction program failed.");
|
||
|
} else {
|
||
|
immutable string extractJsonPath = extractDir ~ ".json";
|
||
|
// It was successful, so add __COUNT__ to each object in the extract data.
|
||
|
JSONValue extractData = parseJSON(extractionResult.output);
|
||
|
for (uint i = 0; i < extractData.array.length; i++) {
|
||
|
extractData.array[i].object["__COUNT__"] = JSONValue(counts[i]);
|
||
|
}
|
||
|
std.file.write(extractJsonPath, extractData.toPrettyString());
|
||
|
return extractIdStr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Nullable!string getExtractorProgramPath() {
|
||
|
import std.string;
|
||
|
foreach (DirEntry entry; dirEntries(getcwd(), SpanMode.shallow, false)) {
|
||
|
if (entry.isFile() && endsWith(entry.name, ".jar")) {
|
||
|
return nullable(entry.name);
|
||
|
}
|
||
|
}
|
||
|
return Nullable!string.init;
|
||
|
}
|
||
|
|
||
|
Nullable!string getCurrentExtractorVersion() {
|
||
|
import std.regex;
|
||
|
Nullable!string extractorPath = getExtractorProgramPath();
|
||
|
if (extractorPath.isNull) return Nullable!string.init;
|
||
|
auto r = regex(`v\d+\.\d+\.\d+`);
|
||
|
auto c = matchFirst(extractorPath.get(), r);
|
||
|
if (c.empty) return Nullable!string.init;
|
||
|
return nullable(c.front());
|
||
|
}
|