Some checks reported errors
farmcontrol/farmcontrol-server/pipeline/head Something is wrong with the build of this commit
307 lines
8.8 KiB
JavaScript
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",
|
|
};
|
|
}
|
|
}
|
|
}
|