From 4762e216d9cee5cbafcca3f307f7ba17a12d2234 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Mon, 24 Jul 2023 09:04:40 -0400 Subject: [PATCH] Added item-reports endpoint, schematic-exporter2.lua --- schematic-exporter2.lua | 299 ++++++++++++++++++++++++++++++++++++++++ source/csgs/http.d | 13 +- 2 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 schematic-exporter2.lua diff --git a/schematic-exporter2.lua b/schematic-exporter2.lua new file mode 100644 index 0000000..7e9e10a --- /dev/null +++ b/schematic-exporter2.lua @@ -0,0 +1,299 @@ +local g = require("simple-graphics") +local mon = peripheral.wrap("monitor_24") +local W, H = mon.getSize() +local RUNNING = true -- Flag to indicate global program state. + +-- Export flags +local EXPORTING = false -- Flag to indicate if we're exporting. +local EXPORT_SKIP = false -- Flag to indicate we should skip the current item. +local EXPORT_REPORT = false -- Flag to indicate we should report the current item as invalid. + +local function startsWith(str, start) + return str:sub(1, #start) == start +end + +local function getTotalItemCount(itemList) + local total = 0 + for name, value in pairs(itemList) do + if not startsWith(name, "__") then + total = total + value + end + end + local count = 1 + if itemList.__COUNT__ then count = itemList.__COUNT__ end + return count * total +end + +local function findItem(items, name) + for _, item in pairs(items) do + if item.name == name then return item end + end + return nil +end + +local function attemptItemExport(name, count) + local P_NAME = "meBridge_4" + local p = peripheral.wrap(P_NAME) + if p == nil then + return nil, "Missing peripheral "..P_NAME + end + local func = function() + return p.exportItem({name=name, count=count}, "south") + end + local success, result = pcall(func) + if success then + return result + else + return nil, result + end +end + +local function drawProgress(p) + local discreteWidth = W - 2 + local filled = p * discreteWidth + g.drawXLine(mon, 2, W-1, 5, colors.lightGray) + g.drawXLine(mon, 2, 2+filled-1, 5, colors.green) + local s = string.format("%.0f%%", p*100) + for i = 1, #s do + local x = 2+i + local c = s:sub(i,i) + local bg = colors.lightGray + if x < filled then bg = colors.green end + g.drawText(mon, x, 5, c, colors.white, bg) + end +end + +local function setProgressText(text) + g.drawXLine(mon, 2, W-1, 6, colors.gray) + if text ~= nil and #text > 0 then + g.drawText(mon, 2, 6, text, colors.white, colors.gray) + end +end + +local function clearMainPanel() + g.fillRect(mon, 1, 7, W, H-6, colors.black) +end + +local function clearExportPanel() + g.fillRect(mon, 1, 7, W, H-9, colors.black) +end + +local function promptForUrlInput() + clearMainPanel() + drawProgress(0) + setProgressText(nil) + g.drawText(mon, 1, 8, "Please enter a schematic URL that", colors.white, colors.black) + g.drawText(mon, 1, 9, "you've obtained from", colors.white, colors.black) + g.drawText(mon, 1, 10, "schematics.andrewlalis.com in the", colors.white, colors.black) + g.drawText(mon, 1, 11, "computer to your right to continue.", colors.white, colors.black) + + g.clear(term, colors.black) + g.drawText(term, 1, 1, "Paste your schematic URL here (CTRL+V): ", colors.white) + g.drawText(term, 2, 2, "*Press enter without pasting to quit*", colors.gray) + term.setCursorPos(1, 3) + term.setTextColor(colors.lightGray) + local link = io.read() + if link == nil or #link == 0 then + g.drawText(term, 1, 8, "No URL entered. Quitting.", colors.white) + return nil + else + g.drawText(term, 1, 8, "URL was pasted. Please continue on the monitor.", colors.white) + return link + end +end + +local function fetchItemLists(url) + local response = http.get(url) + if response == nil then + return nil, "HTTP request failed." + end + if response.getResponseCode() ~= 200 then + return nil, "HTTP code " .. response.getResponseCode() + end + local rawText = response.readAll() + response.close() + local itemLists, jsonErr = textutils.unserializeJSON(rawText) + if not itemLists then + return nil, "Failed to parse JSON: "..jsonErr + end + return itemLists +end + +local function exportItem(name, count) + clearExportPanel() + g.drawText(mon, 1, 7, "Exporting "..count.." of", colors.white, colors.black) + g.drawText(mon, 1, 8, name, colors.lime, colors.black) + -- Check for flags and do different stuff if so. + if EXPORT_REPORT then + -- Report the item issue to the schematic site. + g.drawText(mon, 1, 10, "Reporting item. Skipping to next one.", colors.orange, colors.black) + os.sleep(2) + return count + elseif EXPORT_SKIP then + -- Skip this item. + g.drawText(mon, 1, 10, "Skipping this item.", colors.orange, colors.black) + os.sleep(2) + return count + end + + local me = peripheral.find("meBridge") + if me == nil then + g.drawText(mon, 1, 10, "Error: No \"meBridge\" peripheral.", colors.red, colors.black) + g.drawText(mon, 1, 11, "Attach one please.", colors.red, colors.black) + os.sleep(1) + return 0 + end + + local allItems, err = me.listItems() + if allItems == nil or #allItems < 5 then + g.drawText(mon, 1, 10, "Error: Couldn't list AE items.", colors.red, colors.black) + g.drawText(mon, 1, 11, "Msg: " .. err, colors.red, colors.black) + os.sleep(1) + return 0 + end + + local item = findItem(allItems, name) + if item ~= nil and item.amount > 0 then + local exported, err = attemptItemExport(name, count) + if exported ~= nil then + if exported == 0 then + g.drawText(mon, 1, 10, "Exported 0 items. Make sure there is", colors.yellow, colors.black) + g.drawText(mon, 1, 11, "space in the output container.", colors.yellow, colors.black) + os.sleep(0.5) + end + return exported + end + g.drawText(mon, 1, 10, "Transfer failed: " .. err, colors.red, colors.black) + os.sleep(1) + return 0 + else + g.drawText(mon, 1, 10, "Item isn't present in the AE system.", colors.yellow, colors.black) + g.drawText(mon, 1, 11, "Please add some, craft, or skip.", colors.yellow, colors.black) + os.sleep(1) + return 0 + end +end + +local function exportSchematics(itemLists) + -- First build a list of instances of schematics, each with a task list. + local totalItemCount = 0 + local listInstances = {} + for _, list in pairs(itemLists) do + totalItemCount = totalItemCount + getTotalItemCount(list) + for i = 1, list.__COUNT__ do + local instance = { + name = list.__NAME__, + instanceNumber = i, + countOfThisType = list.__COUNT__, + tasks = {} + } + for name, amount in pairs(list) do + if not startsWith(name, "__") then + table.insert(instance.tasks, {name=name, amount=amount}) + end + end + table.insert(listInstances, instance) + end + end + -- Now execute on that task list, quitting if the EXPORTING flag goes false. + local totalItemsExported = 0 + local instanceIndex = 1 + while instanceIndex <= #listInstances and EXPORTING do + local listInstance = listInstances[instanceIndex] + setProgressText(listInstance.instanceNumber.."/"..listInstance.countOfThisType.." "..listInstance.name) + local taskIndex = 1 + while taskIndex <= #listInstance.tasks and EXPORTING do + local task = listInstance.tasks[taskIndex] + local itemsExported = 0 + -- Reset item-specific control flags. + EXPORT_REPORT = false + EXPORT_SKIP = false + while itemsExported < task.amount and EXPORTING do + local exportedCount = exportItem(task.name, task.amount - itemsExported) + itemsExported = itemsExported + exportedCount + totalItemsExported = totalItemsExported + exportedCount + drawProgress(totalItemsExported / totalItemCount) + end + taskIndex = taskIndex + 1 + end + instanceIndex = instanceIndex + 1 + end + -- Done! Show a small message, then set the EXPORTING flag to false. + EXPORTING = false + for i = 1, 3 do -- Queue up some no-op touch events make sure the event handler quits. + os.queueEvent("monitor_touch", "monitor_24", 1, 1) + end + g.drawText(mon, 1, H-4, "Export complete!", colors.lime, colors.black) +end + +local function handleExportEvents() + while EXPORTING do + local event, monName, x, y = os.pullEvent("monitor_touch") + if monName == "monitor_24" and y >= H-2 then + if x >= 27 then + EXPORTING = false + elseif x >= 14 then + EXPORT_REPORT = true + else + EXPORT_SKIP = true + end + end + end +end + +local function handleSchematicUrl(url) + clearMainPanel() + if url == nil then + g.drawText(mon, 1, 8, "No URL was entered. Quitting.", colors.white, colors.black) + RUNNING = false + os.sleep(2) + return + end + g.drawText(mon, 1, 8, "Fetching item lists from URL...", colors.lightGray, colors.black) + local itemLists, err = fetchItemLists(url) + if not itemLists then + g.drawText(mon, 1, 9, "Failed to fetch item lists.", colors.red, colors.black) + g.drawText(mon, 1, 10, err, colors.red, colors.black) + os.sleep(3) + return + end + g.drawText(mon, 1, 9, "Got lists for "..(#itemLists).." schematics.", colors.white, colors.black) + local y = 10 + for i, itemList in pairs(itemLists) do + local count = itemList.__COUNT__ + local name = itemList.__NAME__ + g.drawText(mon, 2, y, count.."x "..name, colors.white, colors.black) + y = y+1 + if y == H and i < #itemLists then + local numRemaining = #itemLists - i + g.drawText(mon, 2, y, "... and "..numRemaining.." more.") + end + end + os.sleep(3) + -- Draw control buttons. + g.fillRect(mon, 1, H-2, 13, 3, colors.blue) + g.drawText(mon, 5, H-1, "Skip", colors.white) + g.fillRect(mon, 14, H-2, 13, 3, colors.yellow) + g.drawText(mon, 14, H-1, "Report Error", colors.lightBlue) + g.fillRect(mon, 27, H-2, 13, 3, colors.red) + g.drawText(mon, 31, H-1, "Quit", colors.white) + EXPORTING = true + local fExport = function() exportSchematics(itemLists) end + local fHandleEvents = function() handleExportEvents() end + parallel.waitForAll(fExport, fHandleEvents) +end + +-- MAIN SCRIPT + +g.clear(mon, colors.black) +g.fillRect(mon, 1, 1, W, 3, colors.yellow) +g.drawTextCenter(mon, W/2, 2, "Schematic Exporter", colors.black) + +g.fillRect(mon, 1, 4, W, 3, colors.gray) +g.drawText(mon, 2, 4, "Progress", colors.white) +g.drawXLine(mon, 2, W-1, 5, colors.lightGray) + +while RUNNING do + local url = promptForUrlInput() + handleSchematicUrl(url) +end \ No newline at end of file diff --git a/source/csgs/http.d b/source/csgs/http.d index bdfbd48..3d9c312 100644 --- a/source/csgs/http.d +++ b/source/csgs/http.d @@ -17,6 +17,7 @@ void startServer() { PathDelegatingHandler handler = new PathDelegatingHandler(); handler.addMapping("POST", "/extracts", &handleExtract); handler.addMapping("GET", "/extracts/{extractId}", &getExtract); + handler.addMapping("POST", "/item-reports", &handleItemReport); handler.addMapping("GET", "/status", (ref HttpRequestContext ctx) { ctx.response.setStatus(HttpStatus.OK); ctx.response.writeBodyString("online"); @@ -42,4 +43,14 @@ private void getExtract(ref HttpRequestContext ctx) { string extractId = ctx.request.getPathParamAs!string("extractId"); const extractFile = buildPath(EXTRACTS_DIR, extractId ~ ".json"); fileResponse(ctx.response, extractFile, "application/json"); -} \ No newline at end of file +} + +private void handleItemReport(ref HttpRequestContext ctx) { + import std.string : strip; + import std.stdio; + + string itemName = ctx.request.readBodyAsString().strip(); + File f = File("item-reports.txt", "a"); + f.writeln(itemName); + f.close(); +}