@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
JavaScript
"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