Tom Butcher 7dbe7da4ee
Some checks reported errors
farmcontrol/farmcontrol-server/pipeline/head Something is wrong with the build of this commit
Overhauled server code.
2025-12-28 21:46:58 +00:00

307 lines
8.8 KiB
JavaScript

// cupsprinterinterface.js - CUPS printer interface implementation
import log4js from "log4js";
import { loadConfig } from "../../config.js";
import ipp from "ipp";
const config = loadConfig();
const logger = log4js.getLogger("CUPS Printer Interface");
logger.level = config.logLevel;
export default class CupsInterface {
constructor(documentPrinterClient) {
this.documentPrinterClient = documentPrinterClient;
this.host = documentPrinterClient.connection.host;
this.port = documentPrinterClient.connection.port || 631;
this.interface = documentPrinterClient.connection.interface || "cups";
this.protocol = documentPrinterClient.connection.protocol;
this.name = documentPrinterClient.documentPrinter.name;
this.isConnected = false;
this.cupsPrinter = null;
this.pdfs = new Map();
}
/**
* Build the IPP printer URL from connection settings
*/
buildPrinterUrl() {
var hostWithPort = `${this.host}:${this.port}`;
var path = "/";
if (this.host.includes("/")) {
const parts = this.host.split("/");
var hostName = parts[0];
if (hostName.includes(":")) {
const portHost = hostName.split(":");
hostName = portHost[0];
}
hostWithPort = `${hostName}:${this.port}`;
path += parts.slice(1).join("/");
}
switch (this.protocol) {
case "ipp":
return `ipp://${hostWithPort}${path}`;
case "http":
return `http://${hostWithPort}${path}`;
default:
logger.warn(`Unknown protocol ${this.protocol}, defaulting to ipp.`);
return `ipp://${hostWithPort}${path}`;
}
}
/**
* Promisify IPP execute method
*/
async executeIPP(operation, message) {
return new Promise((resolve, reject) => {
this.cupsPrinter.execute(operation, message, (err, res) => {
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
}
async connect() {
logger.info(`Connecting to CUPS printer ${this.name}`);
try {
this.cupsPrinterUrl = this.buildPrinterUrl();
logger.debug(`Printer URL: ${this.cupsPrinterUrl}`);
// Create IPP printer instance
this.cupsPrinter = ipp.Printer(this.cupsPrinterUrl);
// Test connection by getting printer attributes
const getPrinterAttributes = {
"operation-attributes-tag": {
"requested-attributes": [
"printer-name",
"printer-state",
"printer-state-message",
"printer-uri-supported",
],
},
};
try {
const response = await this.executeIPP(
"Get-Printer-Attributes",
getPrinterAttributes
);
const printerState =
response["printer-attributes-tag"]?.["printer-state"];
logger.info(
`Successfully connected to CUPS printer ${this.name}. State: ${printerState}`
);
this.isConnected = true;
return true;
} catch (error) {
logger.error(
`Failed to get printer attributes for ${this.name}:`,
error
);
return {
error: "Failed to get printer attributes. " + error.message,
};
}
} catch (error) {
logger.error(`Failed to connect to CUPS printer:`, error);
this.isConnected = false;
this.cupsPrinter = null;
return {
error: "Failed to connect to CUPS printer. " + error.message,
};
}
}
async disconnect() {
logger.info(`Disconnecting from CUPS printer...`);
this.isConnected = false;
this.cupsPrinter = null;
return { success: true };
}
async initialize() {
logger.info(`Initializing CUPS printer...`);
if (!this.isConnected || !this.cupsPrinter) {
return { error: "Printer is not connected" };
}
try {
// Verify printer is ready by checking attributes
const getPrinterAttributes = {
"operation-attributes-tag": {
"requested-attributes": [
"printer-state",
"printer-state-message",
"printer-state-reasons",
],
},
};
const response = await this.executeIPP(
"Get-Printer-Attributes",
getPrinterAttributes
);
const printerState =
response["printer-attributes-tag"]?.["printer-state"];
const stateMessage =
response["printer-attributes-tag"]?.["printer-state-message"] || "";
// Printer states: 3 = idle, 4 = processing, 5 = stopped
if (printerState === 5) {
logger.warn(`Printer ${this.name} is stopped: ${stateMessage}`);
return { error: `Printer is stopped: ${stateMessage}` };
}
logger.info(
`CUPS printer ${this.name} initialized successfully. State: ${printerState}`
);
return true;
} catch (error) {
logger.error(`Failed to initialize CUPS printer ${this.name}:`, error);
return { error: error.message || "Failed to initialize CUPS printer" };
}
}
/**
* Get document data from documentJob
* Supports file references, buffers, and content
*/
async getDocumentData(documentJob) {
let data = null;
// If documentJob has a file reference, fetch it
if (documentJob.file || documentJob.fileId) {
const fileId =
documentJob.file?._id || documentJob.file || documentJob.fileId;
if (this.documentPrinterClient.socketClient?.fileManager) {
data =
await this.documentPrinterClient.socketClient.fileManager.getFile(
fileId
);
} else {
throw new Error("File manager not available to fetch file");
}
}
// If documentJob has a buffer directly
else if (documentJob.buffer || documentJob.data) {
data = documentJob.buffer || documentJob.data;
}
// If documentJob has rendered image
else if (documentJob.renderedImage) {
data = documentJob.renderedImage;
}
// If documentJob has content (text), convert to buffer
else if (documentJob.content) {
data = Buffer.from(documentJob.content, "utf-8");
} else {
throw new Error("Document job has no printable content");
}
// Ensure data is a Buffer
if (!Buffer.isBuffer(data)) {
if (data instanceof Uint8Array) {
data = Buffer.from(data);
} else if (data instanceof ArrayBuffer) {
data = Buffer.from(data);
} else if (typeof data === "string") {
data = Buffer.from(data, "utf-8");
} else {
throw new Error(
"Document data must be a Buffer, Uint8Array, ArrayBuffer, or string"
);
}
}
return data;
}
async deploy(documentJob, pdf) {
logger.info(
`Deploying job ${documentJob._id} to CUPS printer ${this.name}`
);
// Store the PDF buffer
this.pdfs.set(documentJob._id, pdf);
return { success: true };
}
async print(jobId) {
logger.info(`Printing job ${jobId} to CUPS printer ${this.name}`);
if (!this.isConnected || !this.cupsPrinter) {
throw new Error("Printer is not connected");
}
const pdf = this.pdfs.get(jobId);
if (!pdf) {
throw new Error("PDF not found for job");
}
try {
// Ensure PDF is a Buffer
let documentData = pdf;
if (!Buffer.isBuffer(documentData)) {
if (documentData instanceof Uint8Array) {
documentData = Buffer.from(documentData);
} else if (documentData instanceof ArrayBuffer) {
documentData = Buffer.from(documentData);
} else if (typeof documentData === "string") {
documentData = Buffer.from(documentData, "utf-8");
} else {
throw new Error(
"PDF data must be a Buffer, Uint8Array, ArrayBuffer, or string"
);
}
}
// Build IPP print job message
const jobName = jobId?.toString() || "Print Job";
const userName = process.env.USER || "system";
const printJobMessage = {
"operation-attributes-tag": {
"requesting-user-name": userName,
"job-name": jobName,
"document-format": "application/pdf",
},
data: documentData,
};
// Send print job
logger.debug(`Sending print job ${jobId} to ${this.cupsPrinterUrl}`);
const response = await this.executeIPP("Print-Job", printJobMessage);
// Extract job ID from response
const ippJobId = response["job-attributes-tag"]?.["job-id"];
const jobUri = response["job-attributes-tag"]?.["job-uri"];
logger.info(
`Successfully printed job ${jobId} to CUPS printer ${this.name}. IPP Job ID: ${ippJobId}`
);
return {
success: true,
jobId: ippJobId,
jobUri: jobUri,
};
} catch (error) {
logger.error(
`Failed to print job ${jobId} to CUPS printer ${this.name}:`,
error
);
return {
success: false,
error: error.message || "Failed to print job",
};
}
}
}