diff --git a/core/client_connections.js b/core/client_connections.js index 6a2504cc..2e838e77 100644 --- a/core/client_connections.js +++ b/core/client_connections.js @@ -18,6 +18,7 @@ function addNewClient(client, clientSock) { var id = client.session.id = clientConnections.push(client) - 1; // Create a client specific logger + // Note that this will be updated @ login with additional information client.log = logger.log.child( { clientId : id } ); var connInfo = { diff --git a/core/client_term.js b/core/client_term.js index 2cf90ac3..42d71a77 100644 --- a/core/client_term.js +++ b/core/client_term.js @@ -144,21 +144,23 @@ ClientTerminal.prototype.isANSI = function() { // :TODO: probably need to update these to convert IAC (0xff) -> IACIAC (escape it) -ClientTerminal.prototype.write = function(s, convertLineFeeds) { - this.rawWrite(this.encode(s, convertLineFeeds)); +ClientTerminal.prototype.write = function(s, convertLineFeeds, cb) { + this.rawWrite(this.encode(s, convertLineFeeds), cb); }; -ClientTerminal.prototype.rawWrite = function(s) { +ClientTerminal.prototype.rawWrite = function(s, cb) { if(this.output) { this.output.write(s, function written(err) { - if(err) { + if(_.isFunction(cb)) { + cb(err); + } else if(err) { Log.warn('Failed writing to socket: ' + err.toString()); } }); } }; -ClientTerminal.prototype.pipeWrite = function(s, spec) { +ClientTerminal.prototype.pipeWrite = function(s, spec, cb) { spec = spec || 'renegade'; var conv = { @@ -166,11 +168,12 @@ ClientTerminal.prototype.pipeWrite = function(s, spec) { renegade : renegadeToAnsi, }[spec] || enigmaToAnsi; - this.write(conv(s, this)); + this.write(conv(s, this), null, cb); // null = use default for |convertLineFeeds| }; ClientTerminal.prototype.encode = function(s, convertLineFeeds) { - convertLineFeeds = _.isUndefined(convertLineFeeds) ? this.convertLF : convertLineFeeds; + convertLineFeeds = _.isBoolean(convertLineFeeds) ? convertLineFeeds : this.convertLF; + if(convertLineFeeds && _.isString(s)) { s = s.replace(/\n/g, '\r\n'); } diff --git a/core/message.js b/core/message.js index 6f67ba9e..11dcbf83 100644 --- a/core/message.js +++ b/core/message.js @@ -81,8 +81,9 @@ function Message(options) { } Message.WellKnownAreaNames = { - Invalid : '', - Private : 'private_mail' + Invalid : '', + Private : 'private_mail', + Bulletin : 'local_bulletin', }; // :TODO: This doesn't seem like a good way to go -- perhaps only for local/user2user, or just use diff --git a/core/message_area.js b/core/message_area.js index b32f724a..61cbb786 100644 --- a/core/message_area.js +++ b/core/message_area.js @@ -14,6 +14,7 @@ exports.getDefaultMessageArea = getDefaultMessageArea; exports.getMessageAreaByName = getMessageAreaByName; exports.changeMessageArea = changeMessageArea; exports.getMessageListForArea = getMessageListForArea; +exports.getNewMessagesInAreaForUser = getNewMessagesInAreaForUser; exports.getMessageAreaLastReadId = getMessageAreaLastReadId; exports.updateMessageAreaLastReadId = updateMessageAreaLastReadId; @@ -104,6 +105,60 @@ function changeMessageArea(client, areaName, cb) { ); } +function getNewMessagesInAreaForUser(userId, areaName, cb) { + // + // If |areaName| is Message.WellKnownAreaNames.Private, + // only messages addressed to |userId| should be returned. + // + // Only messages > lastMessageId should be returned + // + var msgList = []; + + async.waterfall( + [ + function getLastMessageId(callback) { + getMessageAreaLastReadId(userId, areaName, function fetched(err, lastMessageId) { + callback(null, lastMessageId || 0); // note: willingly ignoring any errors here! + }); + }, + function getMessages(lastMessageId, callback) { + var sql = + 'SELECT message_id, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, modified_timestamp, view_count ' + + 'FROM message ' + + 'WHERE area_name="' + areaName + '" AND message_id > ' + lastMessageId; + + if(Message.WellKnownAreaNames.Private === areaName) { + sql += + ' AND message_id in (' + + 'SELECT message_id from message_meta where meta_category=' + Message.MetaCategories.System + + ' AND meta_name="' + Message.SystemMetaNames.LocalToUserID + '" and meta_value=' + userId + ')'; + } + + sql += ' ORDER BY message_id;'; + + msgDb.each(sql, function msgRow(err, row) { + if(!err) { + msgList.push( { + messageId : row.message_id, + messageUuid : row.message_uuid, + replyToMsgId : row.reply_to_message_id, + toUserName : row.to_user_name, + fromUserName : row.from_user_name, + subject : row.subject, + modTimestamp : row.modified_timestamp, + viewCount : row.view_count, + } ); + } + }, callback); + } + ], + function complete(err) { + console.log(msgList) + cb(err, msgList); + } + ); +} + function getMessageListForArea(options, areaName, cb) { // // options.client (required) diff --git a/core/new_scan.js b/core/new_scan.js new file mode 100644 index 00000000..222abce9 --- /dev/null +++ b/core/new_scan.js @@ -0,0 +1,104 @@ +/* jslint node: true */ +'use strict'; + +// ENiGMA½ +var msgArea = require('./message_area.js'); +var Message = require('./message.js'); +var MenuModule = require('./menu_module.js').MenuModule; + +var async = require('async'); + +exports.moduleInfo = { + name : 'New Scan', + desc : 'Performs a new scan against various areas of the system', + author : 'NuSkooler', +}; + +exports.getModule = NewScanModule; + +/* + * :TODO: + * * Update message ID when reading (this should be working!) + * * New scan all areas + * * User configurable new scan: Area selection (avail from messages area) + * + * + +*/ + + +function NewScanModule(options) { + MenuModule.call(this, options); + + var self = this; + var config = this.menuConfig.config; + + this.currentStep = 'privateMail'; + + this.newScanMessageArea = function(areaName, cb) { + async.waterfall( + [ + function newScanAreaAndGetMessages(callback) { + msgArea.getNewMessagesInAreaForUser( + self.client.user.userId, areaName, function msgs(err, msgList) { + callback(err, msgList); + } + ); + }, + function displayMessageList(msgList, callback) { + if(msgList && msgList.length > 0) { + var nextModuleOpts = { + extraArgs: { + messageAreaName : areaName, + messageList : msgList, + } + }; + + self.gotoMenu(config.newScanMessageList || 'newScanMessageList', nextModuleOpts); + } else { + callback(null); + } + } + ], + function complete(err) { + cb(err); + } + ); + }; +} + +require('util').inherits(NewScanModule, MenuModule); + +NewScanModule.prototype.getSaveState = function() { + return { + currentStep : this.currentStep, + }; +}; + +NewScanModule.prototype.restoreSavedState = function(savedState) { + this.currentStep = savedState.currentStep; +}; + +NewScanModule.prototype.mciReady = function(mciData, cb) { + + var self = this; + + // :TODO: display scan step/etc. + + switch(this.currentStep) { + case 'privateMail' : + self.currentStep = 'finished'; + self.newScanMessageArea(Message.WellKnownAreaNames.Private, cb); + break; + + default : + cb(null); + } + +}; + +/* +NewScanModule.prototype.finishedLoading = function() { + NewScanModule.super_.prototype.finishedLoading.call(this); +}; +*/ \ No newline at end of file diff --git a/core/predefined_mci.js b/core/predefined_mci.js index 9ac291c0..2d9d4a7d 100644 --- a/core/predefined_mci.js +++ b/core/predefined_mci.js @@ -109,7 +109,7 @@ function getPredefinedMCIValue(client, code) { TC : function totalCalls() { return sysProp.getSystemProperty('login_count').toString(); }, - }[code](); + }[code](); // :TODO: Just call toString() here and remove above - DRY } catch(e) { // Don't use client.log here as we may not have a client logger established yet!! diff --git a/core/system_menu_method.js b/core/system_menu_method.js index 54528c80..0299c00e 100644 --- a/core/system_menu_method.js +++ b/core/system_menu_method.js @@ -2,7 +2,7 @@ 'use strict'; var theme = require('./theme.js'); -var clientConnections = require('./client_connections.js').clientConnections; +var removeClient = require('./client_connections.js').removeClient; var ansi = require('./ansi_term.js'); var userDb = require('./database.js').dbs.user; var sysProp = require('./system_property.js'); @@ -50,9 +50,11 @@ function logoff(callingMenu, formData, extraArgs) { client.term.write( ansi.normal() + '\n' + iconv.decode(require('crypto').randomBytes(Math.floor(Math.random() * 65) + 20), client.term.outputEncoding) + - 'NO CARRIER'); + 'NO CARRIER', null, function written() { - client.end(); + // after data is written, disconnect & remove the client + removeClient(client); + }); }, 500); } diff --git a/core/user_login.js b/core/user_login.js index cfa4caa4..3b5798a9 100644 --- a/core/user_login.js +++ b/core/user_login.js @@ -5,6 +5,7 @@ var theme = require('./theme.js'); var clientConnections = require('./client_connections.js').clientConnections; var userDb = require('./database.js').dbs.user; var sysProp = require('./system_property.js'); +var logger = require('./logger.js'); var async = require('async'); var _ = require('lodash'); @@ -48,13 +49,13 @@ function userLogin(client, username, password, cb) { var existingConnError = new Error('Already logged in as supplied user'); existingClientConnection.existingConn = true; - cb(existingClientConnection); - return; + return cb(existingClientConnection); } - // use client.user so we can get correct case - client.log.info( { username : user.username }, 'Successful login'); + // update client logger with addition of username + client.log = logger.log.child( { clientId : client.log.fields.clientId, username : user.username }); + client.log.info('Successful login'); async.parallel( [ diff --git a/mods/whos_online.js b/mods/whos_online.js index 4e854efb..ad0219d8 100644 --- a/mods/whos_online.js +++ b/mods/whos_online.js @@ -73,7 +73,33 @@ WhosOnlineModule.prototype.mciReady = function(mciData, cb) { var now = moment(); - onlineListView.setItems(_.map(onlineList, function formatOnlineEntry(oe) { + onlineListView.setItems(_.map(onlineList, function formatOnlineEntry(oe) { + var fmtObj = { + node : oe.node, + userId : oe.user.userId, + userName : oe.user.username, + realName : oe.user.properties.real_name, + timeOn : function getTimeOn() { + var diff = now.diff(moment(oe.user.properties.last_login_timestamp), 'minutes'); + return _.capitalize(moment.duration(diff, 'minutes').humanize()); + }, + action : function getCurrentAction() { + var cmm = oe.currentMenuModule; + if(cmm) { + return cmm.menuConfig.desc || 'Unknown'; + } + return 'Unknown'; + //oe.currentMenuModule.menuConfig.desc || 'Unknown', + }, + location : oe.user.properties.location, + affils : oe.user.properties.affiliation, + }; + try { + return listFormat.format(fmtObj); + } catch(e) { + console.log('Exception caught formatting: ' + e.toString() + ':\n' + JSON.stringify(fmtObj)); + } + /* return listFormat.format({ node : oe.node, userId : oe.user.userId, @@ -94,6 +120,7 @@ WhosOnlineModule.prototype.mciReady = function(mciData, cb) { location : oe.user.properties.location, affils : oe.user.properties.affiliation, }); + */ })); // :TODO: This is a hack until pipe codes are better implemented