text: /**
   * Publish usernames to all the sockets
   * @param  {string}   meetingID ID of the meeting
   * @param  {string}   sessionID ID of the user
   * @param  {Function} callback  callback to call when finished
   * @return {undefined} publish to Redis PubSub
   */
  exports.publishUsernames = function(meetingID, sessionID, callback) {
    var usernames = [];
    redisAction.getUsers(meetingID, function (users) {
        for (var i = users.length - 1; i >= 0; i--){
          usernames.push({ 'name' : users[i].username, 'id' : users[i].pubID });
        };
        var receivers = sessionID != undefined ? sessionID : meetingID;
        pub.publish(receivers, JSON.stringify(['user list change', usernames]));
        if(callback) callback(true);
    });
    store.scard(redisAction.getUsersString(meetingID), function(err, cardinality) {
      if(cardinality == '0') {
        redisAction.processMeeting(meetingID);
        if(callback) callback(false);
      }
    });
  };
  
  /**
   * Publish presenter to appropriate clients.
   * @param  {string}    meetingID ID of the meeting
   * @param  {string}    sessionID ID of the user
   * @param  {Function}  callback  callback to call when finished
   * @return {undefined} publish to Redis PubSub
   */
  exports.publishPresenter = function(meetingID, sessionID, callback) {
    redisAction.getPresenterPublicID(meetingID, function(publicID) {
      var receivers = sessionID != undefined ? sessionID : meetingID;
      pub.publish(receivers, JSON.stringify(['setPresenter', publicID]));
      if(callback) callback(true);
    });
  };
  
  /**
   * Get all messages from Redis and publish to a specific sessionID (user)
   * @param  {string}    meetingID ID of the meeting
   * @param  {string}    sessionID ID of the user
   * @param  {Function}  callback  callback to call when finished
   * @return {undefined} publish to Redis PubSub
   */
  exports.publishMessages = function(meetingID, sessionID, callback) {
    var messages = [];
    redisAction.getCurrentPresentationID(meetingID, function(presentationID) {
      redisAction.getCurrentPageID(meetingID, presentationID, function(pageID) {
        redisAction.getItems(meetingID, presentationID, pageID, 'messages', function (messages) {
          var receivers = sessionID != undefined ? sessionID : meetingID;
          pub.publish(receivers, JSON.stringify(['all_messages', messages]));
          if(callback) callback();
        });
      });
    });
  };
  
  /**
   * Publish list of slides from Redis to the appropriate clients
   * @param  {string}    meetingID ID of the meeting
   * @param  {string}    sessionID ID of the user
   * @param  {Function}  callback  callback to call when finished
   * @return {undefined} publish to Redis PubSub
   */
  exports.publishSlides = function(meetingID, sessionID, callback) {
    var slides = [];
    redisAction.getCurrentPresentationID(meetingID, function(presentationID) {
      redisAction.getPageIDs(meetingID, presentationID, function(presentationID, pageIDs) {
       var numOfSlides = pageIDs.length;
       var slideCount = 0;
       for(var i = 0; i < numOfSlides; i++) {
         redisAction.getPageImage(meetingID, presentationID, pageIDs[i], function(pageID, filename) {
           redisAction.getImageSize(meetingID, presentationID, pageID, function(width, height) {
             slides.push(['images/presentation' +presentationID+'/'+filename, width, height]);
              if(slides.length == numOfSlides) {
                 var receivers = sessionID != undefined ? sessionID : meetingID;
                 pub.publish(receivers, JSON.stringify(['all_slides', slides]));
                 if(callback) callback();
              }
           });
         });
       }
      });
    });
  };
  /**
   * Publish list of shapes from Redis to appropriate clients
   * @param  {string}    meetingID ID of the meeting
   * @param  {string}    sessionID ID of the user
   * @param  {Function}  callback  callback to call when finished
   * @return {undefined} publish to Redis PubSub
   */
  exports.publishShapes = function(meetingID, sessionID, callback) {
    var shapes = [];
    redisAction.getCurrentPresentationID(meetingID, function(presentationID) {
      redisAction.getCurrentPageID(meetingID, presentationID, function(pageID) {
        redisAction.getItems(meetingID, presentationID, pageID, 'currentshapes', function (shapes) {
          var receivers = sessionID != undefined ? sessionID : meetingID;
          pub.publish(receivers, JSON.stringify(['all_shapes', shapes]));
          if(callback) callback();
        });
      });
    });
  };
  
  /**
   * Publish viewbox from Redis to appropriate clients
   * @param  {string}    meetingID ID of the meeting
   * @param  {string}    sessionID ID of the user
   * @param  {Function}  callback  callback to call when finished
   * @return {undefined} publish to Redis PubSub
   */
  exports.publishViewBox = function(meetingID, sessionID, callback) {
    redisAction.getCurrentPresentationID(meetingID, function(presentationID) {
      redisAction.getViewBox(meetingID, function(viewBox) {
        viewBox = JSON.parse(viewBox);
        var receivers = sessionID != undefined ? sessionID : meetingID;
        pub.publish(receivers, JSON.stringify(['paper', viewBox[0], viewBox[1], viewBox[2], viewBox[3]]));
        if(callback) callback();
      });
    });
  };
  
  /**
   * Publish tool from Redis to appropriate clients
   * @param  {string}    meetingID ID of the meeting
   * @param  {string}    sessionID ID of the user
   * @param  {string}    tool      [description]
   * @param  {Function}  callback  callback to call when finished
   * @return {undefined} publish to Redis PubSub
   */
  exports.publishTool = function(meetingID, sessionID, tool, callback) {
    redisAction.getCurrentTool(meetingID, function(tool) {
      var receivers = sessionID != undefined ? sessionID : meetingID;
      pub.publish(receivers, JSON.stringify(['toolChanged', tool]));
      if(callback) callback();
    });
  };
  
  /**
   * All socket IO events that can be emitted by the client
   * @param {[type]} socket [description]
   */
  exports.SocketOnConnection = function(socket) {
   
    //When a user sends a message...
    socket.on('msg', function (msg) {
      msg = sanitizer.escape(msg);
      var handshake = socket.handshake;
      var sessionID = handshake.sessionID;
      var meetingID = handshake.meetingID;
      redisAction.isValidSession(meetingID, sessionID, function (reply) {
        if(reply) {
          if(msg.length > max_chat_length) {
            pub.publish(sessionID, JSON.stringify(['msg', 'System', 'Message too long.']));
          }
          else {
            var username = handshake.username;
            pub.publish(meetingID, JSON.stringify(['msg', username, msg]));
            var messageID = rack(); //get a randomly generated id for the message
           
            //try later taking these nulls out and see if the function still works
            store.rpush(redisAction.getMessagesString(meetingID, null, null), messageID); //store the messageID in the list of messages
            store.hmset(redisAction.getMessageString(meetingID, null, null, messageID), 'message', msg, 'username', username);
          }
        }
      });
    });
  
    /**
     * When a user connects to the socket...
     * @return {undefined} publish to Redis PubSub
     */
    socket.on('user connect', function () {
      var handshake = socket.handshake;
      var sessionID = handshake.sessionID;
      var meetingID = handshake.meetingID;
      redisAction.isValidSession(meetingID, sessionID, function (reply) {
       if(reply) {
          var username = handshake.username;
          var socketID = socket.id;
       
          socket.join(meetingID); //join the socket Room with value of the meetingID
          socket.join(sessionID); //join the socket Room with value of the sessionID
         
          //add socket to list of sockets.
          redisAction.getUserProperties(meetingID, sessionID, function(properties) {
            var numOfSockets = parseInt(properties.sockets, 10);
            numOfSockets+=1;
            store.hset(redisAction.getUserString(meetingID, sessionID), 'sockets', numOfSockets);
            if ((properties.refreshing == 'false') && (properties.dupSess == 'false')) {
              //all of the next sessions created with this sessionID are duplicates
              store.hset(redisAction.getUserString(meetingID, sessionID), 'dupSess', true);
              socketAction.publishUsernames(meetingID, null, function() {
                socketAction.publishPresenter(meetingID);
              });
           }
           else {
             store.hset(redisAction.getUserString(meetingID, sessionID), 'refreshing', false);
             socketAction.publishUsernames(meetingID, sessionID, function() {
               socketAction.publishPresenter(meetingID, sessionID);
             });
           }
           socketAction.publishMessages(meetingID, sessionID);
           socketAction.publishSlides(meetingID, sessionID, function() {
             socketAction.publishTool(meetingID, sessionID);
             socketAction.publishShapes(meetingID, sessionID);
             socketAction.publishViewBox(meetingID, sessionID);
           });
         });
       }
      });
    });
  
    /**
     * When a user disconnects from the socket...
     * @return {undefined} publish to Redis PubSub
     */
    socket.on('disconnect', function () {
      var handshake = socket.handshake;
     var sessionID = handshake.sessionID;
     var meetingID = handshake.meetingID;
     //check if user is still in database
     redisAction.isValidSession(meetingID, sessionID, function (isValid) {
       if(isValid) {
         var username = handshake.username;
         var socketID = socket.id;
         redisAction.updateUserProperties(meetingID, sessionID, ['refreshing', true], function(success) {
           setTimeout(function () {
               //in one second, check again...
             redisAction.isValidSession(meetingID, sessionID, function (isValid) {
              if(isValid) {
                redisAction.getUserProperties(meetingID, sessionID, function(properties) {
                    var numOfSockets = parseInt(properties.sockets, 10);
                    numOfSockets-=1;
                  if(numOfSockets == 0) {
                    redisAction.deleteUser(meetingID, sessionID, function() {
                      socketAction.publishUsernames(meetingID);
                    });
                  }
                  else {
                    redisAction.updateUserProperties(meetingID, sessionID, ['sockets', numOfSockets]);
                  }
                });
              }
               else {
                socketAction.publishUsernames(meetingID);
                socketAction.publishPresenter(meetingID, sessionID);
               }
             });
           }, 1000);
         });
       }
     });
    });
   
    /**
     * When the user logs out
     * @return {undefined} publish to Redis PubSub
     */
    socket.on('logout', function() {
      var handshake = socket.handshake;
     var sessionID = handshake.sessionID;
     var meetingID = handshake.meetingID;
     redisAction.isValidSession(meetingID, sessionID, function (isValid) {
       if(isValid) {
         //initialize local variables
         var username = handshake.username;
         //remove the user from the list of users
         store.srem(redisAction.getUsersString(meetingID), sessionID, function(numDeleted) {
           //delete key from database
           store.del(redisAction.getUserString(meetingID, sessionID), function(reply) {
              pub.publish(sessionID, JSON.stringify(['logout'])); //send to all users on same session (all tabs)
              socket.disconnect(); //disconnect own socket
           });
         });
       }
       socketAction.publishUsernames(meetingID);
      });
    });
   
    /**
     * A user clicks to change to previous slide
     * @return {undefined} publish to Redis PubSub
     */
    socket.on('prevslide', function () {
      var handshake = socket.handshake;
     var sessionID = handshake.sessionID;
     var meetingID = handshake.meetingID;
      redisAction.getPresenterSessionID(meetingID, function(presenterID) {
        if(presenterID == sessionID) {
          redisAction.getCurrentPresentationID(meetingID, function(presentationID) {
            redisAction.changeToPrevPage(meetingID, presentationID, function(pageID){
              redisAction.getPageImage(meetingID, presentationID, pageID, function(pageID, filename)  {
                pub.publish(meetingID, JSON.stringify(['changeslide', 'images/presentation' + presentationID + '/'+filename]));
                pub.publish(meetingID, JSON.stringify(['clrPaper']));
               socketAction.publishShapes(meetingID);
              });
            });
          });
        }
      });
    });
   
    /**
     * A user clicks to change to next slide
     * @return {undefined} publish to Redis PubSub
     */
    socket.on('nextslide', function () {
      var handshake = socket.handshake;
     var sessionID = handshake.sessionID;
     var meetingID = handshake.meetingID;
      redisAction.getPresenterSessionID(meetingID, function(presenterID) {
        if(presenterID == sessionID) {
          redisAction.getCurrentPresentationID(meetingID, function(presentationID) {
            redisAction.changeToNextPage(meetingID, presentationID, function(pageID){
              redisAction.getPageImage(meetingID, presentationID, pageID, function(pageID, filename) {
                pub.publish(meetingID, JSON.stringify(['changeslide', 'images/presentation' + presentationID + '/'+filename]));
                pub.publish(meetingID, JSON.stringify(['clrPaper']));
               socketAction.publishShapes(meetingID);
              });
            });
          });
        }
      });
    });
   
    /**
     * When a rectangle creation event is received
     * @param  {string} shape type of shape
     * @param  {Object} data  information needed to draw the shape
     * @return {undefined} publish to Redis PubSub
     */
    socket.on('makeShape', function (shape, data) {
      var meetingID = socket.handshake.meetingID;
      redisAction.getPresenterSessionID(meetingID, function(presenterID) {
        if(presenterID == socket.handshake.sessionID) {
          pub.publish(meetingID, JSON.stringify(['makeShape', shape, data]));
        }
      });
    });
   
    /**
     * When a update shape event is received
     * @param  {string} shape type of shape
     * @param  {Object} data  information needed to draw the shape
     * @return {undefined} publish to Redis PubSub
     */
    socket.on('updShape', function (shape, data) {
      var meetingID = socket.handshake.meetingID;
      redisAction.getPresenterSessionID(meetingID, function(presenterID) {
        if(presenterID == socket.handshake.sessionID) {
          pub.publish(meetingID, JSON.stringify(['updShape', shape, data]));
        }
      });
    });
  
    /**
     * When a cursor move event is received
     * @param  {[type]} x x coord of cursor as a percentage of width
     * @param  {[type]} y y coord of cursor as a percentage of height
     * @return {undefined} publish to Redis PubSub
     */
    socket.on('mvCur', function (x, y) {
      var meetingID = socket.handshake.meetingID;
      redisAction.getPresenterSessionID(meetingID, function(presenterID) {
        if(presenterID == socket.handshake.sessionID) {
          pub.publish(meetingID, JSON.stringify(['mvCur', x, y]));
        }
      });
    });
   
    /**
     * When a clear Paper event is received
     * @return {undefined} publish to Redis PubSub
     */
    socket.on('clrPaper', function () {
      var meetingID = socket.handshake.meetingID;
      var sessionID = socket.handshake.sessionID;
      redisAction.getPresenterSessionID(meetingID, function(presenterID) {
        if(presenterID == socket.handshake.sessionID) {
          redisAction.getCurrentPresentationID(meetingID, function(presentationID) {
            redisAction.getCurrentPageID(meetingID, presentationID, function(pageID) {
              redisAction.getItemIDs(meetingID, presentationID, pageID, 'currentshapes', function(meetingID, presentationID, pageID, itemIDs, itemName) {
                redisAction.deleteItemList(meetingID, presentationID, pageID, itemName, itemIDs);
              });
              pub.publish(meetingID, JSON.stringify(['clrPaper']));
              socketAction.publishTool(meetingID, sessionID);
            });
          });
        }
      });
    });
  
    /**
     * When the user wishes to set the presenter to another user
     * @param  {[type]} publicID public ID of user to make presenter
     * @return {undefined} publish to Redis PubSub
     */
    socket.on('setPresenter', function (publicID) {
      console.log('setting presenter to' + publicID);
      var meetingID = socket.handshake.meetingID;
      redisAction.setPresenterFromPublicID(meetingID, publicID, function(success) {
        if(success) {
          pub.publish(meetingID, JSON.stringify(['setPresenter', publicID]));
        }
      });
    });
  
    /**
     * When a user is updating the viewBox of the paper
     * @param  {number} cx x-offset from corner as a percentage of width
     * @param  {number} cy y-offset from corner as a percentage of height
     * @param  {number} sw width of page as a percentage of original width
     * @param  {number} sh height of page as a percentage of original height
     * @return {undefined} publish to Redis PubSub
     */
    socket.on('paper', function (cx, cy, sw, sh) {
      var meetingID = socket.handshake.meetingID;
      redisAction.getPresenterSessionID(meetingID, function(presenterID) {
        if(presenterID == socket.handshake.sessionID) {
          pub.publish(socket.handshake.meetingID, JSON.stringify(['paper', cx, cy, sw, sh]));
          if(!sw && !sh) {
            redisAction.getViewBox(socket.handshake.meetingID, function(viewbox) {
              var viewbox = JSON.parse(viewbox);
              redisAction.setViewBox(socket.handshake.meetingID, JSON.stringify([cx, cy, viewbox[2], viewbox[3]]));
            });
          }
          else redisAction.setViewBox(socket.handshake.meetingID, JSON.stringify([cx, cy, sw, sh]));
        }
      });
    });
   
    /**
     * When a user is zooming
     * @param  {number} delta amount the mouse scroll has moved
     * @return {undefined} publish to Redis PubSub
     */
    socket.on('zoom', function(delta) {
      var meetingID = socket.handshake.meetingID;
      redisAction.getPresenterSessionID(meetingID, function(presenterID) {
        if(presenterID == socket.handshake.sessionID) {
          pub.publish(meetingID, JSON.stringify(['zoom', delta]));
        }
      });
    });
   
    /**
     * When a user finishes panning
     * @return {undefined} publish to Redis PubSub
     */
    socket.on('panStop', function() {
      var meetingID = socket.handshake.meetingID;
      redisAction.getPresenterSessionID(meetingID, function(presenterID) {
        if(presenterID == socket.handshake.sessionID) {
          pub.publish(meetingID, JSON.stringify(['panStop']));
        }
      });
    });
    
    /**
     * Undoing the last shape
     * @return {undefined} publish to Redis PubSub
     */
    socket.on('undo', function() {
      var meetingID = socket.handshake.meetingID;
      redisAction.getPresenterSessionID(meetingID, function(presenterID) {
        if(presenterID == socket.handshake.sessionID) {
          redisAction.getCurrentPresentationID(meetingID, function(presentationID) {
            redisAction.getCurrentPageID(meetingID, presentationID, function(pageID) {
              //pop the last shape off the current list of shapes
              store.rpop(redisAction.getCurrentShapesString(meetingID, presentationID, pageID), function(err, reply) {
                socketAction.publishShapes(meetingID);
              });
            });
          });
        }
      });
    });
    
    /**
     * Telling everyone the current text has been finished
     * @return {undefined} publish to Redis PubSub
     */
    socket.on('textDone', function() {
      var meetingID = socket.handshake.meetingID;
      redisAction.getPresenterSessionID(meetingID, function(presenterID) {
        if(presenterID == socket.handshake.sessionID) {
          pub.publish(meetingID, JSON.stringify(['textDone']));
        }
      });
    });
   
    /**
     * Saving a shape to Redis. Does not provide feedback to client(s)
     * @param  {string} shape type of shape
     * @param  {Object} data  information needed to recreate shape
     * @return {undefined}    publish to Redis PubSub
     */
    socket.on('saveShape', function (shape, data) {
      var handshake = socket.handshake;
      var meetingID = handshake.meetingID;
      redisAction.getPresenterSessionID(meetingID, function(presenterID) {
        if(presenterID == handshake.sessionID) {
          redisAction.getCurrentPresentationID(meetingID, function(presentationID) {
            redisAction.getCurrentPageID(meetingID, presentationID, function(pageID) {
              var shapeID = rack(); //get a randomly generated id for the shape
              store.rpush(redisAction.getCurrentShapesString(meetingID, presentationID, pageID), shapeID);
              store.hmset(redisAction.getShapeString(meetingID, presentationID, pageID, shapeID),
                  'shape', shape, 'data', data, function(err, reply) {
              });
            });
          });
        }
      });
    });
  
    /**
     * Changing the currently set tool.
     * Set the current tool in Redis, then publish
     * the tool change to the members
     * @param  {string} tool  name of the tool to change to
     * @return {undefined}    publish to Redis PubSub
     */
    socket.on('changeTool', function (tool) {
       var handshake = socket.handshake;
       var meetingID = handshake.meetingID;
       redisAction.getPresenterSessionID(meetingID, function(presenterID) {
         if(presenterID == socket.handshake.sessionID) {
            redisAction.setCurrentTool(meetingID, tool, function(success) {
              if(success) {
                pub.publish(meetingID, JSON.stringify(['toolChanged', tool]));
              }
            });
         }
       });
    });
   
    /**
     * If a user requests all the shapes,
     * publish the shapes to everyone.
     * Only reason this happens is when its fit changes.
     * @return {undefined} publish to Redis PubSub
     */
    socket.on('all_shapes', function(){
      var handshake = socket.handshake;
      var meetingID = handshake.meetingID;
      var sessionID = handshake.sessionID;
      socketAction.publishShapes(meetingID);
    });
    
    /**
     * Updating the fit of the image to the whiteboard
     * @param  {boolean} fit true for fit to page and false for fit to width
     * @return {undefined}   publish to Redis PubSub
     */
    socket.on('fitToPage', function(fit) {
      var handshake = socket.handshake;
      var meetingID = handshake.meetingID;
      redisAction.getPresenterSessionID(meetingID, function(presenterID) {
         if(presenterID == sockt.handshake.sessionID) {
           pub.publish(meetingID, JSON.stringify(['fitToPage', fit]));
         }
       });
    });
  };
  