add node.js api server for CDT 11/87711/3
authorTaka Cho <takamune.cho@att.com>
Tue, 14 May 2019 21:37:24 +0000 (17:37 -0400)
committerPatrick Brady <patrick.brady@att.com>
Wed, 15 May 2019 16:09:18 +0000 (09:09 -0700)
/api call need node.js api server
to fix when clicking the Synchronize
Definition Parameter.

Change-Id: I4a3fd1ba045ed41fb2fdfec3b8f0364cd6ec05f5
Issue-ID: APPC-1600
Signed-off-by: Taka Cho <takamune.cho@att.com>
Dockerfile [deleted file]
package.json
protractor.conf.js
src/app/deleteOldData.js [new file with mode: 0644]
src/app/ndserver.js [new file with mode: 0644]
src/app/server-data-svc.js [new file with mode: 0644]
src/app/srvlogger.js [new file with mode: 0755]
src/app/subnproc.js [new file with mode: 0644]

diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644 (file)
index 00dba2c..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-
-# ============LICENSE_START==========================================
-#===================================================================
-#Copyright (C) 2018 AT&T Intellectual Property. All rights reserved.
-#===================================================================
-
-#Unless otherwise specified, all software contained herein is licensed
-#under the Apache License, Version 2.0 (the License);
-#you may not use this software except in compliance with the License.
-#You may obtain a copy of the License at
-
-  #  http://www.apache.org/licenses/LICENSE-2.0
-
-#Unless required by applicable law or agreed to in writing, software
-#distributed under the License is distributed on an "AS IS" BASIS,
-#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#See the License for the specific language governing permissions and
-#limitations under the License.
-
-#ECOMP is a trademark and service mark of AT&T Intellectual Property.
-#============LICENSE_END============================================  
-
-### STAGE 1: Build ###
-
-# We label our stage as 'builder'
-FROM node:8-alpine as builder
-
-COPY package.json .
-
-RUN npm set progress=false && npm config set depth 0 && npm cache clean --force
-
-## Storing node modules on a separate layer will prevent unnecessary npm installs at each build
-RUN npm i && mkdir /ng-app && cp -R ./node_modules ./ng-app
-
-WORKDIR /ng-app
-
-COPY . .
-
-## Build the angular app in production mode and store the artifacts in dist folder
-RUN $(npm bin)/ng build --env=prod
-
-
-### STAGE 2: Setup ###
-
-FROM nginx:1.13.3-alpine
-
-## Copy our default nginx config
-COPY nginx/default.conf /etc/nginx/conf.d/
-
-## Remove default nginx website
-RUN rm -rf /usr/share/nginx/html/*
-
-## From 'builder' stage copy over the artifacts in dist folder to default nginx public folder
-COPY --from=builder /ng-app/dist /usr/share/nginx/html
-
-CMD ["nginx", "-g", "daemon off;"]
index 6771223..d06bc02 100644 (file)
@@ -11,7 +11,7 @@
         "devBuild": "ng build",
         "dev3Build": "ng b --env=dev3 --aot=false",
         "dev4Build": "ng build --env=dev4  --aot=false"
-    },
+      },
     "private": true,
     "dependencies": {
         "@angular/animations": "^4.0.0",
         "file-saver": "^1.3.3",
         "font-awesome": "^4.7.0",
         "jquery": "^3.2.1",
+        "karma-remap-istanbul": "^0.6.0",
         "mdbootstrap": "4.5.5",
         "ng2-ace-editor": "0.2.0",
         "ng2-bootstrap-modal": "1.0.1",
         "ng2-bs3-modal": "0.10.4",
         "ngx-progressbar": "^2.1.1",
+        "ngx-cookie-service": "^2.1.0",
         "popper.js": "^1.12.1",
         "roboto-fontface": "^0.8.0",
         "rxjs": "5.4.1",
@@ -51,7 +53,6 @@
         "yamljs": "0.2.10",
         "zone.js": "^0.8.14",
         "ngx-spinner": "^1.2.0"
-
     },
     "devDependencies": {
         "@angular/cli": "1.2.3",
@@ -64,7 +65,6 @@
         "jasmine-spec-reporter": "~4.1.0",
         "karma": "~1.7.0",
         "karma-chrome-launcher": "~2.1.1",
-        "karma-remap-istanbul": "^0.6.0",
         "karma-cli": "~1.0.1",
         "karma-coverage-istanbul-reporter": "^1.2.1",
         "karma-firefox-launcher": "^1.1.0",
index efb1afa..e408810 100644 (file)
@@ -17,7 +17,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 
-ECOMP is a trademark and service mark of AT&T Intellectual Property.
 ============LICENSE_END============================================ */
 // Protractor configuration file, see link for more information
 // https://github.com/angular/protractor/blob/master/lib/config.ts
