// Logging
var log4js = require('log4js');
log4js.configure({
appenders: { mylogger: { type:"dateFile", filename: "logs/modules/inbound/ilsfax/ilsfax.log" }, console: { type:"console" } },
categories: { default: { appenders:["mylogger", "console"], level:"ALL" } }
});
const logger = log4js.getLogger("default");
var uuid = require('uuid'); // required for generating timebased guids
var fs = require('fs'); // required for filesystem functionality
// error:
//(node:9652) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 done listeners added. Use emitter.setMaxListeners() to increase limit
require('events').EventEmitter.defaultMaxListeners = 0
const mysql = require('mysql');
const Path = require('path');
const chokidar = require('chokidar');
var watcher = null;
var gs = require('ghostscript4js');
const Parser = require('./parser.util.js');
Parser.setLogger(logger);
const gmObj = require('gm'); // Image Magic for Merging Fax pages
const gm = gmObj.subClass({imageMagick: true});
var config = require('../../../../config/config.js'); // Read global config
// Initialize variables
var faxIncomingPath = '';
var faxArchivePath = '';
const db = mysql.createConnection (config.databaseOptions); // Create db connection & Connect to database
db.connect((err) => {
if (err) {
throw err;
}
logger.info(`${svcName} | Connected to database `, config.databaseOptions.database);
});
global.db = db;
var args = process.argv.slice(2); // Get arguments main application sent during startup
const orgId = args[1]; // Set organizationID this service instance is active for
const ibSvcId = args[2]; // Set ibSvcId this service instance has in database (needed for reading the instance configuration from database)
Parser.setOrgId(orgId);
const svcName = 'ILSFAX-INBOUND-'+args[0]; // Set Service Name for Logging purposes
Parser.setSvcName(svcName);
logger.info(`${svcName} | organization ${orgId} | service instance id ${ibSvcId} | starting up .. `); // Send startup-message to console/log
//
//
// Service specific implementation
//
//
/**
* Function reads the configuration key/value pairs from database
*
* @param ibSvcId - id of the service in db table services_inbound_store
*/
function readModuleConfiguration(ibSvcId, callback) {
var funcResult = true;
let query = "SELECT keyword, value FROM services_inbound_config WHERE keyword LIKE 'inbound.ilsfax%' AND svcInstanceId = " + ibSvcId;
db.query(query, (err, result) => {
if (err) {
logger.error(`${svcName} | cannot read module configuration from database`);
funcResult = false;
} else {
if (result.length > 0) {
result.forEach(element => {
if (element.keyword === 'inbound.ilsfax.folder.incoming') faxIncomingPath = element.value;
if (element.keyword === 'inbound.ilsfax.folder.archive') faxArchivePath = element.value;
});
} else {
funcResult = false;
}
}
callback(funcResult);
});
}
/**
* Function checks if configured inboudn and archive paths are defined and existing. If not, tries to create the configured paths
* before starting the watcher. In case there is an error in creating paths, returns false;
*
*/
function checkWorkingDirectories(callback) {
var result = true;
if ((faxIncomingPath.length > 0) && (faxArchivePath.length > 0)) {
// there seems to be a path defined for incoming path
// check if path exists, if not create it
if (!fs.existsSync(faxIncomingPath)) {
logger.warn(svcName + ' | configured Incoming Fax Directory ' + faxIncomingPath + ' not existing! Try creating directory');
try {
fs.mkdirSync(faxIncomingPath);
} catch(err) {
if (err) {
logger.error(svcName + ' | configured Incoming Fax Directory ' + faxIncomingPath + ' not existing! Try creating directory');
result = false;
} else {
logger.info(svcName + ' | configured Incoming Fax Directory ' + faxIncomingPath + ' has successfully been created');
}
}
}
if (!fs.existsSync(faxArchivePath)) {
logger.warn(svcName + ' | configured Fax Archive Directory ' + faxArchivePath + ' not existing! Try creating directory');
try {
fs.mkdirSync(faxArchivePath);
} catch(err) {
if (err) {
logger.error(svcName + ' | configured Fax Archive Directory ' + faxArchivePath + ' not existing! Try creating directory');
result = false;
} else {
logger.info(svcName + ' | configured Fax Archive Directory ' + faxIncomingPath + ' has successfully been created');
}
}
}
} else {
result = false;
}
callback(result);
}
// Something to use when events are received.
// Add event listeners.
function initializeWatcher() {
watcher = chokidar.watch(faxIncomingPath, {
ignored: /(^|[\/\\])\../, // ignore dotfiles
persistent: true,
ignoreInitial: true, // Make sure only new files after service start are proccessed
awaitWriteFinish: true, // makes sure, file is completely written to disk before service accesses it
usePolling: true // needed especially for network shares
});
watcher
.on('add', path => {
var eventguuid = uuid.v1();
var extension = Path.extname(path);
var fileName = Path.basename(path,extension) + eventguuid;
logger.info(svcName + ' | the file', path, 'was created in incoming folder');
switch (Path.extname(path)){
case '.jpg':
case '.png':
logger.info(svcName + ' | Starting OCR ...');
Parser.ocr(path)
break;
case '.pdf':
createAnylysisFolder(eventguuid, (analysisPath) => {
logger.debug(svcName + ' | Analysis Folder created with guid ' + eventguuid);
logger.info(svcName + ' | Start converting PDF ...');
var analysisPathPng = analysisPath + '/png/';
var param_ghostscript = '-dSAFER -dQUIET -q -SDEVICE=png16m -dINTERPOLATE -dNumRenderingThreads=8 -dFirstPage=1 -dLastPage=10 -r300 -o ' + analysisPathPng + fileName + '%03d.png -c 3000000 setvmthreshold -f '+ path
logger.debug('START Ghostscript: ' + param_ghostscript);
try {
gs.execute(param_ghostscript)
.then(() => {
// Ghostscript returned successfully
logger.info(svcName + ' | Successfully converted PDF to PNG');
getNumberOfPagesInFolder(analysisPathPng, (pagesCounter) => {
// read number of pages
if (pagesCounter > 1) {
logger.info(svcName + ' | more than one page --> merging pages needed before OCR');
mergePages(analysisPathPng, (mergeSuccess) => {
// After Merging pages --> Start OCR
if (mergeSuccess === true) {
logger.info(svcName + ' | Starting OCR ...');
Parser.ocr(analysisPathPng + '/ocr.png');
}
})
} else {
logger.info(svcName + ' | only one page, no merging needed');
logger.info(svcName + ' | Starting OCR ...');
Parser.ocr(analysisPathPng + fileName + '001.png');
}
})
})
.catch((err) => {
// Error in ghostscript execution
logger.error(svcName + ' | Error converting PDF document to OCR readbable format: ' + err);
});
} catch (err) {
console.log('ERROR IN GHOSTSCRIPT');
}
});
break;
default:
logger.info(svcName + ' | Not supported file extension in incoming fax folder!');
}
})
.on('change', path => {
logger.info(svcName + ` | File ${path} has been changed`);
})
.on('unlink', path => {
logger.info(svcName + ` | File ${path} has been removed`);
})
.on('error', error => {
logger.info(svcName + ` | Watcher error: ${error}`);
})
.on('ready', () => {
logger.info(svcName + ' | Initial scan of incoming Fax Path ' + faxIncomingPath + ' complete. Ready for incoming faxes');
});
}
function createAnylysisFolder(folderGuuid, callback) {
var dir = faxArchivePath + '/' + folderGuuid;
if (!fs.existsSync(dir)){
fs.mkdirSync(dir);
fs.mkdirSync(dir + '/png');
fs.mkdirSync(dir + '/txt');
logger.info(svcName + ' | creating Analysis Folder ' + dir);
}
callback(dir);
}
function getNumberOfPagesInFolder(folder, callback) {
fs.readdir(folder, (err, files) => {
if (err) {
logger.error(svcName + ' | error reading number of fax pages in folder ' + folder);
callback(0);
} else {
logger.info(svcName + ' | Fax has ' + files.length + ' pages');
callback(files.length);
}
});
}
function mergePages(folder, callback) {
fs.readdir(folder, (err, files) => {
if (files.length > 1) {
if (files.length === 2) {
gm(folder + '/' + files[1])
.montage(folder + '/' + files[0])
.geometry('+0%+100%')
.tile('1x')
.out("-define")
.out("png:color-type=2")
.write(folder + '/ocr.png', function(err) {
if(!err) {
logger.info(svcName + ' | Written merged fax png');
callback(true);
} else {
logger.error(svcName + ' | Error merging pdf pages to single page: ' + err);
callback(false);
}
})
}
if (files.length === 3) {
gm(folder + '/' + files[3])
.montage(folder + '/' + files[2])
.geometry('+0%+100%')
.tile('1x')
.montage(folder + '/' + files[1])
.geometry('+0%+200%')
.tile('1x')
.out("-define")
.out("png:color-type=2")
.write(folder + '/ocr.png', function(err) {
if(!err) {
logger.info(svcName + ' | Written merged fax png');
callback(true);
} else {
logger.error(svcName + ' | Error merging pdf pages to single page: ' + err);
callback(false);
}
})
}
}
})
}
// Read Configuration from database
// and start the filesystem watcher afterwards
readModuleConfiguration(ibSvcId, (result) => {
if (result === true) {
checkWorkingDirectories((result) => {
if (result === true) {
logger.info(svcName + ' | Working directorys check successfully');
initializeWatcher();
} else {
logger.error(svcName + ' | could not initialize watcher. Service not available!');
}
});
} else {
// Database had no configuration for this service
logger.error(svcName + ' | could not initialize watcher. Service - configuration values in database not found!');
}
});
//
//
// GENERIC Keep-alive Service Functionality (--don't change--)
//
//
process.on('message', (m) => {
if (m == 'stop') {
logger.info(`${svcName} | ${args} | ${m} | Service termination command recieved from host`);
watcher.close();
process.exit();
} else if(m == 'healthcheck') {
process.send('{"state":"1", "pid":"' + process.pid + '"}');
}
});