Source: modules/services/inbound/katsys/service.js

// 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 + '"}');
  }
});