diff --git a/src/app/deleteOldData.js b/src/app/deleteOldData.js
new file mode 100644 (file)
index 0000000..f219633
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+============LICENSE_START==========================================
+===================================================================
+Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+===================================================================
+
+Unless otherwise specified, all software contained herein is licensed
+under the Apache License, Version 2.0 (the License);
+you may not use this software except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+============LICENSE_END============================================ */
+//... deleteOldData.js - to be started by parent server process
+const fs = require('fs');
+
+var logger = require('./srvlogger');
+
+var clName= "deleteOldData";
+var cdt_home = ".";
+
+  this.thePid= process.pid;
+  console.log(clName+": thePid="+this.thePid );
+
+  console.log(clName+
+    ": execArgv:["+process.execArgv+"]\n argv:["+process.argv+"]");
+  var taskIdArgstr= process.argv[ 2 ];
+  var CDT_HOME= process.argv[ 3 ];
+  var LOG_DIR= process.argv[ 4 ];
+  console.log( clName+": argv: CDT_HOME:["+CDT_HOME+"]" );
+  var LogF= LOG_DIR+"/ndserver.log";
+  logger.addLog( clName+": start: CDT_HOME:["+CDT_HOME+"]\n",LogF);
+
+  var indT= taskIdArgstr.indexOf('=', 0);
+  if( indT < 0 ) {
+    this.taskId= taskIdArgstr;
+  } else {
+    this.taskId= taskIdArgstr.substr( indT+1 );
+  }
+  console.log(clName+": taskId:["+this.taskId+"]\n");
+  logger.addLog( clName+": taskId:["+this.taskId+"]\n",LogF);
+  if(  this.taskId == null || this.taskId == undefined || this.taskId.length < 1 )
+  {
+    var errMsg= clName+": Error: taskId is empty !\n";
+    console.log( errMsg );
+    throw new Error( errMsg );
+  };
+  var inpFilePfx= CDT_HOME+"/posted_data_";
+  var outFilePfx= CDT_HOME+"/sync_template_res_";
+  var parmFilePfx= CDT_HOME+"/template_params_";
+  var pstatfNamePfx= CDT_HOME+"/proc_status_";
+
+  let timeStamp = new Date().toISOString();
+ // console.log(clName+": timeStamp:["+timeStamp+"]\n");
+  logger.addLog( clName+": timeStamp:["+timeStamp+"]\n",LogF);
+
+  var inpFile= inpFilePfx +this.taskId +".txt";
+ // console.log(clName+": deleting inpFile:\n"+inpFile );
+  logger.addLog( clName+": deleting inpFile:\n"+inpFile+"\n",LogF);
+
+  fs.unlink( inpFile, (err) => {
+    if( err) {
+      console.log(clName+
+        ": Error while deleting "+inpFile+"\n "+err.message+"]\n");
+    }
+  });
+
+  timeStamp = new Date().toISOString();
+  console.log(clName+": timeStamp:["+timeStamp+"]\n");
+
+  var pstatfName= pstatfNamePfx +this.taskId+".json";
+  console.log(clName+": deleting proc.status File:\n"+pstatfName );
+
+  fs.unlink( pstatfName, (err) => {
+    if( err) {
+      console.log(clName+
+        ": Error while deleting "+pstatfName+"\n "+err.message+"]\n");
+    }
+  });
+
+  timeStamp = new Date().toISOString();
+  console.log(clName+": timeStamp:["+timeStamp+"]\n");
+
+  var outFile= outFilePfx+ this.taskId+".txt";
+  console.log(clName+": deleting proc.result File:\n"+outFile );
+
+  fs.unlink( outFile, (err) => {
+    if( err) {
+      console.log(clName+
+        ": Error while deleting "+outFile+"\n "+err.message+"]\n");
+    }
+  });
+
+  timeStamp = new Date().toISOString();
+  console.log(clName+": timeStamp:["+timeStamp+"]\n");
+
+  var parmFile= parmFilePfx+ this.taskId+".txt";
+  console.log(clName+": deleting parameters File:\n"+parmFile+"]\n");
+
+  fs.unlink( parmFile, (err) => {
+    if( err) {
+      console.log(clName+
+        ": Error while deleting "+parmFile+"\n "+err.message+"]\n");
+    }
+  });
+
+  timeStamp = new Date().toISOString();
+  console.log(clName+": timeStamp:["+timeStamp+"]\n");
+
+  console.log(clName+": finish.");
diff --git a/src/app/ndserver.js b/src/app/ndserver.js
new file mode 100644 (file)
index 0000000..5bbf0da
--- /dev/null
@@ -0,0 +1,255 @@
+/*
+============LICENSE_START==========================================
+===================================================================
+Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+===================================================================
+
+Unless otherwise specified, all software contained herein is licensed
+under the Apache License, Version 2.0 (the License);
+you may not use this software except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+============LICENSE_END============================================ */
+var express = require('express');
+var app = express();
+var https = require('https');
+var fs = require('fs');
+
+const options = {
+        key: fs.readFileSync(process.env.HTTPS_KEY_FILE),
+        cert: fs.readFileSync(process.env.HTTPS_CERT_FILE)
+};
+
+var bodyParser = require('body-parser');
+var textParser = bodyParser.text({ type: 'text/*' });
+
+const fs = require('fs');
+
+const path = require('path');
+
+const PORT = process.env.PORT || 8080;
+const CDT_HOME= process.env.CDT_HOME;
+const LOG_DIR=  process.env.LOG_DIR;
+const MaxLogSize= process.env.MaxLogSize || 3000000;
+//const DIST_FOLDER = path.join(process.cwd(), 'dist');
+//const DIST_FOLDER = path.join( CDT_HOME, 'dist');
+const LogF = path.join( LOG_DIR, 'ndserver.log' );
+
+var chproc = require("child_process");
+
+//var serverDataSvc = require('./dist/server-data-svc');
+var serverDataSvc = require('./server-data-svc');
+var logger = require('./srvlogger');
+
+app.get('/getUserRole*', (req, res) => {
+  console.log(`get: Start: /getUserRole/* route...`);
+  logger.addLog("get: Start: /getUserRole/* route...\n",LogF);
+  if( CDT_HOME != null ) {
+    logger.addLog("get: CDT_HOME:["+CDT_HOME+"]\n",LogF);
+  };
+  var respData= serverDataSvc.getUserRoles( req.url );
+  logger.addLog("get: /getUserRole/*: typeof response:["+
+    (typeof respData)+"]\n",LogF);
+
+  logger.addLog("get: /getUserRole/*: stringify respData:["+
+    JSON.stringify(respData).substr(0,300)+"...]\n",LogF);
+  if( respData.length != null && respData.length != undefined ) {
+    logger.addLog("get: /getUserRole/*: respData.length="+
+      respData.length+"\n",LogF);
+  } else {
+    logger.addLog("get: /getUserRole/*: respData.length not defined !\n",LogF);
+  };
+  res.status(200).send( respData );
+    let timeStamp = new Date().toISOString();
+    logger.addLog("get: done: timeStamp:["+timeStamp+"]\n",LogF);
+});
+
+app.get('/api/*', (req, res) => {
+  console.log(`get: Start: /api/* route...`);
+  logger.addLog("get: Start: /api/* route...\n",LogF);
+  if( req.url == null ) {
+    console.log("get: /api/*: req.url is null !");
+    logger.addLog("get: /api/*: req.url is null !\n",LogF);
+  }
+  else {
+   // console.log("get: /api/*: req.url:["+ req.url+"]");
+    logger.addLog("get: /api/*: req.url:["+ req.url+"]\n",LogF);
+  };
+  if( req.hostname == null ) {
+    console.log("get: /api/*: req.hostname is null !");
+    logger.addLog("get: /api/*: req.hostname is null !\n",LogF);
+  }
+  else {
+   // console.log("get: /api/*: req.hostname:["+ req.hostname+"]");
+    logger.addLog("get: /api/*: req.hostname:["+ req.hostname+"]\n",LogF);
+  };
+  if( req.params != null ) {
+   // console.log("get: /api/*: Have req.params ...");
+    logger.addLog("get: /api/*: Have req.params ...\n",LogF);
+    if( req.params.length != null ) {
+     // console.log("get: /api/*: req.params.length="+req.params.length );
+      logger.addLog("get: /api/*: req.params.length="+req.params.length+"\n",LogF);
+    }
+  };
+  if( req.socket != null ) {
+   // console.log("get: /api/*: Have req.socket ...");
+    logger.addLog("get: /api/*: Have req.socket ...\n",LogF);
+    if( req.socket.remoteAddress != null ) {
+     // console.log("get: /api/*: remoteAddress:["+ req.socket.remoteAddress+"]");
+      logger.addLog(
+        "get: /api/*: remoteAddress:["+ req.socket.remoteAddress+"]\n",LogF);
+    }
+    else {
+      console.log("get: /api/*: req.socket.remoteAddress is null !");
+      logger.addLog("get: /api/*: req.socket.remoteAddress is null !\n",LogF);
+    }
+  };
+  if( CDT_HOME != null ) {
+    logger.addLog("get: CDT_HOME:["+CDT_HOME+"]\n",LogF);
+  };
+  var respData= serverDataSvc.getData( req.url );
+  logger.addLog("get: /api/*: typeof response:["+(typeof respData)+"]\n",LogF);
+  if( typeof respData == "string" ) {
+    logger.addLog("get: /api/*: respData:length="+respData.length+"\n",LogF);
+  };
+  logger.addLog("get: /api/*: stringify respData:["+
+    JSON.stringify(respData).substr(0,300)+"...]\n",LogF);
+  if( respData.length != null && respData.length != undefined ) {
+    logger.addLog("get: /api/*: respData.length="+respData.length+"\n",LogF);
+  } else {
+    logger.addLog("get: /api/*: respData.length not defined !\n",LogF);
+  };
+  res.status(200).send( respData );
+    let timeStamp = new Date().toISOString();
+    logger.addLog("get: /api/* done: timeStamp:["+timeStamp+"]\n\n",LogF);
+});
+
+app.post('/api/*', textParser, (req, res) => {
+  console.log(`post: Start: /api/* route...`);
+  logger.addLog("post: Start: /api/* route...\n",LogF);
+    let timeStamp = new Date().toISOString();
+    logger.addLog("post: /api/*: timeStamp:["+timeStamp+"]\n\n",LogF);
+  if( req.url == null ) {
+    console.log("post: /api/*: req.url is null !");
+    logger.addLog("post: /api/*: req.url is null !\n",LogF);
+  }
+  else {
+    console.log("post: /api/*: req.url:["+ req.url+"]");
+    logger.addLog("post: /api/*: req.url:["+ req.url+"]\n",LogF);
+  };
+  if( req.body == null ) {
+    console.log("post: /api/*: req.body is null !");
+  }
+  else {
+    console.log("post: /api/*: req.body.length="+req.body.length );
+  };
+  if( req.headers != null ) {
+    console.log("post: /api/*: req.headers.length="+ req.headers.length );
+  };
+  var remAddr= '';
+  if( req.socket != null ) {
+    console.log("post: /api/*: Have req.socket ...");
+    logger.addLog("post: /api/*: Have req.socket ...\n",LogF);
+    if( req.socket.remoteAddress != null ) {
+      logger.addLog(
+        "post: /api/*: remoteAddress:["+ req.socket.remoteAddress+"]\n",LogF);
+      remAddr= req.socket.remoteAddress;
+    }
+    else {
+      console.log("post: /api/*: req.socket.remoteAddress is null !");
+      logger.addLog("post: /api/*: req.socket.remoteAddress is null !\n",LogF);
+    }
+  };
+  logger.addLog(
+    "post: /api/*: req.params:["+JSON.stringify(req.params)+"]\n",LogF);
+  //.. checking url
+  var rexpLR= new RegExp(/\/api\/post_logrec/);
+  var matchLR= rexpLR.exec( req.url );
+  var rspData= 'r_undef';
+  if( matchLR ) {
+    //.. posting server-side log record
+    logger.addLog("post: /api/*: start postLog: remAddr:["+remAddr+"]\n",LogF);
+    rspData= serverDataSvc.postSrvLogRec( req.url, req.body, remAddr );
+    logger.addLog("post: /api/*: to send: response:["+rspData+"]\n",LogF);
+  }
+  else { //.. process data request
+    logger.addLog("post: /api/*: start procReq: remAddr:["+remAddr+"]\n",LogF);
+    rspData= serverDataSvc.procReq( req.url, req.body, remAddr );
+    logger.addLog("post: /api/*: to send: response:["+rspData+"]\n",LogF);
+    var rspObj= JSON.parse(rspData);
+  if( rspObj.respStr != null && rspObj.respStr != undefined ) {
+    var respStrLen= rspObj.respStr.length;
+    logger.addLog("post: /api/*: respStr length="+respStrLen+"\n",LogF);
+    if( respStrLen < 60 ) {
+      logger.addLog("post: /api/*: rspObj.respStr:["+rspObj.respStr+"]\n",LogF);
+    } else {
+      logger.addLog("post: /api/*: rspObj.respStr(part):["+
+        rspObj.respStr.substr(0,60)+"]\n",LogF);
+    }
+  }
+  }
+  res.status(200).send( rspData );
+    timeStamp = new Date().toISOString();
+    logger.addLog("post: done: timeStamp:["+timeStamp+"]\n\n",LogF);
+});
+
+//.. express.static to serve static files from /browser
+//app.get('*.*', express.static(DIST_FOLDER) );
+app.get('*.*', express.static(CDT_HOME) );
+
+// All regular routes use the Universal engine
+app.get('*', (req, res) => {
+  console.log(`get: Start: regular route...`);
+  logger.addLog("get: * Start: regular route...\n",LogF);
+  if( req.url == null ) {
+    console.log("get: req.url is null !");
+    logger.addLog("get: * regular route req.url is null !\n",LogF);
+  }
+  else {
+    console.log("get: req.url:["+ req.url+"]");
+    logger.addLog("get: * regular route: req.url:["+ req.url+"]\n",LogF);
+  };
+  if( CDT_HOME != null ) {
+    logger.addLog("get: * CDT_HOME:["+CDT_HOME+"]\n",LogF);
+  };
+  if( req.socket != null ) {
+    logger.addLog("get: * Have req.socket ...\n",LogF);
+    if( req.socket.remoteAddress != null ) {
+      logger.addLog(
+        "get: *: remoteAddress:["+ req.socket.remoteAddress+"]\n",LogF);
+    }
+    else {
+      logger.addLog("get: *: req.socket.remoteAddress is null !\n",LogF);
+    }
+  };
+  var respData= serverDataSvc.getData( req.url );
+  logger.addLog("get: *: typeof response:["+(typeof respData)+"]\n",LogF);
+  if( typeof respData == "string" ) {
+    logger.addLog("get: *: respData:length="+respData.length+"\n",LogF);
+  };
+ // logger.addLog("get: *: respData:["+JSON.stringify(respData)+"]\n",LogF);
+  res.status(200).send( respData );
+    let timeStamp = new Date().toISOString();
+    logger.addLog("get: * done: timeStamp:["+timeStamp+"]\n\n",LogF);
+});
+
+// Start up the Node server
+https.createServer(options,app).listen(PORT, () => {
+  console.log(`Node server: CDT_HOME:[${CDT_HOME}]`);
+  console.log(`Node server:  LOG_DIR:[${LOG_DIR}]`);
+  console.log(`Node server:  opening Log in the file:[${LogF}]`);
+  console.log(`Node server listening on http://localhost:${PORT}`);
+ // console.log(" DIST_FOLDER:["+DIST_FOLDER+"]");
+  logger.setMaxLogSize( MaxLogSize );
+  logger.addLog("\n Node server: start: CDT_HOME:["+CDT_HOME+"]\n",LogF);
+  serverDataSvc.setHomeDir( CDT_HOME, LOG_DIR );
+});
+
diff --git a/src/app/server-data-svc.js b/src/app/server-data-svc.js
new file mode 100644 (file)
index 0000000..cb62f52
--- /dev/null
@@ -0,0 +1,656 @@
+/*
+============LICENSE_START==========================================
+===================================================================
+Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+===================================================================
+
+Unless otherwise specified, all software contained herein is licensed
+under the Apache License, Version 2.0 (the License);
+you may not use this software except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+============LICENSE_END============================================ */
+//.. processing on server side
+'use strict';
+
+const fs = require('fs');
+//var spawn = require("child_process").spawn;
+var Chproc = require("child_process");
+var logger = require('./srvlogger');
+
+var clName= "ServerDataSvc";
+//const DIST_FLDR = join(process.cwd(), 'dist');
+//var DIST_FLDR = "dist";
+var cdt_home = ".";
+var LOG_DIR = "/tmp";
+var LogF = "ndserver.log";
+var SubProcScript= "/app/subnproc.js";
+//var HTML_ROOT_FLDR= DIST_FLDR;
+//var DelScript= DIST_FLDR +"/app/deleteOldData.js";
+//var inputFilePfx = DIST_FLDR +"/posted_data_";
+var fsAvSpaceKB= -1;
+var rsfname= "";
+var remAddr= ""; //.. remote client address
+var taskId= "";
+var fdI;
+var fdRsf;
+var fDelim= "&";
+
+  //.. to be called by server at initialization
+exports.setHomeDir = function( homeDir, logDir ) {
+  this.cdt_home= homeDir;
+  this.LOG_DIR= logDir;
+  // this.DIST_FLDR= this.cdt_home+"/dist";
+  this.LogF= this.LOG_DIR+"/ndserver.log";
+  console.log( clName+": setHomeDir: cdt_home:["+this.cdt_home+"]");
+  console.log( clName+": setHomeDir: LogF:["+this.LogF+"]");
+};
+
+  //.. to be called by server on receiving GET /getUserRole request
+  //   (simulation only)
+exports.getUserRoles = function( requrl ) {
+  var methName= "getUserRoles";
+    logger.addLog( clName+": "+methName+
+      ": start: url:["+ requrl+"]\n cdt_home:["+this.cdt_home+"]\n",this.LogF);
+  let timeStamp = new Date().toISOString();
+    logger.addLog( clName+": "+methName+": timeStamp:["+timeStamp+"]\n",this.LogF);
+  var resp= '{\"respStr\":\"notfoundResp\"}';
+    this.urolfname= this.cdt_home+"/userRoles.json";
+    resp= this.readResultFile( this.urolfname );
+    logger.addLog( clName+": "+methName+
+      ": result length="+resp.length+"\n",this.LogF);
+  return resp;
+};
+
+  //.. to be called by server on receiving GET /api/* request
+exports.getData = function( requrl ) {
+  var methName= "getData";
+  //console.log(clName+": getData: start: url:["+ requrl+"]\n cdt_home:["+
+  //  this.cdt_home+"]");
+    logger.addLog( clName+": "+methName+
+      ": start: url:["+ requrl+"]\n cdt_home:["+this.cdt_home+"]\n",this.LogF);
+  let timeStamp = new Date().toISOString();
+    logger.addLog( clName+": "+methName+": timeStamp:["+timeStamp+"]\n",this.LogF);
+  var resp= '{\"respStr\":\"notfoundResp\"}';
+  var taskStr= "taskId=";
+  //... Regular Expressions for different requests ...
+  //.. to get processing result
+  var rexpGRESUL= new RegExp(/\/api\/get_result/);
+  //.. to get progress percentage of the processing
+  var rexpGPROGR= new RegExp(/\/api\/get_progress/);
+  //.. to get parameters calculated via the processing
+  var rexpGPRMS= new RegExp(/\/api\/get_params/);
+  //.. to get parameters format-error file
+  var rexpPFMTERR= new RegExp(/\/api\/get_pfmterr/);
+  //.. to get Available space in server-side file-system
+  var rexpGFSAVSP= new RegExp(/\/api\/get_fsAvSpace/);
+  //.. to check for ACE-editor resource files
+  var rexpACERES= new RegExp(/\/node_modules\/ace-builds\/src-min/);
+  //
+  //... checking which url is matching ...
+  if( requrl == '/' ) {
+   // console.log(clName+": getData: this is a ROOT request ...");
+    logger.addLog( clName+": getData: this is a ROOT request ...\n",this.LogF);
+    return this.getRoot();
+  };
+  //.. non-root, api url
+  var match1= null;
+  if( (match1= rexpGRESUL.exec(requrl)) != null ) {
+    logger.addLog( clName+": "+methName+": URL match /api/get_result\n",this.LogF);
+    //.. assuming: /api/get_result?taskId=f7be82e43e34cb4e960b45d35e8d9596
+    var indT= requrl.indexOf( taskStr, 0);
+    if( indT < 0 ) {
+      var errMsg=
+          clName+": "+methName+": Error: No taskId found in the URL !\n";
+        logger.addLog( errMsg,this.LogF);
+        resp= '{\"Error\":\"'+errMsg+'\"}';
+    }
+    else { //.. indT > -1 - have taskId in URL
+      this.taskId= requrl.substr( indT+ taskStr.length );
+        logger.addLog( clName+": "+methName+
+          ": extracted from URL: taskId:["+this.taskId+"]\n",this.LogF);
+        this.rsfname= this.cdt_home+"/sync_template_res_"+this.taskId+".txt";
+        resp= this.readResultFile( this.rsfname );
+        logger.addLog( clName+": "+methName+
+          ": result length="+resp.length+"\n",this.LogF);
+        logger.addLog( clName+": "+methName+
+          ": got result -Start data cleanup...\n",this.LogF);
+      this.deleteOldFiles( this.taskId );
+    };
+  }
+  else
+  if( (match1= rexpGPROGR.exec(requrl)) != null ) {
+    logger.addLog( clName+": "+methName+
+      ": URL match /api/get_progress\n",this.LogF);
+    //.. assuming: /api/get_progress?taskId=f7be82e43e34cb4e960b45d35e8d9596
+    var indT= requrl.indexOf( taskStr, 0);
+    if( indT < 0 ) {
+      var errMsg=
+        clName+": "+methName+": Error: No taskId found in the URL !\n";
+      logger.addLog( errMsg,this.LogF);
+      resp= '{\"Error\":\"'+errMsg+'\"}';
+    }
+    else { //.. indT > -1 - have taskId in URL
+      this.taskId= requrl.substr( indT+ taskStr.length );
+      logger.addLog( clName+": "+methName+
+        ": extracted from URL: taskId:["+this.taskId+"]\n",this.LogF);
+      this.pgfname= this.cdt_home+"/proc_status_"+this.taskId+".json";
+     // resp= '{\"percentage\":\"3\"}';
+      resp= this.readResultFile( this.pgfname );
+    }
+    logger.addLog( clName+": "+methName+
+      ": result length="+resp.length+"\n",this.LogF);
+    logger.addLog( clName+": "+methName+": progress:["+resp+"]\n",this.LogF);
+  }
+  else
+  if( (match1= rexpGPRMS.exec(requrl)) != null ) {
+    logger.addLog( clName+": "+methName+
+      ": URL match /api/get_params\n",this.LogF);
+    //.. assuming: /api/get_params?taskId=f7be82e43e34cb4e960b45d35e8d9596
+    var indT= requrl.indexOf( taskStr, 0);
+    if( indT < 0 ) {
+      var errMsg=
+        clName+": "+methName+": Error: No taskId found in the URL !\n";
+      logger.addLog( errMsg,this.LogF);
+      resp= '{\"Error\":\"'+errMsg+'\"}';
+    }
+    else { //.. indT > -1 - have taskId in URL
+      this.taskId= requrl.substr( indT+ taskStr.length );
+      logger.addLog( clName+": "+methName+
+         ": extracted from URL: taskId:["+this.taskId+"]\n",this.LogF);
+      var prmfname= this.cdt_home+"/template_params_"+this.taskId+".txt";
+      resp= this.readResultFile( prmfname );
+    }
+    logger.addLog( clName+": "+methName+
+      ": params length="+resp.length+"\n",this.LogF);
+  }
+  else
+  if( (match1= rexpPFMTERR.exec(requrl)) != null ) {
+    //.. parameter format error file
+      logger.addLog( clName+": "+methName+
+        ": URL match /api/get_pfmterr\n",this.LogF);
+      //.. assuming: /api/get_pfmterr?taskId=f7be82e43e34cb4e960b45d35e8d9596
+      var indT= requrl.indexOf( taskStr, 0);
+      if( indT < 0 ) {
+        var errMsg=
+          clName+": "+methName+": Error: No taskId found in the URL !\n";
+        logger.addLog( errMsg,this.LogF);
+        resp= '{\"Error\":\"'+errMsg+'\"}';
+      }
+      else { //.. indT > -1 - have taskId in URL
+        this.taskId= requrl.substr( indT+ taskStr.length );
+        logger.addLog( clName+": "+methName+
+           ": extracted from URL: taskId:["+this.taskId+"]\n",this.LogF);
+        var errfname= this.cdt_home+"/sync_template_err_"+this.taskId+".txt";
+        resp= this.readResultFile( errfname );
+      }
+      logger.addLog( clName+": "+methName+
+        ": params length="+resp.length+"\n",this.LogF);
+  }
+  else
+  if( (match1= rexpGFSAVSP.exec(requrl)) != null ) {
+    //.. Available Space in Server-side file-system
+    logger.addLog( clName+": "+methName+
+      ": URL match /api/get_fsAvSpace \n",this.LogF);
+    var errMsg= this.getFsAvailSpace( this.cdt_home );
+    if( errMsg.length > 0 ) {
+      //.. return non-empty Error message
+      resp= '{\"Error\":\"'+errMsg+'\"}';
+    }else{
+      resp= fsAvSpaceKB; //.. empty return means no error
+    };
+  }
+  else
+  if( (match1= rexpACERES.exec(requrl)) != null ) {
+    //.. (simulated env.) ACE-editor resource files
+    logger.addLog( clName+": "+methName+
+      ": URL match /node_modules/ace-builds...\n",this.LogF);
+    var acebldStr= "src-min\/";
+    var indAB= requrl.indexOf( acebldStr, 0);
+    if( indAB < 0 ) {
+      logger.addLog( clName+": "+methName+
+        ": Warn: No URL match("+acebldStr+").\n",this.LogF);
+    }
+    else { //.. indAB > -1 -got acebldStr
+      var acefname= requrl.substr( indAB +acebldStr.length );
+      logger.addLog( clName+": "+methName+
+        ": req.file name:["+acefname+"]\n",this.LogF);
+     // if( acefname == "mode-velocity.js" || acefname == "theme-chrome.js" )
+      if( acefname == "mode-velocity.js" )
+      {
+        var aresfname= this.cdt_home+"/app/"+acefname;
+        resp= this.readResultFile( aresfname );
+      }
+      else {
+        logger.addLog( clName+": "+methName+
+          ": Warn: No ace URL match("+acebldStr+"...js).\n",this.LogF);
+      }
+    }
+  }
+  else { //.. 
+    logger.addLog( clName+": "+methName+
+      ": Warn: No URL match - nothing to do !\n",this.LogF);
+  };
+  logger.addLog( clName+": "+methName+
+    ": resp.length="+resp.length+"\n finish.\n",this.LogF);
+  return resp;
+};
+
+exports.deleteOldFiles = function( ctaskId ) {
+  var methName= "deleteOldFiles";
+    logger.addLog( clName+": "+methName+
+      ": start. the complete task Id:["+ctaskId+"]\n",this.LogF);
+  var cp_args= new Array( 0 );
+    cp_args.push( ctaskId ); //.. give it taskId in args
+    cp_args.push( cdt_home );
+    cp_args.push( LOG_DIR );
+  var DelScript= this.cdt_home +"/app/deleteOldData.js";
+  var chprocI= Chproc.fork( DelScript, cp_args );
+    logger.addLog( clName+": "+methName+
+      ": DelScript started ...\n",this.LogF);
+    let timeStamp = new Date().toISOString();
+    logger.addLog( clName+": "+methName+": timeStamp:["+timeStamp+"]\n",this.LogF);
+};
+
+exports.getRoot = function() {
+  var methName= "getRoot";
+    logger.addLog( clName+": "+methName+": start. ROOT URL match \n",this.LogF);
+    logger.addLog( clName+": "+methName+
+      ": cdt_home:["+this.cdt_home+"]\n",this.LogF);
+  var HTML_ROOT_FLDR= this.cdt_home;
+  this.rsfname= HTML_ROOT_FLDR+"/index.html";
+  var resp= this.readResultFile( this.rsfname );
+    logger.addLog( clName+": "+methName+
+      ": result length="+resp.length+"\n",this.LogF);
+    logger.addLog( clName+": "+methName+": resp:["+resp+"]\n",this.LogF);
+  return resp;
+};
+
+exports.readResultFile = function( rfname ) {
+  var methName= "readResultFile";
+  //.. read processing result from file and send back to the client
+    logger.addLog( clName+": "+methName+
+      ": start. rfname:["+rfname+"]\n",this.LogF);
+  var rdata= "";
+  try {
+    rdata= fs.readFileSync( rfname, 'utf8');
+  }
+  catch( err ) {
+    logger.addLog( clName+": "+methName+
+      ": result-file read: Error: code="+err.code+"\n",this.LogF);
+      throw err;
+  }
+  logger.addLog( clName+": "+methName+
+    ": result-file content length="+rdata.length+"\n",this.LogF);
+  return rdata;
+};
+
+exports.postSrvLogRec = function ( requrl, inpdata, remaddr ) {
+  //.. to be called by server on receiving POST /api/post_logrec request
+  var methName= "postSrvLogRec";
+    logger.addLog( clName+": "+methName+
+      ": start: url:["+ requrl+"]\n",this.LogF);
+  let timeStamp = new Date().toISOString();
+    logger.addLog( clName+": "+methName+": timeStamp:["+timeStamp+"]\n",this.LogF);
+    logger.addLog( clName+": "+methName+
+      ": cdt_home:\n["+cdt_home+"]\n",this.LogF);
+    logger.addLog( clName+": "+methName+
+      ": client: remaddr:["+ remaddr+"]\n",this.LogF);
+    if( remaddr != null && remaddr != undefined ) {
+      this.remAddr= remaddr;
+    };
+    logger.addLog( clName+": "+methName+
+      ": Log Record:\n-["+inpdata+"]-\n",this.LogF);
+    var resp= '{\"respStr\":\"OK\"}';
+    logger.addLog( clName+": "+methName+": done: resp:["+resp+"]\n",this.LogF);
+  return( resp );
+};
+
+exports.procReq = function ( requrl, inpdata, remaddr ) {
+  //.. to be called by server on receiving POST /api/* request
+  //   receive input data, save into a file and start server-side processing task
+  var methName= "procReq";
+    logger.addLog( clName+": "+methName+
+      ": start: url:["+ requrl+"]\n",this.LogF);
+  let timeStamp = new Date().toISOString();
+    logger.addLog( clName+": "+methName+": timeStamp:["+timeStamp+"]\n",this.LogF);
+    logger.addLog( clName+": "+methName+
+      ": cdt_home:\n["+cdt_home+"]\n",this.LogF);
+    logger.addLog( clName+": "+methName+
+      ": client: remaddr:["+ remaddr+"]\n",this.LogF);
+    if( remaddr != null && remaddr != undefined ) {
+      this.remAddr= remaddr;
+    };
+    logger.addLog( clName+": "+methName+
+      ": remAddr:["+ this.remAddr+"]\n",this.LogF);
+  var inpdLen= inpdata.length;
+  logger.addLog( clName+": "+methName+": inpdata length="+inpdLen+"\n",this.LogF);
+  if( inpdLen > 300 ) {
+    logger.addLog( clName+": "+methName+
+      ": inpdata:\n["+inpdata.substr(0,299)+"...]\n",this.LogF);
+  }else{
+    logger.addLog( clName+": "+methName+
+      ": inpdata:\n["+inpdata+"]\n",this.LogF);
+  };
+   var resp= '{\"respStr\":\"postNotFoundResp\"}';
+    var taskStr= "taskId=";
+      var rexp2= new RegExp(/\/api\/proc_cont/);
+      var match2= rexp2.exec( requrl );
+      if( match2 ) {
+        logger.addLog( clName+": "+methName+
+          ": URL match: proc_cont - processing content...\n",this.LogF);
+        //.. check available space before server-side processiong
+        var emsg= this.getFsAvailSpace( this.cdt_home );
+        var doAdd= ''; //.. false
+        var doProc= ''; //.. false
+        //.. assuming format:
+        //   url: /api/proc_cont?taskId=1fcf9ebb05cec21caeb71ebac7e34b2c&part=2of2
+        var indT= requrl.indexOf( taskStr, 0);
+        if( indT < 0 ) {
+          logger.addLog( clName+": "+methName+
+            ": Warn: No taskId found in the URL - New Task !\n",this.LogF);
+          this.calcTaskId();
+          logger.addLog( clName+": "+methName+
+            ": calculated New taskId:["+this.taskId+"]\n", this.LogF);
+          resp= '{\"respStr\":\"proc_start\",\"taskId\":\"'+this.taskId+'\"}';
+        }
+        else { //.. indT > -1 - have taskId in URL
+          var indTV= indT+ taskStr.length;
+          var indDL= requrl.indexOf( fDelim, indTV );
+          if( indDL < 0 ) {
+            logger.addLog( clName+": "+methName+
+              ": no more fields after taskId.\n",this.LogF);
+            this.taskId= requrl.substr( indTV );
+          }else{ //.. found delimiter
+            this.taskId= requrl.substring( indTV, indDL );
+          };
+          logger.addLog( clName+": "+methName+
+            ": extracted from URL: taskId:["+this.taskId+"]\n", this.LogF);
+          doAdd= "true"; //.. will add to old content
+          resp= '{\"respStr\":\"proc_upload\"}'; //.. no return of old taskId
+        }
+        var inputFilePfx = this.cdt_home +"/posted_data_";
+        var infname= inputFilePfx +this.taskId +".txt";
+          logger.addLog( clName+": "+methName+
+            ": got input file name:["+infname+"]\n",this.LogF);
+        //.. extracting parts info, e.g.: "...&part=2of2"
+        var rexp2p= new RegExp(/part=(\d+)of(\d+)/);
+          var match2p= rexp2p.exec( requrl );
+        if( match2p == null ) {
+          logger.addLog( clName+": "+methName+
+            ": Warn: No part number found in the URL "+
+            "-cant determine the last part !\n",this.LogF);
+        }
+        else { //.. match2p != null
+          logger.addLog( clName+": "+methName+
+              ": match2p:  length="+match2p.length+"\n",this.LogF);
+            for( var i1=0; i1 < match2p.length; i1++ ) {
+              logger.addLog( clName+": "+methName+
+                ": match2p:["+match2p[i1]+"]\n",this.LogF);
+            };
+            var partNum= match2p[ 1];
+            var partCnt= match2p[ 2];
+            logger.addLog( clName+": "+methName+
+              ": partNum:["+partNum+"] partCnt:["+partCnt+"]\n",this.LogF);
+            if( partNum == partCnt ) {
+              logger.addLog( clName+": "+methName+
+                ": this is the last part -need doProc\n",this.LogF);
+              doProc= "true";
+            };
+        };
+        //.. write data into the file and start processing right after the close
+        var errW= this.writeInp2File( infname, inpdata, doAdd, doProc );
+        if( errW.length > 0 ) {
+          resp= errW;
+        };
+      }
+      else { //.. no match2
+        logger.addLog( clName+": "+methName+": Warn: No URL match.\n",this.LogF);
+      }
+      logger.addLog( clName+": "+methName+": done: resp:["+resp+"]\n",this.LogF);
+    return( resp );
+};
+
+exports.getFsAvailSpace = function ( fspath ) {
+  var methName= "getFsAvailSpace";
+  //.. get available space (kB) in filesystem where the fspath is mounted
+   logger.addLog( clName+": "+methName+
+     ": start: fspath:\n["+fspath+"]\n",this.LogF);
+  var timeStampI = Date.now();
+    logger.addLog( clName+": "+methName+": timeStampI="+timeStampI+"\n",this.LogF);
+  var timeStamp = new Date( timeStampI ).toISOString();
+    logger.addLog( clName+": "+methName+": timeStamp:["+timeStamp+"]\n",this.LogF);
+  if( fspath.length < 1 ) {
+    var errMsg= methName+": Error: the filesystem directory path is empty !";
+    logger.addLog( clName+ errMsg+"\n", this.LogF);
+    return errMsg;
+  };
+ // var carg= "-k "+fspath;
+ // var mopts= { input: carg, timeout: 4000 };
+ //var moutObj= Chproc.spawnSync('df',[],mopts);
+  var c_args= new Array( 0 );
+    c_args.push( '-k' );
+    c_args.push( fspath );
+  var mopts= { timeout: 4000 };
+    logger.addLog( clName+": "+methName+
+      ": mopts:["+JSON.stringify(mopts)+"]\n",this.LogF);
+  //.. start
+  var moutObj= Chproc.spawnSync('df', c_args, mopts);
+    logger.addLog( clName+": "+methName+
+      ": df done: moutObj: status:["+moutObj.status+"](0 means Ok)\n",this.LogF);
+  var timeStampI = Date.now();
+    logger.addLog( clName+": "+methName+": timeStampI="+timeStampI+"\n",this.LogF);
+  if( moutObj.error != null ) {
+    var errMsg= clName+": "+methName+
+      ": spawn df: Error:["+JSON.stringify(moutObj.error)+"]";
+    logger.addLog( errMsg+"\n", this.LogF);
+    return errMsg;
+  };
+    logger.addLog( clName+": "+methName+
+      ": df moutObj.output: length="+moutObj.output.length+"\n",this.LogF);
+  if( moutObj.output.length > 0 ) {
+    for( var i0=0; i0 < moutObj.output.length; i0++ ) {
+        logger.addLog( clName+": "+methName+
+          ": moutObj.output["+i0+"]:["+moutObj.output[i0]+"]\n",this.LogF);
+    }; //.. loop
+    //.. output[1]: should contain 2 lines:
+    // [Filesystem     1K-blocks    Used Available Use% Mounted on\n
+    //  /dev/dm-0       37383720 8933604  26528068  26% /\n]
+    var out1= moutObj.output[1];
+    logger.addLog( clName+": "+methName+": to parse out1:["+out1+"]\n",this.LogF);
+    var rexp1= new RegExp(/\s+(\d+)\s+(\d+)\s+(\d+)\s+/);
+    logger.addLog( clName+": "+methName+": rexp1:["+rexp1+"]\n",this.LogF);
+    var matchArr= rexp1.exec( out1 );
+    if( matchArr == null ) {
+      var errMsg= clName+": "+methName+
+        ": spawn: Error: No digitals (bytes counts) found in the output !";
+      logger.addLog( errMsg+"\n", this.LogF);
+      return errMsg;
+    };
+    if( matchArr[3] == null || matchArr[3] == undefined ) {
+      var errMsg= clName+": "+methName+
+        ": parsed: Error: The 3-rd byte count is empty (no Available Kbytes) !";
+      logger.addLog( errMsg+"\n", this.LogF);
+      return errMsg;
+    };
+    fsAvSpaceKB= matchArr[3];
+    logger.addLog( clName+": "+methName+
+      ": extracted fsAvSpaceKB="+fsAvSpaceKB+"\n",this.LogF); 
+  }else{
+    var errMsg= clName+": "+methName+
+      ": spawn: Error: Empty df-command output array !";
+    logger.addLog( errMsg+"\n", this.LogF);
+    return errMsg;
+  }
+  logger.addLog( clName+": "+methName+": finished.\n",this.LogF);
+  return '';
+};
+
+exports.calcTaskId = function () {
+  var methName= "calcTaskId";
+  //.. calculate taskId
+    logger.addLog( clName+": "+methName+": start:\n",this.LogF);
+  var timeStampI = Date.now();
+    logger.addLog( clName+": "+methName+": timeStampI="+timeStampI+"\n",this.LogF);
+  var timeStamp = new Date( timeStampI ).toISOString();
+    logger.addLog( clName+": "+methName+": timeStamp:["+timeStamp+"]\n",this.LogF);
+
+  var rawId = String( this.remAddr+'-'+timeStampI );
+    logger.addLog( clName+": "+methName+": rawId:["+rawId+"]\n",this.LogF);
+
+  var mopts= { input: rawId, timeout: 4000 };
+    logger.addLog( clName+": "+methName+
+      ": mopts:["+JSON.stringify(mopts)+"]\n",this.LogF);
+
+  var moutObj= Chproc.spawnSync('md5sum',[],mopts);
+    logger.addLog( clName+": "+methName+
+      ": moutObj: status:["+moutObj.status+"]\n",this.LogF);
+  if( moutObj.error != null ) {
+    logger.addLog( clName+": "+methName+
+      ": moutObj calc.: Error:["+JSON.stringify(moutObj.error)+"]\n",this.LogF);
+    throw moutObj.error;
+  }
+  else { //.. no errors
+    logger.addLog( clName+": "+methName+
+      ": moutObj: output: length="+moutObj.output.length+"\n",this.LogF);
+    if( moutObj.output.length > 0 ) {
+      for( var i0=0; i0 < moutObj.output.length; i0++ ) {
+        logger.addLog( clName+": "+methName+
+          ": moutObj: output:["+moutObj.output[i0]+"]\n",this.LogF);
+      }
+    }
+    var out1= moutObj.output[ 1 ];
+    logger.addLog( clName+": "+methName+": out1:["+out1+"]\n",this.LogF);
+    var rexp1= new RegExp(/\w+/); //.. alphanumeric
+    logger.addLog( clName+": "+methName+": rexp1:["+rexp1+"]\n",this.LogF);
+    var match1= rexp1.exec( out1 );
+    if( match1 == null ) {
+      logger.addLog( clName+": "+methName+
+        ": Warn.: No Match of rexp1 in the cmd output (No taskId) !\n",this.LogF);
+    }
+    else {
+      logger.addLog( clName+": "+methName+
+        ": match1:["+match1[0]+"] length="+match1.length+"\n",this.LogF);
+      this.taskId= match1[0];
+    };
+  };
+  logger.addLog( clName+": "+methName+
+    ": done: calculated taskId:["+this.taskId+"]\n",this.LogF);
+};
+
+exports.writeInp2File = function ( fname,  inpd, doAdd, doProc ) {
+  var methName= "writeInp2File";
+  //.. write input data to the file
+    logger.addLog( clName+": "+methName+
+      ": Start: file:["+fname+"]\n",this.LogF);
+  let timeStamp = new Date().toISOString();
+  logger.addLog( clName+": "+methName+": timeStamp:["+timeStamp+"]\n",this.LogF);
+  var inpdLen= inpd.length;
+    logger.addLog( clName+": "+methName+": inpd length="+ inpdLen+"\n",this.LogF);
+  if( inpdLen < 1 ) {
+    logger.addLog( clName+": "+methName+
+      ": Warn.: Empty input - nothing to do !\n",this.LogF);
+    return ''; //.. not an error
+  };
+  if( inpdLen > 300 ) {
+    logger.addLog( clName+": "+methName+
+      ": inpd(begin):["+ inpd.substr(0,299)+"...]\n",this.LogF);
+  }
+  else {
+    logger.addLog( clName+": "+methName+": inpd:["+ inpd+"]\n",this.LogF);
+  };
+  logger.addLog( clName+": "+methName+
+    ": doAdd:["+doAdd+"] doProc:["+doProc+"]\n",this.LogF);
+  if( ! doAdd ) {
+    logger.addLog( clName+": "+methName+
+      ": Dont doAdd - need new content.\n",this.LogF);
+    //.. removing old file if is there with the same name (async is Not OK !)
+    var fname_o= fname+".save";
+    try {
+      fs.renameSync( fname, fname_o );
+    }
+    catch( err ) {
+      var errMsg= clName+": "+methName+
+        ": Warn.: Failed to rename old file:["+fname+"]"+
+        " err.code="+err.code+"\n";
+      logger.addLog( errMsg, this.LogF );
+    };
+  };
+  logger.addLog( clName+": "+methName+": opening file:["+fname+"]\n",this.LogF);
+  try {
+    this.fdI= fs.openSync( fname, 'a' );
+  }
+  catch( err ) {
+    var errMsg= clName+": "+methName+
+      ": Error: Failed to open file:["+fname+"]"+
+      " err.code="+err.code+"\n";
+    logger.addLog( errMsg, this.LogF );
+    return( errMsg );
+  };
+  logger.addLog( clName+": "+methName+
+    ": writing data into the file: fdI="+ this.fdI+"\n",this.LogF);
+  try {
+    fs.appendFileSync( this.fdI, inpd, 'utf8' );
+  }
+  catch( err ) {
+    var errMsg= clName+": "+methName+
+      ": Error: Failed to append input data to file: err.code="+err.code+"\n";
+    logger.addLog( errMsg, this.LogF );
+    return( errMsg );
+  };
+  logger.addLog( clName+": "+methName+
+    ": closing input data file...\n",this.LogF);
+  try {
+    fs.closeSync( this.fdI );
+  }
+  catch( err ) {
+    var errMsg= clName+": "+methName+
+      ": Error: Failed to close input data file: err.code="+err.code+"\n";
+    logger.addLog( errMsg, this.LogF );
+    return( errMsg );
+  };
+  logger.addLog( clName+": "+methName+
+    ": input file was written and closed successfully.\n",this.LogF);
+  timeStamp = new Date().toISOString();
+  logger.addLog( clName+": "+methName+": timeStamp:["+timeStamp+"]\n",this.LogF);
+  if( doProc ) {
+    logger.addLog( clName+": "+methName+
+      ": starting processing...\n",this.LogF);
+    this.startProcFile();
+  };
+  return ''; //.. no error
+};
+
+exports.startProcFile = function () {
+  var methName= "startProcFile";
+  //.. start processing of the input file in external sub-process
+  logger.addLog( clName+": "+methName+
+    ": Start: taskId:["+this.taskId+"]\n",this.LogF);
+    let timeStamp = new Date().toISOString();
+    logger.addLog( clName+": "+methName+
+      ": timeStamp:["+timeStamp+"]\n",this.LogF);
+  console.log(methName+": cdt_home:["+this.cdt_home+"]");
+  var SubProcScriptP= this.cdt_home + SubProcScript;
+    logger.addLog( clName+": "+methName+
+      ": run SubProcScript:\n["+SubProcScriptP+"]\n",this.LogF);
+  var cp_args= new Array( 0 );
+    cp_args.push( this.taskId ); //.. give it taskId in args
+    cp_args.push( this.cdt_home );
+    cp_args.push( this.LOG_DIR ); 
+  var chprocI= Chproc.fork( SubProcScriptP, cp_args );
+
+ // chprocI.on('message', (msg) => {
+ //   console.log(methName+": chprocI got msgJ:["+JSON.stringify(msg)+"]");
+ // });
+ // console.log("startProcFile: sending a message to child.");
+ // chprocI.send({ hello: 'world' });
+  logger.addLog( clName+": "+methName+" done submit.\n",this.LogF);
+};
diff --git a/src/app/srvlogger.js b/src/app/srvlogger.js
new file mode 100755 (executable)
index 0000000..22c6d83
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+============LICENSE_START==========================================
+===================================================================
+Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+===================================================================
+
+Unless otherwise specified, all software contained herein is licensed
+under the Apache License, Version 2.0 (the License);
+you may not use this software except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+============LICENSE_END============================================ */
+//.. srvlogger.js -logging in file system
+const fs = require('fs');
+
+const clName= "srvlogger";
+
+var MaxLogSize= 5000000;
+var LogFileNm;
+var fdL;
+
+exports.addLog = function( logStr, fileNm ) {
+
+  this.LogFileNm= fileNm
+  this.fdL= fs.openSync( this.LogFileNm, 'a' );
+ // console.log(clName+": log opened. fdL="+this.fdL);
+
+  var lfStats= fs.fstatSync( this.fdL );
+  if( lfStats.size + logStr.length >= MaxLogSize )
+  {
+    this.changeLogFile();
+  };
+
+  try {
+    fs.appendFileSync( this.fdL, logStr, 'utf8' );
+  }
+  catch( err ) {
+    console.log(clName+": log append: error:"+err.message );
+    throw err;
+  };
+  fs.closeSync( this.fdL );
+}
+
+exports.changeLogFile = function() {
+
+  var msgO= "\n=== The Log reached max size. Changing the file. ===\n";
+  try {
+    fs.appendFileSync( this.fdL, msgO, 'utf8' );
+  }
+  catch( err ) {
+    console.log(clName+": log append: error:"+err.message );
+    throw err;
+  };
+  fs.closeSync( this.fdL );
+
+  var LogFileNm_o= this.LogFileNm +".old";
+  try {
+    fs.renameSync( this.LogFileNm, LogFileNm_o );
+  }
+  catch( err ) {
+    throw err;
+  };
+
+  try {
+    this.fdL= fs.openSync( this.LogFileNm, 'a' );
+  }
+  catch( err ) {
+    console.log(clName+": New Log file open: error:["+err.message+"]\n");
+    throw err;
+  }
+  console.log( clName+": New Log file opened: fdL="+this.fdL+"\n");
+
+  var msgN= "\n=== New Log file ===\n";
+  try {
+    fs.appendFileSync( this.fdL, msgN, 'utf8' );
+  }
+  catch( err ) {
+    console.log( clName+": new log append: error:"+err.message );
+    throw err;
+  };
+}
+
+exports.setMaxLogSize = function( newMaxLogSize ) {
+  if( newMaxLogSize > 0 )
+    MaxLogSize= newMaxLogSize;
+  else
+    console.log(clName+": Wrong arg: newMaxLogSize <= 0 !");
+}
diff --git a/src/app/subnproc.js b/src/app/subnproc.js
new file mode 100644 (file)
index 0000000..6719366
--- /dev/null
@@ -0,0 +1,840 @@
+/*
+============LICENSE_START==========================================
+===================================================================
+Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+===================================================================
+
+Unless otherwise specified, all software contained herein is licensed
+under the Apache License, Version 2.0 (the License);
+you may not use this software except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+============LICENSE_END============================================ */
+//.. subnproc.js -to be started by parent server process
+const fs = require('fs');
+const Chproc = require('child_process');
+
+var clName= "subnproc";
+var thePid= 0;
+var taskId= '';
+//.. assuming all these files are in CDT_HOME
+var inpFilePfx= "posted_data_";
+var outFilePfx= "sync_template_res_"; //.. processing results
+var errFilePfx= "sync_template_err_"; //.. error found while processing
+var parmFilePfx= "template_params_";
+var pstatfNamePfx= "proc_status_";
+//.. pDlm -delimiter of params
+//   (cant be: alphanumeric,underbar,dollar,dot,comma,brackets,dash,equal sign,etc)
+var pDlm= '\n';
+var logfname= clName+".log";
+ var logfname_o= logfname+".save"; //.. to keep old content
+var fdL;
+var fdIn;
+var fdOut;
+var errMsgBadParm= ''; //.. create error message to report to client
+//var checkSpecialCharsReg= new RegExp(/[^\w\s-]/);
+var checkSpecialCharsReg= new RegExp(/[^\w\s-\.]/);
+var doStopOnSpecChars= 'Y';
+var doCheckComments= 'Y';
+//var SYNC_T_KEY_EXPRES= '\\${.+?\\}';
+var VSTART= '${';
+var VEND= '}';
+var lenVS= VSTART.length, lenVE= VEND.length;
+var KEY_START= '${(';
+var KEY_MID= ')=(';
+var KEY_END= ')}';
+var LD= "\n"; //.. LD -line delimeter in the input file
+var lenLD= LD.length;
+var percAtRngs= 55; //.. percentage defining report to client
+
+var traceLvl= 1;
+//var CCnt= 52;
+var CCnt= 2;
+
+  var taskIdArgstr= process.argv[ 2 ];
+  var CDT_HOME= process.argv[ 3 ];
+  var LOG_DIR= process.argv[ 4 ];
+  this.thePid= process.pid;
+  console.log(clName+": Start: thePid="+this.thePid );
+  console.log(clName+": CDT_HOME:["+CDT_HOME+"]" );
+  console.log(clName+":  LOG_DIR:["+LOG_DIR+"]" );
+
+try {
+  //.. need renameSync (not asynchronous) !
+  fs.renameSync( LOG_DIR+"/"+logfname, LOG_DIR+"/"+logfname_o);
+}
+catch( err ) {
+    console.log(clName+": log rename error: code="+ err.code );
+    console.log(clName+": log rename error.msg:["+err.message+"]");
+    //throw err;
+};
+fs.open( LOG_DIR+"/"+logfname, 'a', (err, fd) => {
+  if( err ) {
+    console.log(clName+": log file open error:"+err.message );
+    throw err;
+  }
+  this.fdL= fd;
+  console.log(clName+": log opened. fdL="+this.fdL);
+
+  this.logWr( clName+": Start: thePid="+this.thePid+"\n");
+
+  this.logWr( clName+
+    ": execArgv:["+process.execArgv+"]\n argv:["+process.argv+"]\n");
+  var indT= taskIdArgstr.indexOf('=', 0);
+  if( indT < 0 ) {
+   // this.logWr(clName+": Error: Failed to extract taskId from args:["+
+   //   taskIdArgstr+"]\n");
+   // this.taskId= "000";
+    this.taskId= taskIdArgstr;
+  } else {
+    this.taskId= taskIdArgstr.substr( indT+1 );
+  }
+  this.logWr(clName+": taskId:["+this.taskId+"]\n");
+  if(  this.taskId == null || this.taskId == undefined || this.taskId.length < 1 )
+  {
+    var errMsg= clName+": Error: taskId is empty !\n";
+    console.log( errMsg );
+    this.logWr( errMsg );
+    throw new Error( errMsg );
+  };
+  this.logWr(clName+": CDT_HOME:["+CDT_HOME+"]\n");
+
+  var timeStampI = Date.now();
+  this.logWr(clName+": timeStampI="+timeStampI+" \n");
+  var timeStamp = new Date( timeStampI ).toISOString();
+  this.logWr(clName+": timeStamp:["+timeStamp+"]\n");
+
+ //.. setup callback waiting for message from parent
+ // process.on('message', (msg) => {
+ //   console.log('subnp: got message:', msg);
+ // });
+
+  //this.logWr("subnp: sending msg to parent.\n");
+ // var msg2parent = { taskid: taskId };
+ // this.logWr("subnp: msg2parent:["+JSON.stringify(msg2parent)+"]\n");
+ // process.send( msg2parent );
+
+  var inpFile= CDT_HOME+"/"+ inpFilePfx +this.taskId +".txt";
+  this.logWr(clName+": opening inpFile for reading:["+inpFile+"]\n");
+
+  fs.open( inpFile, 'r', (err, fd0) => {
+    if( err ) {
+      console.log(clName+": inpFile file open: Error: code="+err.code );
+      this.logWr(clName+": inpFile file open: Error: code="+err.code+"\n");
+      throw err;
+    }
+    this.fdIn= fd0;
+    console.log(clName+": Input file opened. fdIn="+this.fdIn);
+
+    fs.readFile( this.fdIn, (err, data) => {
+      if( err ) {
+        console.log(clName+": Failed to read inpFile: Error: code="+err.code );
+        this.logWr(clName+": Failed to read inpFile: Error: code="+err.code+"\n");
+        throw err;
+      }
+      this.processData( data );
+      //.. close log
+      console.log(clName+": close: fdL="+this.fdL );
+      fs.closeSync( this.fdL );
+      this.fdL= -1;
+    });
+  });
+});
+
+exports.processData = function ( data ) {
+  //.. processing data content from already opened input file
+  var methName= "processData";
+  this.logWr(methName+": start...\n");
+  var timeStamp = new Date().toISOString();
+  this.logWr( methName+": timeStamp:["+timeStamp+"]\n");
+  var fcontStr= new String( data );
+  let fcontLen= fcontStr.length;
+  this.logWr(methName+": fcontLen="+fcontLen+"\n");
+
+  this.logWr(methName+": trying to close inpFile: fdIn="+this.fdIn+"\n");
+  fs.closeSync( this.fdIn );
+  this.logWr(methName+": inpFile closed.\n");
+
+  var pstatfName= CDT_HOME+"/"+ pstatfNamePfx +this.taskId+".json";
+  var percent= 1;
+  var stmsg= '{\"percentage\":'+percent+'}';
+  this.updateProcStatus( pstatfName, stmsg );
+
+  timeStamp = new Date().toISOString();
+  this.logWr( methName+": timeStamp:["+timeStamp+"]\n");
+
+  var perrorfName= CDT_HOME+"/"+ errFilePfx+ this.taskId+".txt";
+  var formatErr= '';
+  var lineEnds= new Array( 0 ); //.. indexes of new-line chars
+  var selecRanges= new Array( 0 ); //.. indexes of ${variables}
+  var parmNamVals= new Map(); //.. parameter Name-Value pairs from inside ${...}
+  var ind0= 0;
+  var ind1= fcontStr.indexOf( LD,ind0 ); //.. line delimeter
+  var rn= 0, li= 0;
+  while( ind1 > -1 ) {
+    lineEnds.push( ind1 );
+    this.logWr(
+      methName+":--line #:"+li+" beg: ind0="+ind0+" end: ind1="+ind1+
+      " array:"+lineEnds[li]+"\n");
+    var line0= fcontStr.substring( ind0, ind1 ); //.. not including LD
+    var liLen= line0.length;
+    this.logWr( methName+":  liLen="+liLen+"\n");
+    if( liLen > 60 ) {
+      this.logWr( methName+": line(p):["+line0.substr(0,60)+"]\n");
+    } else {
+      this.logWr( methName+": line:["+line0+"]\n");
+    }
+    //.. look for ${param variables}
+    rn= this.getRangesForVars( line0, li, rn, selecRanges );
+    if( rn < 0 ) {
+      this.logWr( methName+": got Error:\n["+errMsgBadParm+"]");
+      this.reportParamBadFmt( li, '', errMsgBadParm, perrorfName, pstatfName );
+      this.logWr( methName+": Stopping processing !\n");
+      return;
+    }
+    if( traceLvl > 1 )
+      this.logWr( methName+": added ranges at li="+li+": next rn="+rn+"\n");
+    ind0= ind1 +lenLD; //.. for next search of LD
+    if( ind0 >= fcontLen ) { break; };
+    li++;
+    ind1= fcontStr.indexOf( LD,ind0 ); //.. line delimeter
+  }; //.. loop
+  //.. process the last line
+  if( ind0 < fcontLen ) {
+    this.logWr( methName+": have the last line: ind0="+ind0+"\n");
+    lineEnds.push( fcontLen );
+    var line0= fcontStr.substring( ind0 ); //.. up to the end
+    var liLen= line0.length;
+    this.logWr( methName+":  liLen="+liLen+"\n");
+    if( liLen > 60 ) {
+      this.logWr( methName+": line(p):["+line0.substr(0,60)+"]\n");
+    } else {
+      this.logWr( methName+": line:["+line0+"]\n");
+    }
+    //.. look for ${param variables}
+    rn= this.getRangesForVars( line0, li, rn, selecRanges );
+    if( rn < 0 ) {
+      this.logWr( methName+": got Error in getRangesForVars:\n["+
+        errMsgBadParm+"]");
+      this.reportParamBadFmt( li, '', errMsgBadParm, perrorfName, pstatfName );
+      this.logWr( methName+": Stopping processing !\n");
+      return;
+    }
+  };
+  this.logWr( methName+":  -=- lineEnds array length="+lineEnds.length+"\n");
+  timeStamp = new Date().toISOString();
+  this.logWr( methName+": timeStamp:["+timeStamp+"]\n");
+
+  percent= percAtRngs;
+  stmsg= '{\"percentage\":'+percent+'}';
+  this.updateProcStatus( pstatfName, stmsg );
+
+  //.. Cycle through Ranges
+  this.logWr( methName+": selecRanges array length="+selecRanges.length+"\n");
+  if( doCheckComments ) {
+      this.logWr( methName+": doCheckComments is True.\n");
+  } else {
+      this.logWr( methName+": doCheckComments is False\n");
+  };
+  var rnAtUpd= Math.floor( selecRanges.length/ 3.);
+  var percStep= (100.0 - percAtRngs);
+  this.logWr( methName+": range number for proc.status update="+rnAtUpd+
+    "\n percentage step="+percStep );
+  var outLinBuf= new Array( 0 ); //.. array of output lines
+  var outLnLast=-1; //.. last processed
+  var rngLnLast=-1;
+  for( var rn0=0; rn0 < selecRanges.length; rn0++ ) {
+    var rng= selecRanges[rn0];
+    this.logWr( methName+":--- range #:"+rn0+" lineNm="+rng.lineNm+
+      " colBg="+rng.colBg+" colEn="+rng.colEn+"\n");
+    this.logWr( methName+": outLnLast="+outLnLast+"\n");
+    if( rn0 > 0 && (rn0 % rnAtUpd) == 0 && rn0 < selecRanges.length -rnAtUpd )
+    {
+      percent= Math.floor( percAtRngs + percStep *(rn0/selecRanges.length) );
+      this.logWr( methName+": need to update proc.status: percent="+percent+"\n");
+      stmsg= '{\"percentage\":'+percent+'}';
+      this.updateProcStatus( pstatfName, stmsg );
+    };
+    rngLnLast= rng.lineNm;
+    //.. prepare previous lines with no ranges
+    if( rng.lineNm - outLnLast > 1 ) {
+      this.logWr( methName+": prepare previous lines...\n");
+      var plinNm0= outLnLast +1;
+      var plinesIndB= 0;
+       if( plinNm0 > 0 ) { plinesIndB= lineEnds[outLnLast] + lenLD; };
+      var plinesIndE= lineEnds[rng.lineNm -1] + lenLD;
+      this.logWr( methName+": plinesIndB="+plinesIndB+
+        " plinesIndE="+plinesIndE+"\n");
+      var plinStr= fcontStr.substring( plinesIndB, plinesIndE );
+      if( traceLvl > 1 ) {
+        if( plinStr.length > 60 ) {
+          this.logWr( methName+": plinStr(p):["+plinStr.substr(0, 60)+"]\n");
+        } else {
+          this.logWr( methName+": plinStr:["+plinStr+"]\n");
+        }
+      };
+      outLinBuf.push( plinStr );
+      outLnLast= rng.lineNm -1;
+    };
+    var linIndB= 0;
+    var linIndE= lineEnds[rng.lineNm];
+    if( rng.lineNm > 0 ) { linIndB= lineEnds[rng.lineNm - 1] + lenLD; };
+    var linLen= linIndE - linIndB;
+    var line0= fcontStr.substr( linIndB, linLen );
+    this.logWr( methName+": linIndB="+linIndB+"  linIndE="+linIndE+
+      " line length="+linLen+"\n");
+    if( traceLvl > 1 ) {
+      if( linLen > 60 ) {
+        this.logWr( methName+": line0(p):\n["+line0.substr(0,60)+"]\n");
+      } else {
+        this.logWr( methName+": line0:\n["+line0+"]\n");
+      }
+    };
+    //.. content between ${ and }
+    var vword= line0.substring( rng.colBg+lenVS, rng.colEn+1-lenVE );
+    this.logWr( methName+":  vword:["+vword+"]\n");
+    //.. try to check for (pvalue)=(pname)
+    let specialKeys= null;
+    var parmNamAndVal= Array( 0 );
+    var pName= null;
+    var pValue= null;
+    formatErr= this.parseParmValAndName( vword, parmNamAndVal )
+    if( formatErr ) {
+      this.reportParamBadFmt(
+        rng.lineNm, vword, formatErr, perrorfName, pstatfName );
+      this.logWr( methName+": Stopping processing !\n");
+      return;
+    }
+    //.. No errors - parsing done OK
+      pName= parmNamAndVal[0]; //.. to be added to parmNamVals
+      pValue= parmNamAndVal[1];
+      this.logWr( methName+": parsing done: param.name:["+
+        pName+"] value:["+pValue+"]\n");
+
+    if( pName != null && pName.length > 0 ) {
+      this.logWr( methName+": check spec.chars in Non empty pName.\n");
+      specialKeys= checkSpecialCharsReg.exec( pName );
+      if( specialKeys == null ) {
+        this.logWr( methName+":  specialKeys obj is null\n");
+      }
+      else
+      if( specialKeys.length > 0 ) {
+        this.logWr( methName+":  specialKeys obj Not null:["+specialKeys+
+          "] length="+specialKeys.length+"\n");
+        if( doStopOnSpecChars ) {
+          formatErr=
+            "The parameter name("+pName+") contains prohibited character(s):("+
+            specialKeys+") !";
+          this.logWr( methName+": formatErr:["+formatErr+"]\n");
+          this.reportParamBadFmt(
+            rng.lineNm, vword, formatErr, perrorfName, pstatfName );
+          this.logWr( methName+": Stopping processing !\n");
+          return;
+        };
+      };
+    };
+    var haveComment= '';
+    if( doCheckComments ) {
+      this.logWr( methName+": doCheckComments is True.\n");
+      if( line0.trim().startsWith("//") ) haveComment= 'Y';
+    };
+    if( haveComment ) {
+      this.logWr( methName+":   haveComment is True !\n");
+    };
+    //.. prepare parameter name and value
+    if( pName != null && pName.length > 0 && !specialKeys && !haveComment )
+    {
+      this.logWr( methName+": checking non-empty param.name:["+pName+"]\n");
+      if( parmNamVals.has(pName) ) {
+        this.logWr( methName+": the param. is already accounted.\n");
+      } else {
+        this.logWr( methName+": adding new param. with value:["+pValue+"]\n");
+        parmNamVals.set( pName, pValue );
+      };
+    };
+    //.. prepare for template output
+    var colB= 0;
+    if( rn0 > 0 ) {
+      var rngP= selecRanges[rn0 - 1];
+      if( rngP.lineNm == rng.lineNm ) { colB= rngP.colEn+lenVE; };
+    };
+    var colE= rng.colEn;
+    //.. check range content for replacement
+    let checkApplForNamOn=
+      this.checkAppliedForNamesOnly( line0, rng.colBg, rng.colEn );
+    this.logWr( methName+": checkApplForNamOn:["+checkApplForNamOn+"]\n");
+    if( checkApplForNamOn && !specialKeys && !haveComment )
+    {
+      this.logWr( methName+": Need Name Replacement... \n");
+      var replacWrd= KEY_START +KEY_MID +vword +KEY_END; // ${()=(...)}
+      this.logWr( methName+": replacWrd:["+replacWrd+"]\n");
+      var colM= rng.colBg; //.. not including beginning of VSTART
+      var brngStr= line0.substring( colB, colM );
+      this.logWr( methName+": part before word:["+brngStr+"]\n");
+      outLinBuf.push( brngStr + replacWrd );
+    }
+    else {
+      this.logWr( methName+": No Replacement for this range !\n");
+      var rsubs= line0.substring( colB, colE+lenVE );
+      this.logWr( methName+": rsubs:["+rsubs+"]\n");
+      outLinBuf.push( rsubs );
+    };
+    outLnLast= rng.lineNm;
+    //.. check tail of the line
+    if( rn0 == selecRanges.length - 1 ) {
+      var tailStr= line0.substring(colE+lenVE) + LD;
+      this.logWr( methName+": last range: adding line tail:\n["+tailStr+"]\n");
+      outLinBuf.push( tailStr );
+    }
+    else { //.. not the last range
+      var rngN= selecRanges[rn0 + 1];
+      if( rngN.lineNm == rng.lineNm ) {
+        //.. next range on the same line - no tail yet
+      }
+      else { //.. next range on different line
+        var tailStr= line0.substring(colE+lenVE) + LD;
+        this.logWr( methName+": adding line tail:\n["+tailStr+"]\n");
+        outLinBuf.push( tailStr );
+      };
+    };
+  }; //... loop
+  if( rngLnLast < lineEnds.length - 1) {
+    this.logWr( methName+": adding last lines having no ranges...\n");
+      var llinesIndB= 0;
+       if( rngLnLast > 0 ) { llinesIndB= lineEnds[rngLnLast] + lenLD; };
+      var llinesIndE= lineEnds[lineEnds.length -1] + lenLD;
+      this.logWr( methName+": llinesIndB="+llinesIndB+
+        " llinesIndE="+llinesIndE+"\n");
+    var llinStr= fcontStr.substring( llinesIndB, llinesIndE );
+    if( traceLvl > 1 ) {
+      if( llinStr.length > 60 ) {
+        this.logWr( methName+": llinStr(p):\n["+llinStr.substr(0,60)+"]\n");
+      } else {
+        this.logWr( methName+": llinStr:["+llinStr+"]\n");
+      }
+    }
+    outLinBuf.push( llinStr );
+  };
+  this.logWr( methName+":  -=- outLinBuf: array length="+outLinBuf.length+"\n");
+  timeStamp = new Date().toISOString();
+  this.logWr( methName+": timeStamp:["+timeStamp+"]\n");
+
+  var outFile= CDT_HOME+"/"+ outFilePfx+ this.taskId+".txt";
+  var outFile_o= outFile+".save";
+  try {
+    fs.renameSync( outFile, outFile_o );
+  }
+  catch( err ) {
+    this.logWr(clName+": old output file rename: error:["+err.message+"]\n");
+   // throw err;
+  };
+  try {
+    this.fdOut= fs.openSync( outFile, 'a' );
+  }
+  catch( err ) {
+    this.logWr(clName+": output file open: error:["+err.message+"]\n");
+    throw err;
+  }
+  this.logWr(methName+": output file opened: fdOut="+this.fdOut+"\n");
+  for( let outLine of outLinBuf ) {
+    try {
+      fs.appendFileSync( this.fdOut, outLine, 'utf8' );
+    }
+    catch( err ) {
+      this.logWr(clName+": output file append: error: code="+err.code );
+      throw err;
+    }
+  }; //... loop
+  this.logWr(methName+": closing output file: fdOut="+this.fdOut+"\n");
+  fs.closeSync( this.fdOut );
+  this.logWr(methName+": output file closed.\n");
+  timeStamp = new Date().toISOString();
+  this.logWr( methName+": timeStamp:["+timeStamp+"]\n");
+
+  this.logWr( methName+":  -=- parmNamVals (Parameters): map.size="+
+    parmNamVals.size+"\n");
+  var parmFile= CDT_HOME+"/"+ parmFilePfx+ this.taskId+".txt";
+  var parmFile_o= parmFile+".save";
+  try {
+    fs.renameSync( parmFile, parmFile_o );
+  }
+  catch( err ) {
+    this.logWr(clName+": old Params file rename: error:["+err.message+"]\n");
+   // throw err;
+  };
+  try {
+    this.fdParm= fs.openSync( parmFile, 'a' );
+  }
+  catch( err ) {
+    this.logWr(clName+": Params file open: error:["+err.message+"]\n");
+    throw err;
+  };
+  this.logWr(methName+": Params file opened: fdParm="+this.fdParm+"\n");
+  //.. writing parameters into parameter-output file
+  try {
+   // fs.appendFileSync( this.fdParm, "[\n", 'utf8' );
+    var iw= 0;
+    for( let pnm of parmNamVals.keys() ) {
+      let pv= parmNamVals.get( pnm );
+      let pnv= pnm+"="+pv;
+      this.logWr( methName+": iw="+iw+" param.Name-Value:["+pnv+"]\n");
+        //.. need delimiter after each item !
+        fs.appendFileSync( this.fdParm, pnv+pDlm, 'utf8' );
+      iw++;
+    }; //... loop
+   // fs.appendFileSync( this.fdParm, "]", 'utf8' );
+  }
+  catch( err ) {
+    this.logWr(clName+": Params file append: error: code="+err.code );
+    throw err;
+  }
+  this.logWr(methName+": closing Params file: fdParm="+this.fdParm+"\n");
+  fs.closeSync( this.fdParm );
+  this.logWr(methName+": Params file closed.\n");
+
+  timeStamp = new Date().toISOString();
+  this.logWr( methName+": timeStamp:["+timeStamp+"]\n");
+
+  percent= 100;
+  stmsg= '{\"percentage\":'+percent+'}';
+  this.updateProcStatus( pstatfName, stmsg );
+
+  //.. simulated updates
+/*
+  for( var i1=0; i1 < this.CCnt; i1++ ) {
+    this.wait( 2 );
+    percent += 2;
+    if( percent > 100 ) { percent= 100; };
+    stmsg= '{\"percentage\":'+percent+'}';
+    this.updateProcStatus( pstatfName, stmsg );
+  };
+
+  this.wait( 1 );
+*/
+  this.logWr(methName+": finishing.\n");
+  timeStamp = new Date().toISOString();
+  this.logWr( methName+": timeStamp:["+timeStamp+"]\n");
+};
+
+exports.checkAppliedForNamesOnly = function( line, colBg, colEn )
+{
+  var methName= "checkAppliedForNamesOnly";
+  this.logWr( methName+": start: colBg="+colBg+" colEn="+colEn+"\n");
+  if( traceLvl > 1 ) {
+    if( line.length > 60 ) {
+      this.logWr( methName+": line(p):\n["+line.substr(0,60)+"]\n");
+    } else {
+      this.logWr( methName+": line:\n["+line+"]\n");
+    }
+  };
+  this.logWr( methName+": check for: KEY_START:["+KEY_START+"]\n");
+  var lenKS= KEY_START.length;
+  var indMax= colBg + lenKS
+  var subline= line.substring( 0, indMax );
+  var indKS= subline.lastIndexOf( KEY_START, subline.length ); //.. ${(
+  if( indKS < 0 ) {
+    if( traceLvl > 1 )
+      this.logWr( methName+": No KEY_START at all - return true.\n");
+    return true; //.. no KEY_START at all
+  }
+  else { //.. have KEY_START, check KEY_END
+    this.logWr( methName+": check for: KEY_END:["+KEY_END+"]\n");
+    indMax= colBg + KEY_END.length;
+    subline= line.substring( indKS +lenKS, indMax );
+    var indKE= subline.indexOf( KEY_END, 0 ); //.. )}
+    if( indKE < 0 ) {
+      if( traceLvl > 1 )
+        this.logWr( methName+": Only KEY_START there - return false.\n");
+      return false; //..only KEY_START there
+    }
+    else {
+      if( traceLvl > 1 )
+        this.logWr( methName+": Have both - return true.\n");
+      return true; //.. have both: KEY_START and then KEY_END
+    }
+  }
+}
+
+exports.getRangesForVars = function( line, lnum, rnum, selRangesArr )
+{
+  var methName= "getRangesForVars";
+  this.logWr( methName+": start: lnum="+lnum+"  rnum="+rnum+"\n");
+  if( traceLvl > 0 ) {
+    if( line.length > 60 ) {
+      this.logWr( methName+": line(p):["+line.substr(0,60)+"]\n");
+    } else {
+      this.logWr( methName+": line:["+line+"]\n");
+    }
+  };
+  var haveComment= '';
+  if( line.trim().startsWith("//") ) haveComment= 'Y';
+  //.. look for ${variables}
+  var indV0= 0;
+  var indV1= line.indexOf( VSTART, indV0 );
+  while( indV1 > -1 ) {
+      var indB= indV1;
+      indV0= indB + 1;
+      indV1= line.indexOf( VEND, indV0 );
+      if( indV1 < 0 ) {
+        errMsgBadParm=
+          methName+": Parameter ending delimiter ("+VEND+
+          ") not found in line #="+lnum+" after pos.#="+indV0+" !\n"
+        this.logWr( "Error: "+errMsgBadParm);
+        if( ! haveComment ) {
+          return -1;
+        } else { //.. haveComment - do not stop processing
+          this.logWr( methName+": This line is commented out -ignore the error.");
+          break;
+        };
+      };
+      var indE= indV1 + lenVE-1;
+      //.. check for VSTART inside the range (must find either old or new one)
+      indB= line.lastIndexOf( VSTART, indE );
+      this.logWr( methName+": found a ${param} at:"+indB+" - "+indE+"\n");
+      //.. lineNm - line number in the input file
+      //.. colBg - begin index of VSTART
+      //.. colEn - after end index of VEND (both within the line)
+      var range = { lineNm: lnum, colBg: indB, colEn: indE };
+      selRangesArr.push( range );
+      this.logWr( methName+": range #:"+rnum+
+        " param.string:["+JSON.stringify(selRangesArr[rnum])+"]\n");
+      rnum++;
+      indV0= indV1 + lenVE;
+      indV1= line.indexOf( VSTART, indV0 );
+  };
+  return rnum;
+};
+
+//.. assuming the word format is like: "(value)=(name)" or "name"
+//   returning error message or empty string (which means no errors)
+exports.parseParmValAndName = function( word, parmNamAndVal )
+{
+  var methName= "parseParmValAndName";
+  this.logWr( methName+": start: input word:["+word+"]\n");
+  var lenW= word.length;
+  var errorMsg= '';
+  if( traceLvl > 0 )
+    this.logWr( methName+": word length="+lenW+"\n");
+  if( lenW < 1 ) {
+    errorMsg= methName+": the parameter string is empty !\n";
+    errMsgBadParm= errorMsg;
+      this.logWr( "Error: "+errorMsg );
+    return errorMsg;
+  };
+  var lastCh= lenW - 1;
+  var OBR= '(';
+  var lenOBR= OBR.length;
+  var CBR= ')';
+  var lenCBR= CBR.length;
+  var EQS= '=';
+  var lenEQS= EQS.length;
+  var name, val;
+  var ic0= 0;
+  var ic1= word.indexOf( OBR, ic0 );
+  if( ic1 < 0 ) {
+    if( traceLvl > 0 )
+      this.logWr( methName+": no open round brackets -assuming the word has "+
+        "param.name only.\n");
+    parmNamAndVal.push( word );
+    parmNamAndVal.push( '' );
+    return ''; //.. empty errorMsg means OK
+  };
+  //.. got 1-st open round bracket
+  if( traceLvl > 0 )
+    this.logWr( methName+": found 1-st open round bracket at:"+ic1+"\n");
+  if( ic1 > 0 ) {
+    errorMsg= methName+": Unexpected chars before open round bracket at:"+ic1+"\n";
+    errMsgBadParm= errorMsg;
+      this.logWr( "Error: "+errorMsg );
+    return errorMsg;
+  };
+
+  ic0= ic1 + lenOBR;
+  if( ic0 >= lastCh ) {
+    errorMsg=
+      methName+": missing next expected round brackets after pos.#="+ic1+
+        " -bad format\n";
+    errMsgBadParm= errorMsg;
+      this.logWr( "Error: "+errorMsg );
+    return errorMsg;
+  };
+  var ic2= word.indexOf( CBR, ic0 );
+  if( ic2 < 0 ) {
+    errorMsg=
+      methName+": no closing round bracket after 1-st open bracket -bad format\n";
+    errMsgBadParm= errorMsg;
+      this.logWr( "Error: "+errorMsg );
+    return errorMsg;
+  };
+  //.. got 1-st closing round bracket (after Value)
+  if( traceLvl > 0 )
+    this.logWr( methName+": found 1-st closing round bracket at:"+ic2+"\n");
+  val= word.substring( ic0, ic2 );
+  if( traceLvl > 0 )
+    this.logWr( methName+": got val:["+val+"]\n");
+  ic0= ic2 + lenCBR;
+  if( ic0 >= lastCh ) {
+    errorMsg=
+      methName+": missing next expected round brackets after pos.#="+ic2+
+        " -bad format\n";
+    errMsgBadParm= errorMsg;
+      this.logWr( "Error: "+errorMsg );
+    return errorMsg;
+  };
+  var ic3= -1;
+  if( word.substr(ic0).startsWith(EQS) ) {
+    //.. found EQS
+    ic3= ic0;
+  }
+  else {
+    errorMsg=
+      methName+": no equal sign after 1-st closing round bracket at pos.#="+ic2+
+      " -bad format";
+    errMsgBadParm= errorMsg;
+      this.logWr( "Error: "+errorMsg+"\n" );
+    return errorMsg;
+  };
+  //.. got Equal sign after 1-st closing round bracket
+  ic0= ic3 + lenEQS;
+  if( ic0 >= lastCh ) {
+    errorMsg= methName+": missing next expected round brackets after pos.#="+ic3+
+      " -bad format";
+    errMsgBadParm= errorMsg;
+      this.logWr( "Error: "+errorMsg+"\n" );
+    return errorMsg;
+  };
+  var ic4= -1;
+  if( word.substr(ic0).startsWith(OBR) ) {
+    //.. found
+    ic4= ic0;
+  }
+  else {
+    errorMsg=
+      methName+": no 2-nd open round bracket after equal sign at pos.#="+ic3+
+      " -bad format";
+    errMsgBadParm= errorMsg;
+      this.logWr( "Error: "+errorMsg+"\n" );
+    return errorMsg;
+  };
+  //.. got 2-nd open round bracket
+  ic0= ic4 + lenOBR;
+  if( ic0 >= lastCh ) {
+    errorMsg=
+      methName+": missing next expected round bracket after pos.#="+ic4+
+        " -bad format";
+    errMsgBadParm= errorMsg;
+      this.logWr( "Error: "+errorMsg+"\n" );
+    return errorMsg;
+  };
+  var ic5= word.indexOf( CBR, ic0 );
+  if( ic5 < 0 ) {
+   errorMsg=
+     methName+": no 2-nd closing round bracket after 2-nd open bracket at pos.#="+
+       ic4+" -bad format";
+    errMsgBadParm= errorMsg;
+      this.logWr( "Error: "+errorMsg+"\n" );
+    return errorMsg;
+  };
+  //.. got 2-nd closing round bracket
+  // (also assuming the remaining chars are whitespaces)
+  parmNamAndVal.push( word.substring(ic0, ic5) );
+  parmNamAndVal.push( val );
+  if( traceLvl > 1 )
+    this.logWr( methName+": got param: name:["+parmNamAndVal[0]+"]\n value:["+
+      parmNamAndVal[1]+"]\n");
+  return ''; //.. empty error message means OK
+};
+
+exports.reportParamBadFmt = function (
+  lineNm, pword, formatErr, perrorFName, pstatFName )
+{
+  var methName= "reportParamBadFmt";
+  this.logWr( methName+": start: the param.name-value format is bad -Error!\n");
+  this.logWr( methName+": signalling error via percent = -1 \n");
+  var percent=-1;
+  var stmsg= '{\"percentage\":'+percent+'}';
+  this.updateProcStatus( pstatFName, stmsg );
+  this.logWr( methName+": writing the error message into a file "+
+    "for next request from the server...");
+  procErrorMsg= "Error: Bad Format of the parameter string";
+  if( pword != null && pword.length > 0 ) {
+    procErrorMsg += ":["+pword+"]";
+  };
+  var lineNmE= lineNm + 1; //.. line numbering in screen editor starts from 1
+  procErrorMsg += "\n  Error found at line #:"+lineNmE;
+  procErrorMsg += "\n  Error Details:["+formatErr+"]";
+  procErrorMsg += "\n  Please, correct the error and try again.";
+  //.. using updateProcStatus to save the error message in a file
+  this.updateProcStatus( perrorFName, procErrorMsg );
+};
+
+exports.logWr = function ( msg ) {
+ // console.log(clName+": logWr: fdL="+this.fdL+" msg:["+msg+"]");
+  try {
+    fs.appendFileSync( this.fdL, msg, 'utf8' );
+  }
+  catch( err ) {
+    console.log(clName+": log uppend: error:"+err.message );
+    throw err;
+  };
+ // fs.appendFile( this.fdL, msg, 'utf8', (err) => {
+ //   if( err ) {
+ //     throw err;
+ //   }
+ // });
+};
+
+  //.. processing percentage or error message
+exports.updateProcStatus = function ( pstfname, statusMsg ) {
+  var methName= "updateProcStatus";
+  this.logWr(methName+": start: pstfname:["+pstfname+"]\n");
+  var pstfname_p= pstfname+".pre";
+  this.logWr(methName+": new status: message["+statusMsg+"]\n");
+  var timeStamp = new Date().toISOString();
+  this.logWr( methName+": timeStamp:["+timeStamp+"]\n");
+  //.. pre-file
+  try {
+    this.fdPSt= fs.openSync( pstfname_p, 'a' );
+  }
+  catch( err ) {
+    this.logWr(clName+": status file open: error:["+err.message+"]\n");
+    throw err;
+  }
+    this.logWr(methName+": writing status: fdPSt="+this.fdPSt+"\n");
+    try {
+      fs.appendFileSync( this.fdPSt, statusMsg, 'utf8' );
+    }
+    catch( err ) {
+      this.logWr(clName+": status file append: error: code="+err.code );
+      throw err;
+    }
+    this.logWr(methName+": closing status file: fdPSt="+this.fdPSt+"\n");
+    fs.closeSync( this.fdPSt );
+    this.logWr(methName+": status file closed - final renaming...\n");
+    fs.renameSync( pstfname_p, pstfname );
+    this.logWr(methName+": status file renamed - finished.\n");
+};
+
+exports.wait = function ( secs ) {
+  var methName= "wait";
+  this.logWr(methName+": start: secs="+secs+"\n");
+  var inStr= String( secs );
+ // this.logWr(methName+": inStr:["+inStr+"]\n");
+  var s_args= new Array( 0 );
+   s_args.push( inStr );
+  var tmoutms= 500 +1000*secs;
+  var mopts= { timeout: tmoutms };
+ // this.logWr(methName+":  mopts:["+ JSON.stringify(mopts)+"]\n");
+  var moutObj= Chproc.spawnSync( 'sleep', s_args, mopts);
+  this.logWr(methName+": moutObj: status:["+moutObj.status+"](0=ok)\n");
+  if( moutObj.error != null ) {
+    console.log(methName+": moutObj: Error:["+JSON.stringify(moutObj.error)+"]\n");  }
+};