UNPKG

@fdm-monster/server

Version:

FDM Monster is a bulk OctoPrint manager to set up, configure and monitor 3D printers. Our aim is to provide extremely optimized websocket performance and reliability.

222 lines (221 loc) 9.46 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: all[name] }); } _export(exports, { PrinterThumbnailCache: function() { return PrinterThumbnailCache; }, gcodeMaxLinesToRead: function() { return gcodeMaxLinesToRead; } }); const _keydiffcache = require("../utils/cache/key-diff.cache"); const _path = require("path"); const _fsutils = require("../utils/fs.utils"); const _serverconstants = require("../server.constants"); const _nodefs = require("node:fs"); const _fs = require("fs"); const _gcodeutils = require("../utils/gcode.utils"); const _promises = require("node:readline/promises"); const _promises1 = require("node:fs/promises"); const _node = require("@sentry/node"); const _eventconstants = require("../constants/event.constants"); const gcodeMaxLinesToRead = 10000; class PrinterThumbnailCache extends _keydiffcache.KeyDiffCache { printerCache; printerApiFactory; eventEmitter2; settingsStore; logger; constructor(loggerFactory, printerCache, printerApiFactory, eventEmitter2, settingsStore){ super(), this.printerCache = printerCache, this.printerApiFactory = printerApiFactory, this.eventEmitter2 = eventEmitter2, this.settingsStore = settingsStore; this.logger = loggerFactory(PrinterThumbnailCache.name); this.eventEmitter2.on(_eventconstants.printerEvents.printersDeleted, this.handlePrintersDeleted.bind(this)); } async resetCache() { const keys = Object.keys(this.keyValueStore); await this.deleteKeysBatch(keys); } async loadCache() { if (!this.settingsStore.isThumbnailSupportEnabled()) return; const printers = await this.printerCache.listCachedPrinters(); const baseFolder = (0, _path.join)((0, _fsutils.superRootPath)(), _serverconstants.AppConstants.defaultPrinterThumbnailsStorage); this.resetDiffs(); Object.keys(this.keyValueStore).forEach((key)=>{ delete this.keyValueStore[key]; }); for (const printer of printers){ const printerIdStr = printer.id.toString(); const thumbnailFile = (0, _path.join)(baseFolder, printer.id.toString() + ".dat"); if ((0, _nodefs.existsSync)(thumbnailFile)) { const data = (0, _fs.readFileSync)(thumbnailFile, "utf8"); await this.setPrinterThumbnail(printerIdStr, data); } } } async handlePrintersDeleted(event) { if (!this.settingsStore.isThumbnailSupportEnabled()) return; for (const printerId of event.printerIds){ await this.removeThumbnailFile(printerId); await this.unsetPrinterThumbnail(printerId); } } async setPrinterThumbnail(id, imageData) { await this.setKeyValue(id, { id: id, thumbnailBase64: imageData }); } async unsetPrinterThumbnail(id) { await this.deleteKeyValue(id); } async loadPrinterThumbnailRemote(login, printerId, file) { if (!this.settingsStore.isThumbnailSupportEnabled()) return; const id = printerId.toString(); try { const thumbnailData = await this.extractRemoteThumbnailBase64(login, file); await this.writeThumbnailFile(id, thumbnailData); await this.setPrinterThumbnail(id, thumbnailData); return thumbnailData; } catch (e) { this.logger.error("Could not parse thumbnail, clearing printer thumbnail", e); (0, _node.captureException)(e); await this.removeThumbnailFile(id); await this.unsetPrinterThumbnail(id); } } async loadPrinterThumbnailLocal(printerId, file) { if (!this.settingsStore.isThumbnailSupportEnabled()) return; const id = printerId.toString(); try { const thumbnailData = await this.extractThumbnailBase64(file); await this.writeThumbnailFile(id, thumbnailData); await this.setPrinterThumbnail(id, thumbnailData); return thumbnailData; } catch (e) { this.logger.error("Could not parse thumbnail, clearing printer thumbnail", e); (0, _node.captureException)(e); await this.removeThumbnailFile(id); await this.unsetPrinterThumbnail(id); } } async writeThumbnailFile(printerId, thumbnailData) { if (!thumbnailData?.length) { await this.removeThumbnailFile(printerId); await this.unsetPrinterThumbnail(printerId); return; } const baseFolder = (0, _path.join)((0, _fsutils.superRootPath)(), _serverconstants.AppConstants.defaultPrinterThumbnailsStorage); const thumbnailPath = (0, _path.join)(baseFolder, printerId + ".dat"); (0, _fsutils.ensureDirExists)(baseFolder); await (0, _promises1.writeFile)(thumbnailPath, thumbnailData); } async removeThumbnailFile(printerId) { const baseFolder = (0, _path.join)((0, _fsutils.superRootPath)(), _serverconstants.AppConstants.defaultPrinterThumbnailsStorage); const thumbnailPath = (0, _path.join)(baseFolder, printerId + ".dat"); if ((0, _nodefs.existsSync)(thumbnailPath)) { (0, _nodefs.rmSync)(thumbnailPath); } } async extractRemoteThumbnailBase64(login, file) { const lines = await this.readRemoteGcodeLines(login, file, gcodeMaxLinesToRead); if (!lines?.length) { throw new Error("No gcode lines were returned."); } let collecting = false; let currentThumbnailBase64 = ""; let index = 0; for (const line of lines){ if (line.startsWith("; thumbnail begin")) { collecting = true; currentThumbnailBase64 = ""; } else if (collecting && line.startsWith("; thumbnail") && line.includes("end")) { collecting = false; break; } else if (collecting) { const trim = line.trim().replace(/^;/, "").trim(); currentThumbnailBase64 += trim; } if (index > gcodeMaxLinesToRead) { throw new Error("Thumbnail not found (within 10000 lines)."); } index++; } return currentThumbnailBase64; } async readRemoteGcodeLines(login, file, numberOfLines, fromEnd = false, endCondition = "; thumbnail end") { const printer = this.printerApiFactory.getScopedPrinter(login); const fileData = await printer.getFile(file); const fileSize = fileData.size; let lines = []; let position = fromEnd ? fileSize : 0; let iterationsLeft = 5; while(lines.length <= numberOfLines && (fromEnd ? position > 0 : position < fileSize)){ iterationsLeft--; if (iterationsLeft <= 0) { return; } const bytesToRead = Math.min(_gcodeutils.gcodeScanningChunkSize, fromEnd ? position : fileSize - position); const start = fromEnd ? position - bytesToRead : position; const end = fromEnd ? position : position + bytesToRead; position = fromEnd ? start : end; const remoteChunk = await printer.getFileChunk(file, start, end); const chunkLines = remoteChunk.data.split("\n"); if (fromEnd) { lines = chunkLines.concat(lines); } else { lines = lines.concat(chunkLines); } if (endCondition) { const endIndex = lines.findIndex((line)=>line.includes(endCondition)); if (endIndex !== -1) { lines = lines.slice(0, endIndex + 1); break; } } else if (lines.length >= numberOfLines) { break; } } return fromEnd ? lines.slice(-numberOfLines) : lines.slice(0, numberOfLines); } async extractThumbnailBase64(gcodePath) { const fileStream = (0, _fs.createReadStream)(gcodePath); const rl = (0, _promises.createInterface)({ input: fileStream, crlfDelay: Infinity }); let collecting = false; let currentThumbnailBase64 = ""; let index = 0; for await (const line of rl){ if (line.startsWith("; thumbnail begin")) { collecting = true; currentThumbnailBase64 = ""; } else if (collecting && line.startsWith("; thumbnail") && line.includes("end")) { collecting = false; rl.close(); break; } else if (collecting) { const trim = line.trim().replace(/^;/, "").trim(); currentThumbnailBase64 += trim; } if (index > gcodeMaxLinesToRead) { throw new Error("Thumbnail not found (within 10000 lines)."); } index++; } if (!currentThumbnailBase64?.length) { throw new Error("Thumbnail not found (within 10000 lines)."); } Buffer.from(currentThumbnailBase64, "base64"); return currentThumbnailBase64; } } //# sourceMappingURL=printer-thumbnail.cache.js.map