ljswitchboard-ljm_special_addresses
Version:
This module allows Kipling to interface with LJM's special address/specific ip feature.
660 lines (559 loc) • 15.6 kB
JavaScript
/*
* This is a nodejs module that allows uers to interact with LJM's special
* addresses config file to configure LJM to scan for specific IP addresses.
* The functions that are exposed to users are:
* 1. verify
* 2. parse
* 3. addIP
* 4. addIPs
*/
var q = require('q');
var fs = require('fs');
var path = require('path');
var natural_sort = require('javascript-natural-sort');
var fse = require('fs-extra');
var ljm_ffi = require('ljm-ffi');
var ljm = ljm_ffi.load();
var DEBUG_LJM_SPECIAL_ADDRESS_INTEGRATION = false;
function ljmDebugOut() {
if(DEBUG_LJM_SPECIAL_ADDRESS_INTEGRATION) {
console.log.apply(this, addresses);
}
}
var DEFAULT_SPECIAL_ADDRESS_FILE_PATHS = {
'darwin': '/usr/local/share/LabJack/LJM/ljm_special_addresses.config',
'linux': '/usr/local/share/LabJack/LJM/ljm_special_addresses.config',
'windows': '',
};
var DEFAULT_SPECIAL_ADDRESS_FILE_PATH;
function GET_DEFAULT_UNIX_SA_PATHS() {
return ['/usr/local/share/LabJack/LJM/ljm_special_addresses.config'];
}
function GET_DEFAULT_WIN32_SA_PATHS() {
return [
'C:\\ProgramData\\LabJack\\LJM\\ljm_special_addresses.config',
'C:\\Documents and Settings\\All Users\\Application Data\\LabJack\\LJM\\ljm_special_addresses.config'
];
// if(IS_WINDOWS_XP) {
// return 'C:\\Documents and Settings\\All Users\\Application Data\\LabJack\\LJM\\ljm_special_addresses.config';
// } else {
// return 'C:\\ProgramData\\LabJack\\LJM\\ljm_special_addresses.config';
// }
}
try {
var DEFAULT_SPECIAL_ADDRESS_FILE_PATHS = {
'linux': GET_DEFAULT_UNIX_SA_PATHS,
'linux2': GET_DEFAULT_UNIX_SA_PATHS,
'sunos': GET_DEFAULT_UNIX_SA_PATHS,
'solaris': GET_DEFAULT_UNIX_SA_PATHS,
'freebsd': GET_DEFAULT_UNIX_SA_PATHS,
'openbsd': GET_DEFAULT_UNIX_SA_PATHS,
'darwin': GET_DEFAULT_UNIX_SA_PATHS,
'mac': GET_DEFAULT_UNIX_SA_PATHS,
'win32': GET_DEFAULT_WIN32_SA_PATHS,
}[process.platform]();
var exists = DEFAULT_SPECIAL_ADDRESS_FILE_PATHS.some(function(loc) {
DEFAULT_SPECIAL_ADDRESS_FILE_PATH = loc;
return fs.existsSync(loc);
});
} catch(err) {
console.log('ERROR!', err, err.stack);
console.error(
'This platform combination is not supported.',
process.platform
);
}
// console.log('Default S-A Path:', DEFAULT_SPECIAL_ADDRESS_FILE_PATH);
function parseOptions(options) {
var parsedOptions = {
'filePath': DEFAULT_SPECIAL_ADDRESS_FILE_PATH,
};
if(options) {
if(options.filePath) {
parsedOptions.filePath = options.filePath;
}
}
return parsedOptions;
}
function prepareOperation (options) {
var data = {
'filePath': options.filePath,
'fileString': '',
'fileData': '',
'newFileString': '',
'isError': false,
'errorStep': undefined,
'errorInfo': undefined,
'ljmVersion': 0,
'hasSpecialAddresses': false,
'ljmStatus': '',
};
return data;
}
function parseGivenUserIPs(userIPs) {
var isObject = true;
if(typeof(userIPs[0]) === 'string') {
isObject = false;
}
if(!isObject) {
var parsedIPs = userIPs.map(function(userIP) {
return {
'ip': userIP,
'comments': []
};
});
return parsedIPs;
} else {
return userIPs;
}
}
function verify() {
var defered = q.defer();
defered.resolve();
return defered.promise;
}
var MAX_FILE_READ_RETRIES = 5;
function innerReadFile(data) {
var defered = q.defer();
var numReTries = 0;
function handleRead(err, fileData) {
if(err) {
numReTries += 1;
if(numReTries < MAX_FILE_READ_RETRIES) {
fs.readFile(data.filePath, handleRead);
} else {
// There was definitely an error... assume file is empty.
data.isError = true;
data.errorStep = 'innerReadFile';
data.errorInfo = err;
defered.reject(data);
}
} else {
data.fileString = fileData.toString();
defered.resolve(data);
}
}
fs.readFile(data.filePath, handleRead);
return defered.promise;
}
var whiteSpaceRegex = /^\s{1,}$/;
var checkForIP = /([0-9]{1,3}.){3}[0-9]{1,3}/;
var checkForComment = /\/\/(\s{1,}).*/;
var getCommentString = /\/\/(\s{1,})/;
function innerParseFile(data) {
// console.log('Parsing File');
var defered = q.defer();
// We need to parse the file like how LJM is doing it...
/*
1. Whitespace is ignored.
2. Empty lines and lines starting with // are ignored.
3. All other lines are expected to contain one IP address, which should not be a broadcast address.
Ex:
// Have LJM connect to the static-IP T7...
// ...in the lab:
192.168.2.207
// ...in the kitchen:
10.0.0.123
*/
// Define a variable that parsed data will get added to.
var innerParseFileStep = 'initializing';
try {
// Before doing anything, lets get rid of the '\r' character.
innerParseFileStep = 'remove \\r chars';
var removedReturnCarrage = data.fileString.split('\r').join('');
// Now we should get rid of the blank lines so search for '\n\n' and replace
// with a single '\n'
innerParseFileStep = 'remove blank lines';
var blankLineRemoved = removedReturnCarrage.split('\n\n').join('\n');
// Now we should split by lines and remove any lines that are purely white
// space.
innerParseFileStep = 'remove whitespace lines';
var linesWithData = [];
blankLineRemoved.split('\n').forEach(function removeWhitespace(lineStr) {
var res = whiteSpaceRegex.exec(lineStr);
if(res) {
// Don't add to the lines w/ data array.
} else {
linesWithData.push(lineStr);
}
});
// Now we should loop through the remaining lines and group the associated
// comments with the IP address.
innerParseFileStep = 'parsing data';
var ipAddresses = [];
var ipData = {};
var tempComments = [];
var tempIP = '';
linesWithData.forEach(function organizingData(lineStr) {
var isIP = checkForIP.exec(lineStr);
if(isIP) {
ipData[isIP[0]] = {
'ip': isIP[0],
'comments': tempComments
};
ipAddresses.push(isIP[0]);
tempComments = [];
} else {
var isComment = checkForComment.exec(lineStr);
var commentHeader = getCommentString.exec(lineStr);
if(commentHeader) {
var commentText = lineStr.split(commentHeader[0])[1];
tempComments.push(commentText);
}
}
});
// Sort the ipData
ipAddresses.sort(natural_sort);
// Fill the fileData array
data.fileData = [];
ipAddresses.forEach(function(ipAddress) {
data.fileData.push(ipData[ipAddress]);
});
defered.resolve(data);
} catch(err) {
data.isError = true;
data.errorStep = 'innerParseFile:' + innerParseFileStep;
data.errorInfo = err;
defered.reject(data);
}
return defered.promise;
}
function innerParse(data) {
var defered = q.defer();
innerReadFile(data)
.then(innerParseFile)
.then(defered.resolve)
.catch(defered.reject);
return defered.promise;
}
function parse(options) {
var defered = q.defer();
var parsedOptions = parseOptions(options);
var data = prepareOperation(parsedOptions);
function finalize(successData) {
defered.resolve({
'filePath': successData.filePath,
'fileData': successData.fileData,
// 'ljmVersion': successData.ljmVersion,
// 'hasSpecialAddresses': successData.hasSpecialAddresses,
// 'ljmStatus': successData.ljmStatus,
});
}
function resolveError(errData) {
defered.reject({
'data': errData,
'isError': errData.isError,
'errorStep': errData.errorStep,
'errorInfo': errData.errorInfo,
});
}
innerParse(data)
.then(finalize)
.catch(resolveError);
return defered.promise;
}
function sortUserIPs(a, b) {
var ips = {};
ips[a.ip] = a;
ips[b.ip] = b;
var selectedIP = natural_sort(a.ip, b.ip);
return ips[selectedIP];
}
function removeDuplicateIPAddresses(data) {
var defered = q.defer();
var userIPs = [];
var ips = {};
var ipAddrs = [];
data.fileData.forEach(function(userIP) {
var exists = false;
if(ips[userIP.ip]) {
exists = true;
}
// Indicate that the IP exists.
ips[userIP.ip] = userIP;
if(!exists) {
ipAddrs.push(userIP.ip);
}
});
userIPs = ipAddrs.map(function(ipAddr) {
return ips[ipAddr];
});
// over-write the fileData object.
data.fileData = userIPs;
defered.resolve(data);
return defered.promise;
}
function generateNewFileString(data) {
var defered = q.defer();
var userIPs = data.fileData;
var ips = {};
var ipAddrs = [];
userIPs.forEach(function(userIP) {
var exists = false;
if(ips[userIP.ip]) {
exists = true;
}
ips[userIP.ip] = userIP;
if(!exists) {
ipAddrs.push(userIP.ip);
}
});
// console.log('Un-sorted Data', ipAddrs);
ipAddrs.sort(natural_sort);
// console.log('Sorted Data', ipAddrs);
var sortedUserIPs = [];
ipAddrs.forEach(function(ipAddr) {
sortedUserIPs.push(ips[ipAddr]);
});
var partialStrs = [];
partialStrs = sortedUserIPs.map(function(userIP) {
var ipDataLines = [];
userIP.comments.forEach(function(comment) {
ipDataLines.push('// ' + comment);
});
ipDataLines.push(userIP.ip);
return ipDataLines.join('\r\n') + '\r\n';
});
// console.log('Partial Strs', partialStrs);
var fullStr = partialStrs.join('\r\n');
// Save the data into the newFileString attribute of the data object.
// console.log('Full Str:');
// console.log(fullStr);
data.newFileString = fullStr;
defered.resolve(data);
return defered.promise;
}
var MAX_FILE_WRITE_RETRIES = 5;
function innerWriteFile(data) {
// console.log('Creating file', parsedOptions.filePath);
var defered = q.defer();
var numReTries = 0;
function handleWrite(err) {
if(err) {
numReTries += 1;
if(numReTries < MAX_FILE_WRITE_RETRIES) {
fse.outputFile(
data.filePath,
data.newFileString,
handleWrite
);
} else {
// There was definitely an error... assume file is empty.
data.isError = true;
data.errorStep = 'innerWriteFile';
data.errorInfo = err;
defered.reject(data);
}
} else {
defered.resolve(data);
}
}
fse.outputFile(
data.filePath,
data.newFileString,
handleWrite
);
return defered.promise;
}
var MIN_LJM_VERSION = 1.09;
function checkLJMVersion(data) {
ljmDebugOut('Checking LJM Version');
var defered = q.defer();
var libVerKey = 'LJM_LIBRARY_VERSION';
function handleReadLib(ljmData) {
ljmDebugOut('LJM Lib Ver', ljmData);
data.ljmVersion = ljmData.Value;
if(ljmData.Value >= MIN_LJM_VERSION) {
data.hasSpecialAddresses = true;
}
defered.resolve(data);
}
ljm.LJM_ReadLibraryConfigS.async(libVerKey, 0, handleReadLib);
return defered.promise;
}
function updateLJMSpecialAddressesFilePath(data) {
ljmDebugOut('Updating LJM Special Addresses File Path');
var defered = q.defer();
var specAddrFilePathKey = 'LJM_SPECIAL_ADDRESSES_FILE';
function handleWriteLib(ljmData) {
if(ljmData.ljmError) {
console.error('Failed to write', data.filePath,
'to', specAddrFilePathKey);
}
defered.resolve(data);
}
if(data.hasSpecialAddresses) {
ljm.LJM_WriteLibraryConfigStringS.async(
specAddrFilePathKey,
data.filePath,
handleWriteLib
);
} else {
defered.resolve(data);
}
return defered.promise;
}
function checkLJMSpecialAddressesStatus(data) {
ljmDebugOut('Checking LJM Sepcial Addresses Status');
var defered = q.defer();
var specAddrFileStatusKey = 'LJM_SPECIAL_ADDRESSES_STATUS';
function handleReadLib(ljmData) {
if(ljmData.ljmError) {
console.error('Failed to read', specAddrFileStatusKey);
data.ljmStatus = ljmData.String;
} else {
ljmDebugOut('Checked:', specAddrFileStatusKey);
ljmDebugOut(ljmData.String);
data.ljmStatus = ljmData.String;
}
defered.resolve(data);
}
if(data.hasSpecialAddresses) {
ljm.LJM_ReadLibraryConfigStringS.async(
specAddrFileStatusKey,
'',
handleReadLib
);
} else {
data.ljmStatus = 'Installed Version of LJM does not have the Special ';
data.ljmStatus +='Addresses feature. Please install version ';
data.ljmStatus += MIN_LJM_VERSION.toString() + ' or greater.';
defered.resolve(data);
}
return defered.promise;
}
function innerSave(data) {
var defered = q.defer();
removeDuplicateIPAddresses(data)
.then(generateNewFileString)
.then(innerWriteFile)
// instruct LJM to load file
.then(checkLJMVersion)
.then(updateLJMSpecialAddressesFilePath)
.then(checkLJMSpecialAddressesStatus)
.then(defered.resolve)
.catch(defered.reject);
return defered.promise;
}
function save(userIPs, options) {
var defered = q.defer();
var parsedOptions = parseOptions(options);
var data = prepareOperation(parsedOptions);
data.fileData = parseGivenUserIPs(userIPs);
function finalize(successData) {
defered.resolve({
'filePath': successData.filePath,
'fileData': successData.fileData,
'ljmVersion': successData.ljmVersion,
'hasSpecialAddresses': successData.hasSpecialAddresses,
'ljmStatus': successData.ljmStatus,
});
}
function resolveError(errData) {
// console.log('Handling Error...', errData);
defered.reject({
'data': errData,
'isError': errData.isError,
'errorStep': errData.errorStep,
'errorInfo': errData.errorInfo,
});
}
innerSave(data)
.then(finalize)
.catch(resolveError);
return defered.promise;
}
function innerLoad(data) {
var defered = q.defer();
innerReadFile(data)
.then(innerParseFile)
// instruct LJM to load file
.then(checkLJMVersion)
.then(updateLJMSpecialAddressesFilePath)
.then(checkLJMSpecialAddressesStatus)
.then(defered.resolve)
.catch(defered.reject);
return defered.promise;
}
function load(options) {
var defered = q.defer();
var parsedOptions = parseOptions(options);
var data = prepareOperation(parsedOptions);
function finalize(successData) {
defered.resolve({
'filePath': successData.filePath,
'fileData': successData.fileData,
'ljmVersion': successData.ljmVersion,
'hasSpecialAddresses': successData.hasSpecialAddresses,
'ljmStatus': successData.ljmStatus,
});
}
function resolveError(errData) {
defered.reject({
'data': errData,
'isError': errData.isError,
'errorStep': errData.errorStep,
'errorInfo': errData.errorInfo,
});
}
innerLoad(data)
.then(finalize)
.catch(resolveError);
return defered.promise;
}
function getAddNewIPs(newIPs) {
var parsedNewIPs = parseGivenUserIPs(newIPs);
return function addNewIPs(data) {
var defered = q.defer();
// Add new IPs to the parsed file data array.
parsedNewIPs.forEach(function(parsedNewIP) {
data.fileData.push(parsedNewIP);
});
defered.resolve(data);
return defered.promise;
};
}
function addIP(newIP, options) {
return addIPs([newIP], options);
}
function addIPs(newIPs, options) {
var defered = q.defer();
var parsedOptions = parseOptions(options);
var data = prepareOperation(parsedOptions);
function finalize(successData) {
defered.resolve({
'filePath': successData.filePath,
'fileData': successData.fileData,
'ljmVersion': successData.ljmVersion,
'hasSpecialAddresses': successData.hasSpecialAddresses,
'ljmStatus': successData.ljmStatus,
});
}
function resolveError(errData) {
defered.reject({
'data': errData,
'isError': errData.isError,
'errorStep': errData.errorStep,
'errorInfo': errData.errorInfo,
});
}
innerParse(data)
.then(getAddNewIPs(newIPs))
.then(innerSave)
.then(finalize)
.catch(resolveError);
return defered.promise;
}
function getDefaultSpecialAddressesFilePath() {
var defered = q.defer();
defered.resolve({
'filePath': DEFAULT_SPECIAL_ADDRESS_FILE_PATH,
});
return defered.promise;
}
exports.parse = parse;
exports.load = load;
exports.save = save;
exports.addIP = addIP;
exports.addIPs = addIPs;
exports.getDefaultFilePath = getDefaultSpecialAddressesFilePath;