refs @4550 concurrency, logging improved, fixes, code clean, refactor
gitea/printnatura/pipeline/head This commit looks good
Details
gitea/printnatura/pipeline/head This commit looks good
Details
This commit is contained in:
parent
d6a3b831e7
commit
53ff37d66c
10
config.yml
10
config.yml
|
@ -1,4 +1,10 @@
|
||||||
debug: true
|
debug: false
|
||||||
|
log: true
|
||||||
|
dryPrint: false
|
||||||
|
concurrency: 4
|
||||||
|
reconnectTimeout: 10
|
||||||
|
refreshRate: 1000
|
||||||
|
tmpDir: /dev/shm/printnatura
|
||||||
db:
|
db:
|
||||||
host: localhost
|
host: localhost
|
||||||
port: 3306
|
port: 3306
|
||||||
|
@ -9,5 +15,3 @@ salix:
|
||||||
url: http://localhost:3000
|
url: http://localhost:3000
|
||||||
user: user
|
user: user
|
||||||
password: password
|
password: password
|
||||||
reconnectTimeout: 30
|
|
||||||
refreshRate: 1000
|
|
||||||
|
|
1
main.js
1
main.js
|
@ -5,7 +5,6 @@ async function main() {
|
||||||
await printServer.start();
|
await printServer.start();
|
||||||
|
|
||||||
process.on('SIGINT', async function() {
|
process.on('SIGINT', async function() {
|
||||||
console.log(`\nBye ( ◕ ‿ ◕ )っ`);
|
|
||||||
try {
|
try {
|
||||||
await printServer.stop();
|
await printServer.stop();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
207
print-server.js
207
print-server.js
|
@ -10,7 +10,6 @@ const selectQuery = fs.readFileSync(`sql/selectQueued.sql`).toString();
|
||||||
const jobDataQuery = fs.readFileSync(`sql/jobData.sql`).toString();
|
const jobDataQuery = fs.readFileSync(`sql/jobData.sql`).toString();
|
||||||
const jobArgsQuery = fs.readFileSync(`sql/jobArgs.sql`).toString();
|
const jobArgsQuery = fs.readFileSync(`sql/jobArgs.sql`).toString();
|
||||||
const updateQuery = fs.readFileSync(`sql/updateState.sql`).toString();
|
const updateQuery = fs.readFileSync(`sql/updateState.sql`).toString();
|
||||||
const appDir = path.dirname(require.main.filename);
|
|
||||||
|
|
||||||
class PrintServer {
|
class PrintServer {
|
||||||
async start() {
|
async start() {
|
||||||
|
@ -20,22 +19,41 @@ class PrintServer {
|
||||||
conf = Object.assign({}, conf, yml(localConfFile));
|
conf = Object.assign({}, conf, yml(localConfFile));
|
||||||
this.conf = conf;
|
this.conf = conf;
|
||||||
|
|
||||||
const decoration = '△▽'.repeat(10)
|
|
||||||
console.clear();
|
console.clear();
|
||||||
console.log(decoration, `${colors.bgBlack.white.bold(' Print')}${colors.bgBlack.green.bold('Natura ')}`, decoration, '\n')
|
const decoration = '△▽'.repeat(10);
|
||||||
await this.getToken();
|
const printnatura = colors.bgBlack.bold(' Print'.white + 'Natura '.green);
|
||||||
|
console.log(`${decoration} ${printnatura} ${decoration}`);
|
||||||
|
if (this.conf.dryPrint)
|
||||||
|
this.serverLog('log', 'Running in dry print mode! Documents won\'t be printed and PDFs not removed.'.yellow);
|
||||||
|
|
||||||
await this.init();
|
await this.init();
|
||||||
}
|
this.rejectionHandler = (err, p) => this.onRejection(err, p);
|
||||||
async init() {
|
process.on('unhandledRejection', this.rejectionHandler);
|
||||||
this.pool = await mysql.createPool(this.conf.db);
|
|
||||||
console.log('Connected to DB successfully.'.green);
|
this.serverLog('log', 'Ready to print'.green);
|
||||||
await this.poll();
|
setTimeout(() => this.poll());
|
||||||
}
|
}
|
||||||
async stop() {
|
async stop() {
|
||||||
|
process.off('unhandledRejection', this.rejectionHandler);
|
||||||
|
this.serverLog('log', 'Bye ( ◕ ‿ ◕ )っ'.green);
|
||||||
await this.end();
|
await this.end();
|
||||||
await axios.post(`${this.conf.salix.url}/api/Accounts/logout?access_token=${this.token}`);
|
}
|
||||||
|
async init() {
|
||||||
|
const conf = this.conf;
|
||||||
|
const api = this.api = axios.create({
|
||||||
|
baseURL: `${conf.salix.url}/api/`
|
||||||
|
});
|
||||||
|
api.interceptors.request.use(config => {
|
||||||
|
if (this.token)
|
||||||
|
config.headers['Authorization'] = this.token
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
await this.getToken();
|
||||||
|
|
||||||
|
this.pool = await mysql.createPool(conf.db);
|
||||||
}
|
}
|
||||||
async end() {
|
async end() {
|
||||||
|
await this.api.post(`Accounts/logout`);
|
||||||
if (this.pollTimeout) {
|
if (this.pollTimeout) {
|
||||||
clearTimeout(this.pollTimeout);
|
clearTimeout(this.pollTimeout);
|
||||||
this.pollTimeout = null;
|
this.pollTimeout = null;
|
||||||
|
@ -44,62 +62,124 @@ class PrintServer {
|
||||||
}
|
}
|
||||||
async getToken() {
|
async getToken() {
|
||||||
const salix = this.conf.salix;
|
const salix = this.conf.salix;
|
||||||
let response = await axios.post(`${salix.url}/api/Accounts/login`, {
|
let response = await this.api.post(`Accounts/login`, {
|
||||||
user: salix.user,
|
user: salix.user,
|
||||||
password: salix.password
|
password: salix.password
|
||||||
});
|
});
|
||||||
this.token = response.data.token;
|
this.token = response.data.token;
|
||||||
}
|
}
|
||||||
|
serverLog(realm, message) {
|
||||||
|
this.log(`Server`, realm, message);
|
||||||
|
}
|
||||||
|
log(classMsg, realm, message) {
|
||||||
|
classMsg = `${classMsg}:`;
|
||||||
|
|
||||||
|
switch(realm) {
|
||||||
|
case 'debug':
|
||||||
|
if (this.conf.debug) console.debug(classMsg, message.magenta);
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
console.error(classMsg, message.red);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (this.conf.log) console.log(classMsg, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onRejection(err, p) {
|
||||||
|
console.debug('unhandledRejection');
|
||||||
|
this.errorHandler(err);
|
||||||
|
}
|
||||||
|
errorHandler(err) {
|
||||||
|
if (err.code === 'ETIMEDOUT') {
|
||||||
|
if (!this.dbDown) {
|
||||||
|
this.dbDown = true;
|
||||||
|
this.serverLog('error', `DB connection lost: ${err.message}`);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
async poll() {
|
async poll() {
|
||||||
let delay = this.conf.refreshRate;
|
let conf = this.conf;
|
||||||
this.pollTimeout = null;
|
this.pollTimeout = null;
|
||||||
|
|
||||||
try {
|
if (this.dbDown) {
|
||||||
const conn = await this.pool.getConnection();
|
let conn;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.dbDisconnected) {
|
try {
|
||||||
|
conn = await this.pool.getConnection();
|
||||||
await conn.ping();
|
await conn.ping();
|
||||||
this.dbDisconnected = false;
|
this.dbDown = false;
|
||||||
console.log('DB connection recovered.'.green);
|
this.serverLog('log', 'DB connection recovered'.green);
|
||||||
|
} catch (err) {
|
||||||
|
conn.release();
|
||||||
}
|
}
|
||||||
|
} catch(e) {}
|
||||||
if (await this.printJob(conn))
|
|
||||||
delay = 0;
|
|
||||||
} finally {
|
|
||||||
await conn.release();
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (err.code === 'ETIMEDOUT') {
|
|
||||||
delay = this.conf.reconnectTimeout;
|
|
||||||
if (!this.dbDisconnected) {
|
|
||||||
this.dbDisconnected = true;
|
|
||||||
console.log(`DB connection lost: ${err.message}`.red);
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
console.error(err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.dbDown) {
|
||||||
|
try {
|
||||||
|
let jobs;
|
||||||
|
let nJobs = 0;
|
||||||
|
do {
|
||||||
|
jobs = [];
|
||||||
|
for (let i = 0; i < conf.concurrency; i++) {
|
||||||
|
const jobId = await this.getJob();
|
||||||
|
if (jobId)
|
||||||
|
jobs.push(this.printJob(jobId));
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nJobs += jobs.length;
|
||||||
|
await Promise.all(jobs);
|
||||||
|
} while (jobs.length);
|
||||||
|
|
||||||
|
if (nJobs > 0)
|
||||||
|
this.serverLog('debug', `${nJobs} jobs printed`);
|
||||||
|
} catch (err) {
|
||||||
|
this.errorHandler(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let delay = this.conf.refreshRate;
|
||||||
|
if (this.dbDown) delay = this.conf.reconnectTimeout;
|
||||||
this.pollTimeout = setTimeout(() => this.poll(), delay);
|
this.pollTimeout = setTimeout(() => this.poll(), delay);
|
||||||
}
|
}
|
||||||
async printJob(conn) {
|
async getJob() {
|
||||||
const conf = this.conf;
|
|
||||||
let jobId;
|
let jobId;
|
||||||
|
|
||||||
|
const conn = await this.pool.getConnection();
|
||||||
try {
|
try {
|
||||||
let jobData;
|
await conn.beginTransaction();
|
||||||
const args = {};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await conn.beginTransaction();
|
|
||||||
const [[printJob]] = await conn.query(selectQuery);
|
const [[printJob]] = await conn.query(selectQuery);
|
||||||
if (!printJob) {
|
if (printJob) {
|
||||||
await conn.rollback();
|
jobId = printJob.id;
|
||||||
return;
|
await conn.query(updateQuery, ['printing', null, jobId]);
|
||||||
|
await conn.commit();
|
||||||
|
this.jobLog(jobId, 'debug', 'get: printing');
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
await conn.rollback();
|
||||||
|
if (jobId)
|
||||||
|
this.jobLog(jobId, 'error', err.message);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
conn.release();
|
||||||
|
}
|
||||||
|
|
||||||
jobId = printJob.id;
|
return jobId;
|
||||||
|
}
|
||||||
|
async printJob(jobId) {
|
||||||
|
const conf = this.conf;
|
||||||
|
let jobData;
|
||||||
|
const args = {};
|
||||||
|
|
||||||
|
const conn = await this.pool.getConnection();
|
||||||
|
try {
|
||||||
|
await conn.beginTransaction();
|
||||||
|
try {
|
||||||
// Job data
|
// Job data
|
||||||
const [[data]] = await conn.query(jobDataQuery, jobId);
|
const [[data]] = await conn.query(jobDataQuery, jobId);
|
||||||
jobData = data;
|
jobData = data;
|
||||||
|
@ -109,7 +189,6 @@ class PrintServer {
|
||||||
for (const row of res)
|
for (const row of res)
|
||||||
args[row.name] = row.value;
|
args[row.name] = row.value;
|
||||||
|
|
||||||
await conn.query(updateQuery, ['printing', null, jobId]);
|
|
||||||
await conn.commit();
|
await conn.commit();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await conn.rollback();
|
await conn.rollback();
|
||||||
|
@ -126,6 +205,7 @@ class PrintServer {
|
||||||
});
|
});
|
||||||
|
|
||||||
let pdfData;
|
let pdfData;
|
||||||
|
let url;
|
||||||
for (let attempts = 0; !pdfData && attempts < 2; attempts++) {
|
for (let attempts = 0; !pdfData && attempts < 2; attempts++) {
|
||||||
// URL params
|
// URL params
|
||||||
const params = {userFk: jobData.userFk};
|
const params = {userFk: jobData.userFk};
|
||||||
|
@ -135,16 +215,16 @@ class PrintServer {
|
||||||
}
|
}
|
||||||
const urlParams = new URLSearchParams(params);
|
const urlParams = new URLSearchParams(params);
|
||||||
|
|
||||||
|
url = `${methodPath}?${urlParams.toString()}`;
|
||||||
|
this.jobLog(jobId, 'debug', `api: ${url}`);
|
||||||
|
|
||||||
// Request
|
// Request
|
||||||
try {
|
try {
|
||||||
const response = await axios({
|
const response = await this.api({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: `${conf.salix.url}/api/${methodPath}?${urlParams.toString()}`,
|
url,
|
||||||
responseType: 'arraybuffer',
|
responseType: 'arraybuffer',
|
||||||
headers: {
|
headers: {'Accept': 'application/pdf'}
|
||||||
'Accept': 'application/pdf',
|
|
||||||
'Authorization': this.token
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
pdfData = response.data;
|
pdfData = response.data;
|
||||||
}
|
}
|
||||||
|
@ -158,26 +238,26 @@ class PrintServer {
|
||||||
|
|
||||||
// Save PDF to disk
|
// Save PDF to disk
|
||||||
const printer = jobData.printer;
|
const printer = jobData.printer;
|
||||||
const tmpPath = path.join(appDir, 'tmp')
|
const tmpPath = conf.tmpDir;
|
||||||
if (!fs.existsSync(tmpPath))
|
if (!fs.existsSync(tmpPath))
|
||||||
fs.mkdirSync(tmpPath)
|
fs.mkdirSync(tmpPath)
|
||||||
const tmpFilePath = path.join(tmpPath, `${Math.random().toString(36).substring(7)}.pdf`);
|
const tmpFilePath = path.join(tmpPath, `job-${jobId}.pdf`);
|
||||||
await fs.writeFile(tmpFilePath, pdfData, 'binary');
|
await fs.writeFile(tmpFilePath, pdfData, 'binary');
|
||||||
|
|
||||||
// Print PDF
|
// Print PDF
|
||||||
|
const printCommand = `lp -d "${printer}" "${tmpFilePath}"`;
|
||||||
|
this.jobLog(jobId, 'debug', `print: ${printCommand}`);
|
||||||
try {
|
try {
|
||||||
await pExec(`lp -d "${printer}" "${tmpFilePath}"`);
|
if (!conf.dryPrint) await pExec(printCommand);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
await fs.unlink(tmpFilePath);
|
await fs.unlink(tmpFilePath);
|
||||||
throw new Error(`Print error: ${err.message}`);
|
throw new Error(`Print error: ${err.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await conn.query(updateQuery, ['printed', null, jobId]);
|
await conn.query(updateQuery, ['printed', null, jobId]);
|
||||||
|
this.jobLog(jobId, 'log', `report: ${jobData.report}, printer: ${printer}, get: ${url}`);
|
||||||
|
|
||||||
if (conf.debug)
|
if (!conf.dryPrint) await fs.unlink(tmpFilePath);
|
||||||
console.debug(`(${colors.yellow(jobId)}) Document has been printed`, `[${args.collectionFk}, ${jobData.report}, ${printer}]`.green);
|
|
||||||
|
|
||||||
await fs.unlink(tmpFilePath);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
let message = err.message;
|
let message = err.message;
|
||||||
if (err.name === 'AxiosError' && err.code === 'ERR_BAD_REQUEST') {
|
if (err.name === 'AxiosError' && err.code === 'ERR_BAD_REQUEST') {
|
||||||
|
@ -186,10 +266,17 @@ class PrintServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
await conn.query(updateQuery, ['error', message, jobId]);
|
await conn.query(updateQuery, ['error', message, jobId]);
|
||||||
throw new Error(`(${jobId}) ${message}`);
|
this.jobLog(jobId, 'error', message);
|
||||||
}
|
|
||||||
|
|
||||||
return jobId;
|
const jobErr = new Error(`(${jobId}) ${message}`);
|
||||||
|
jobErr.stack = err.stack;
|
||||||
|
throw jobErr;
|
||||||
|
} finally {
|
||||||
|
conn.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jobLog(jobId, realm, message) {
|
||||||
|
this.log(`Job[${colors.yellow(jobId)}]`, realm, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue