2022-11-04 13:41:36 +00:00
|
|
|
const mysql = require('mysql2/promise');
|
|
|
|
const exec = require('child_process').exec;
|
|
|
|
const fs = require('fs-extra');
|
|
|
|
const path = require('path');
|
|
|
|
const colors = require('colors');
|
|
|
|
const axios = require('axios');
|
|
|
|
const yml = require('require-yml');
|
|
|
|
|
|
|
|
const selectQuery = fs.readFileSync(`sql/selectQueued.sql`).toString();
|
2022-12-15 14:04:36 +00:00
|
|
|
const jobDataQuery = fs.readFileSync(`sql/jobData.sql`).toString();
|
2022-11-04 13:41:36 +00:00
|
|
|
const jobArgsQuery = fs.readFileSync(`sql/jobArgs.sql`).toString();
|
|
|
|
const updateQuery = fs.readFileSync(`sql/updateState.sql`).toString();
|
|
|
|
const appDir = path.dirname(require.main.filename);
|
|
|
|
|
|
|
|
class PrintServer {
|
|
|
|
async start() {
|
|
|
|
let conf = yml(path.join(__dirname, 'config.yml'));
|
|
|
|
const localConfFile = path.join(__dirname, 'config.local.yml');
|
|
|
|
if (fs.existsSync(localConfFile))
|
|
|
|
conf = Object.assign({}, conf, yml(localConfFile));
|
|
|
|
this.conf = conf;
|
|
|
|
|
2022-11-10 18:21:13 +00:00
|
|
|
const decoration = '△▽'.repeat(10)
|
2022-11-04 13:41:36 +00:00
|
|
|
console.clear();
|
2022-11-07 09:24:16 +00:00
|
|
|
console.log(decoration, `${colors.bgBlack.white.bold(' Print')}${colors.bgBlack.green.bold('Natura ')}`, decoration, '\n')
|
2022-11-09 17:34:27 +00:00
|
|
|
await this.getToken();
|
2022-11-04 13:41:36 +00:00
|
|
|
await this.init();
|
|
|
|
}
|
|
|
|
async init() {
|
|
|
|
this.conn = await mysql.createConnection(this.conf.db);
|
2022-12-19 13:28:27 +00:00
|
|
|
this.dbErrorHandler = err => this.onDbError(err);
|
|
|
|
this.conn.on('error', this.dbErrorHandler);
|
|
|
|
console.log('Connected to DB successfully.'.green);
|
2022-11-04 13:41:36 +00:00
|
|
|
await this.poll();
|
|
|
|
}
|
|
|
|
async stop() {
|
|
|
|
await this.end();
|
2022-12-19 13:28:27 +00:00
|
|
|
await axios.post(`${this.conf.salix.url}/api/Accounts/logout?access_token=${this.token}`);
|
2022-11-04 13:41:36 +00:00
|
|
|
}
|
|
|
|
async end() {
|
2022-12-19 13:28:27 +00:00
|
|
|
if (this.pollTimeout) {
|
|
|
|
clearTimeout(this.pollTimeout);
|
|
|
|
this.pollTimeout = null;
|
|
|
|
}
|
2022-11-04 13:41:36 +00:00
|
|
|
if (this.reconnectTimeout) {
|
|
|
|
clearTimeout(this.reconnectTimeout);
|
|
|
|
this.reconnectTimeout = null;
|
|
|
|
}
|
2022-12-19 13:28:27 +00:00
|
|
|
this.conn.off('error', this.dbErrorHandler);
|
|
|
|
// FIXME: mysql2/promise bug, conn.end() ends process
|
|
|
|
this.conn.on('error', () => {});
|
|
|
|
await this.conn.end();
|
2022-11-04 13:41:36 +00:00
|
|
|
}
|
|
|
|
async getToken() {
|
2022-12-12 19:04:51 +00:00
|
|
|
const salix = this.conf.salix;
|
2022-11-17 06:29:35 +00:00
|
|
|
let response = await axios.post(`${salix.url}/api/Accounts/login`, {
|
2022-11-10 18:21:13 +00:00
|
|
|
user: salix.user,
|
|
|
|
password: salix.password
|
2022-11-04 13:41:36 +00:00
|
|
|
});
|
2022-12-12 19:04:51 +00:00
|
|
|
this.token = response.data.token;
|
2022-11-04 13:41:36 +00:00
|
|
|
}
|
|
|
|
async onDbError(err) {
|
|
|
|
switch(err.code) {
|
2022-12-19 13:28:27 +00:00
|
|
|
case 'PROTOCOL_CONNECTION_LOST':
|
|
|
|
case 'ECONNRESET':
|
|
|
|
case 1927: // ER_CONNECTION_KILLED
|
|
|
|
console.error(`DB: ${err.message}`.red);
|
2022-11-04 13:41:36 +00:00
|
|
|
try {
|
|
|
|
await this.end();
|
|
|
|
} catch(e) {}
|
2022-12-19 13:28:27 +00:00
|
|
|
console.log('Waiting until DB is available again...'.yellow);
|
2022-11-04 13:41:36 +00:00
|
|
|
await this.reconnect();
|
2022-11-09 17:34:27 +00:00
|
|
|
break;
|
2022-11-04 13:41:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
async reconnect() {
|
|
|
|
this.reconnectTimeout = null;
|
|
|
|
try {
|
|
|
|
await this.init();
|
|
|
|
} catch (err) {
|
2022-12-19 13:28:27 +00:00
|
|
|
this.reconnectTimeout = setTimeout(
|
|
|
|
() => this.reconnect(), this.conf.reconnectTimeout * 1000);
|
2022-11-04 13:41:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
async poll() {
|
2022-12-19 13:28:27 +00:00
|
|
|
this.pollTimeout = null;
|
2022-11-04 13:41:36 +00:00
|
|
|
try {
|
|
|
|
await this.printJob();
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err)
|
|
|
|
}
|
|
|
|
this.pollTimeout = setTimeout(() => this.poll(), this.conf.refreshRate);
|
|
|
|
}
|
|
|
|
async printJob() {
|
|
|
|
const conn = this.conn;
|
|
|
|
const conf = this.conf;
|
|
|
|
let printJob;
|
|
|
|
let jobId;
|
|
|
|
|
|
|
|
try {
|
|
|
|
await conn.beginTransaction();
|
2022-11-28 08:09:30 +00:00
|
|
|
[[printJob]] = await conn.query(selectQuery);
|
2022-11-28 08:06:16 +00:00
|
|
|
if (!printJob) {
|
2022-12-19 09:23:33 +00:00
|
|
|
await conn.rollback();
|
2022-11-28 08:06:16 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-11-04 13:41:36 +00:00
|
|
|
|
|
|
|
jobId = printJob.id;
|
2022-11-10 17:25:21 +00:00
|
|
|
|
2022-11-04 13:41:36 +00:00
|
|
|
await conn.query(updateQuery, ['printing', null, jobId]);
|
|
|
|
await conn.commit();
|
|
|
|
} catch (err) {
|
2022-12-19 09:23:33 +00:00
|
|
|
await conn.rollback();
|
2022-11-04 13:41:36 +00:00
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2022-12-15 14:04:36 +00:00
|
|
|
// Job data
|
2022-12-21 07:14:28 +00:00
|
|
|
// FIXME: Cannot read property 'method' of undefined
|
2022-12-21 08:57:37 +00:00
|
|
|
const [[jobData]] = await conn.query(jobDataQuery, jobId);
|
2022-11-04 13:41:36 +00:00
|
|
|
const args = {};
|
2022-12-15 14:04:36 +00:00
|
|
|
const [res] = await conn.query(jobArgsQuery, jobId);
|
|
|
|
for (const row of res)
|
2022-11-04 13:41:36 +00:00
|
|
|
args[row.name] = row.value;
|
2022-12-15 14:04:36 +00:00
|
|
|
|
|
|
|
// Path params
|
|
|
|
const usedParams = new Set();
|
|
|
|
const methodPath = jobData.method.replace(/{\w+}/g, function(match) {
|
|
|
|
const key = match.substr(1, match.length - 2);
|
|
|
|
const value = args[key];
|
|
|
|
usedParams.add(key);
|
|
|
|
return value !== undefined ? value : match;
|
|
|
|
});
|
|
|
|
|
|
|
|
let pdfData;
|
2022-12-21 07:14:28 +00:00
|
|
|
for (let attempts = 0; !pdfData && attempts < 2; attempts++) {
|
2022-12-12 19:04:51 +00:00
|
|
|
// URL params
|
2022-12-15 14:04:36 +00:00
|
|
|
const params = {
|
2022-12-12 19:04:51 +00:00
|
|
|
access_token: this.token,
|
|
|
|
userFk: printJob.userFk
|
|
|
|
};
|
|
|
|
for (const key in args) {
|
|
|
|
if (!usedParams.has(key))
|
|
|
|
params[key] = args[key];
|
|
|
|
}
|
|
|
|
const urlParams = new URLSearchParams(params);
|
|
|
|
|
|
|
|
// Request
|
2022-12-15 14:04:36 +00:00
|
|
|
try {
|
|
|
|
const response = await axios({
|
|
|
|
method: 'get',
|
|
|
|
url: `${conf.salix.url}/api/${methodPath}?${urlParams.toString()}`,
|
|
|
|
responseType: 'arraybuffer',
|
|
|
|
headers: {
|
|
|
|
'Accept': 'application/pdf'
|
2022-11-09 17:34:27 +00:00
|
|
|
}
|
2022-12-15 14:04:36 +00:00
|
|
|
});
|
|
|
|
pdfData = response.data;
|
|
|
|
}
|
|
|
|
catch (err) {
|
|
|
|
if (err.response?.statusText === 'Unauthorized') {
|
|
|
|
await this.getToken();
|
|
|
|
} else
|
|
|
|
throw err;
|
2022-11-04 13:41:36 +00:00
|
|
|
}
|
2022-11-09 17:34:27 +00:00
|
|
|
}
|
2022-12-15 14:04:36 +00:00
|
|
|
|
|
|
|
// Save PDF to disk
|
|
|
|
const printer = jobData.printer;
|
2022-11-10 18:21:13 +00:00
|
|
|
const tmpPath = path.join(appDir, 'tmp')
|
|
|
|
if (!fs.existsSync(tmpPath))
|
|
|
|
fs.mkdirSync(tmpPath)
|
|
|
|
const tmpFilePath = path.join(tmpPath, `${Math.random().toString(36).substring(7)}.pdf`);
|
2022-12-15 14:04:36 +00:00
|
|
|
await fs.writeFile(tmpFilePath, pdfData, 'binary');
|
2022-11-04 13:41:36 +00:00
|
|
|
|
2022-12-15 14:04:36 +00:00
|
|
|
// Print PDF
|
2022-11-04 13:41:36 +00:00
|
|
|
try {
|
2022-12-19 13:36:51 +00:00
|
|
|
await pExec(`lp -d "${printer}" "${tmpFilePath}"`);
|
2022-11-04 13:41:36 +00:00
|
|
|
} catch(err) {
|
|
|
|
await fs.unlink(tmpFilePath);
|
2022-12-19 13:36:51 +00:00
|
|
|
throw new Error(`Print error: ${err.message}`);
|
2022-11-04 13:41:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
await conn.query(updateQuery, ['printed', null, jobId]);
|
|
|
|
|
|
|
|
if (conf.debug)
|
2022-12-15 14:04:36 +00:00
|
|
|
console.debug(`(${colors.yellow(jobId)}) Document has been printed`, `[${args.collectionFk}, ${jobData.report}, ${printer}]`.green);
|
2022-11-04 13:41:36 +00:00
|
|
|
|
|
|
|
await fs.unlink(tmpFilePath);
|
|
|
|
} catch (err) {
|
2022-12-19 13:28:27 +00:00
|
|
|
let message = err.message;
|
|
|
|
if (err.name === 'AxiosError' && err.code === 'ERR_BAD_REQUEST') {
|
|
|
|
const resMessage = JSON.parse(err.response.data).error.message;
|
|
|
|
message = `${message}: ${resMessage}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
await conn.query(updateQuery, ['error', message, jobId]);
|
|
|
|
throw new Error(`(${jobId}) ${message}`);
|
2022-11-04 13:41:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = PrintServer;
|
|
|
|
|
|
|
|
function pExec(command) {
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
exec(command, function(err, stdout, stderr) {
|
|
|
|
if (err) return reject(err);
|
|
|
|
resolve({stdout, stderr})
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|