<commit_before>// Configuration
const config = require('./config');

// Core modules
var http = require('http');
var url = require('url');
var WebSocketServer = require('ws').Server;
// var SocketIo = require('socket.io');
var mongoose = require('./models/mongoose');
Q = require('q');
// Fix the promise issue in Mongoose
// mongoose.Promise = Q.Promise;
var Schema = mongoose.Schema;

var jwt = require('jsonwebtoken');
var uuid = require('node-uuid');

// Redis
var Redis = require('ioredis');

// Import models
var mongoose   = require('./models/mongoose');
const Activity = mongoose.ctrl_conn.model('Activity', require('./models/activity').ActivitySchema);
const Image    = mongoose.ctrl_conn.model('Image', require('./models/image').ImageSchema);
const Result   = mongoose.ctrl_conn.model('Result', require('./models/result').ResultSchema);

// Definitions of result types
var result_type_trans = {
    mx: {
      snap: ['mx:index+strategy'],
      sweep: ['mx:integrate'],
      merge: [],
      mr: [],
      sad: [],
      mad: []
    }
  };

// All the ws_connections
var ws_connections = {};

// Subscribe to redis updates
try {
  var sub = new Redis(config.redis_connection);
} catch (e) {
  console.error("Cannot connect to redis", config.redis_connection);
  throw e;
}

sub.on("message", function (channel, message) {

  console.log("sub channel " + channel);

  // Decode into oject
  let parsed_message = JSON.parse(message);

  // Grab out the session_id
  let session_id = false;
  try {
    session_id = parsed_message.process.session_id;
    console.log('Message session:', session_id);
  }
  catch (e) {
    if (e instanceof TypeError) {
      console.error('Result has no session_id');
    } else {
      console.error(e);
    }
  }

  // Turn message into messages to send to clients
  parse_message(channel, parsed_message)
  .then(function(messages_to_send){
    console.log('messages_to_send', messages_to_send);
    //
    // console.log('Will send', messages_to_send.length, 'messages');
    //
    // Look for websockets that are watching the same session
    if (session_id) {
      Object.keys(ws_connections).forEach(function(socket_id) {
        console.log(ws_connections[socket_id].session);
        if (ws_connections[socket_id].session.session_id === session_id) {
          console.log('Have a live one!');
          messages_to_send.forEach(function(message) {
            console.log(message);
            ws_connections[socket_id].send(JSON.stringify({msg_type:message[0],
                                                           results:message[1]}));
          });
        }
      });
    }
  })
});

// Subscribe to updates
sub.subscribe("RAPD_RESULTS");

parse_message = function(channel, message) {
  console.log('parse_message');

  var deferred = Q.defer();

  // Array to return
  var return_array = [];

  switch (channel) {

    case 'RAPD_RESULTS':

      console.log('RAPD_RESULTS');

      // Do nothing for ECHO
      if (message.command === 'ECHO') {
        console.log('Echo...');
        deferred.resolve(return_array);
      // Do something for not ECHO
      } else {
        // Create a result
        let result = { _id: message.process.result_id,
            data_type: message.plugin.data_type.toLowerCase(),
            parent_id: message.process.parent_id,
            plugin_id: message.plugin.id,
            plugin_type: message.plugin.type.toLowerCase(),
            plugin_version: message.plugin.version,
            projects: [],
            repr: message.process.repr,
            result_id: message._id,
            session_id: message.process.session_id,
            status: message.process.status,
            timestamp: new Date().toISOString()
            };
        return_array.push(['results', [result]]);
        console.log('  Pushed results object onto return array');

        // Create a detailed result

        // Go through possible images and add their details to the result
        let possible_images = ['image', 'image1', 'image2'];
        let index = 0;
        possible_images.forEach(function(image) {

          // The image key we are looking for
          let image_key = image+'_id';
          console.log('  Looking for', image_key);

          // Look for image_ids in process
          if (message.process[image_key]) {
            Image
              .findOne({_id:message.process[image_key]})
              .exec(function(error, image_result) {
                // console.log(image_key, error, result);
                if (error) {
                  console.error(error);
                } else if (image_result) {
                  console.log('Found image data for', image);
                  message[image] = image_result._doc;
                }
                // Done?
                index += 1;
                if (index == 3) {
                  return_array.push(['result_details', message]);
                  console.log('return_array now has length', return_array.length);
                  console.log('return_array', return_array);
                  deferred.resolve(return_array);
                }
              });
          } else {
            // Done?
            index += 1;
            if (index == 3) {
              return_array.push(['result_details', message]);
              console.log('return_array now has length', return_array.length);
              console.log('return_array', return_array);
              deferred.resolve(return_array);
            }
          }
        });
      }

      break;

    default:
      console.log('Don\'t know about this channel.');
      deferred.resolve(return_array);
      break;

  }

  return deferred.promise;
};

/*
 * Populate image data for detailed result
 *
 */
 populate_image= function (detailed_result, image_key) {

  // Create deferred
  var deferred = Q.defer();

  // If there is an image_key in result
  if (image_key in detailed_result._doc.process) {
    // Make sure that the value is not false or undefined
    if (detailed_result._doc.process[image_key]) {
      // Get the image
      Image.
      findOne({_id:detailed_result._doc.process[image_key]}).
      exec(function(err, image1) {
        if (err) {
          console.error(err);
          deferred.resolve(false);
        } else {
          deferred.resolve(image1);
        }
      });
    } else {
      deferred.resolve(false);
    }
  } else {
    deferred.resolve(false);
  }

  // Return promise
  return deferred.promise;
};

// The websocket code
function Wss (opt, callback) {

    // hide "new"
    if (!(this instanceof Wss)) {
      return new Wss(opt)
    }

    var wss = new WebSocketServer({ server: opt.server });

    wss.on('connection', function connection(ws) {

      console.log('Connected');

      // Create a session object
      ws.session = {};

      // Mark the ws and save to list
      ws.id = uuid.v1();
      ws_connections[ws.id] = ws;

      // Create a ping interval to keep websocket connection alive
      let ping_timer = setInterval(function() {
        ws.send('ping');
      }, 45000);

      // Websocket has closed
      ws.on('close', function() {

        console.log('websocket closed');

        // Cancel the ping interval
        clearInterval(ping_timer);

        // Remove the websocket from the storage objects
        delete ws_connections[ws.id];
      });

      ws.on('error', function (err) {
        if (err.code !== 'ECONNRESET') {
          // Ignore ECONNRESET and re throw anything else
          throw err
        }
      });

      // Message incoming from the client
      ws.on('message', function(message) {

        var data = JSON.parse(message);
        // console.log(data);

        // Initializing the websocket
        if (data.request_type === 'initialize' ) {
          jwt.verify(data.token, config.secret, function(err, decoded) {
            if (err) {
              console.error(err);
              ws.send(JSON.stringify({success:false,
                                      message:'Failed to authenticate token.'}));
            } else {

              let now = Date.now()/1000;
              // console.log(now, decoded.iat, decoded.exp, (decoded.exp-now)/(60));
              // The token is valid
              if (decoded.iat <= now && decoded.exp >= now) {
                // Add token to websocket session
                ws.session.token = decoded;
                // console.log(ws.session);
              }
            }
          });
        } else {

          // Guard against non-authorized connections
          if (ws.session === undefined || ws.session === {}) {
            return false;
          }

          switch (data.request_type) {

            // Set the session id for the connected websocket
            case 'set_session':
              console.log('Session set to '+data.session_id);
              this.session.session_id = data.session_id;
              break;

            case 'unset_session':
              this.session.session_id = undefined;
              break;

            // Get results
            case 'get_results':

              console.log('get_results');

              var data_type,
                  data_class;

              [data_type, data_class] = data.data_type.split(':');

              if (data_type === 'mx') {

                if (data_class === 'data') {

                  Result.
                    find({'session_id':mongoose.Types.ObjectId(data.session_id)}).
                    where('result_type').in(['mx:index', 'mx:integrate']).
                    // populate('children').
                    sort('-timestamp').
                    exec(function(err, results) {
                        if (err) {
                          return false;
                        }
                        console.log('Found', results.length, 'results');
                        // Send back over the websocket
                        ws.send(JSON.stringify({msg_type:'results',
                                                results:results}));
                    });
                } else if (data_class === 'snap') {

                  Result.
                    find({'session_id':mongoose.Types.ObjectId(data.session_id)}).
                    where('result_type').in(result_type_trans[data_type][data_class]).
                    // populate('children').
                    sort('-timestamp').
                    exec(function(err, results) {
                        if (err) {
                          return false;
                        }
                        console.log('Found', results.length, 'results');
                        // Send back over the websocket
                        ws.send(JSON.stringify({msg_type:'results',
                                                results:results}));
                    });
                } else if (data_class === 'sweep') {

                  Result.
                    find({'session_id':mongoose.Types.ObjectId(data.session_id)}).
                    where('result_type').in(result_type_trans[data_type][data_class]).
                    // populate('children').
                    sort('-timestamp').
                    exec(function(err, sessions) {
                      if (err) {
                        return false;
                      }
                      console.log('Found', sessions.length, 'results');
                      // Send back over the websocket
                      ws.send(JSON.stringify({msg_type:'results',
                                              results:sessions}));
                    });
                } else if (data_class === 'all') {

                  console.log('data.session_id', data.session_id);

                  Result.
                    find({'session_id':mongoose.Types.ObjectId(data.session_id)}).
                    // populate('children').
                    sort('-timestamp').
                    exec(function(err, results) {
                        if (err)
                            return false;
                        console.log('Found', results.length, 'results');
                        // Send back over the websocket
                        ws.send(JSON.stringify({msg_type:'results',
                                                results:results}));
                    });
                }

              }

              // Register activity
              let new_activity = new Activity({
                source:'websocket',
                type:'get_results',
                subtype:data.data_type+'_'+ data.plugin_type,
                user:ws.session.token._doc._id
              }).save();

              break;

            case 'update_result':

              console.log('update_result');
              console.log(data.result);

              Result.
                update({_id:data.result._id}, data.result).
                exec(function(err, res) {
                  console.log(err);
                  console.log(res);
                });

              break;

            // Get result details
            case 'get_result_details':

              console.log('get_result_details', data);

              // Create a mongoose model for the result
              let name = data.data_type+'_'+ data.plugin_type +'_result';
              let collection_name = name.charAt(0).toUpperCase() + name.slice(1);
              var ResultModel;
              try {
                if (mongoose.ctrl_conn.model(collection_name)) {
                  ResultModel = mongoose.ctrl_conn.model(collection_name);
                }
              } catch(e) {
                if (e.name === 'MissingSchemaError') {
                  let schema = new mongoose.Schema({
                    _id: {
                      type: Schema.ObjectId,
                      auto: true
                      },
                    }, {strict:false});
                  // ResultModel = mongoose.model(collection_name, schema);
                  ResultModel = mongoose.ctrl_conn.model(collection_name, schema);
                }
              }

              // Now get the result
              ResultModel.
                findOne({'process.result_id':mongoose.Types.ObjectId(data._id)}).
                exec(function(err, detailed_result) {
                  // Error
                  if (err) {
                    console.error(err);
                    ws.send(JSON.stringify({msg_type:'result_details',
                                            success:false,
                                            results:err}));
                    return false;
                  // No error
                  } else {
                    // console.log(detailed_result);
                    if (detailed_result) {
                      // console.log(Object.keys(detailed_result));
                      // console.log(detailed_result._doc);
                      // console.log(detailed_result._doc.process);

                        // Make sure there is a process
                        if ('process' in detailed_result._doc) {

                          Q.all([
                            // Image 1
                            populate_image(detailed_result, 'image1_id'),
                            // Image 2
                            populate_image(detailed_result, 'image2_id')
                          ])
                          .then(function(results) {
                            // Assign results to detailed results
                            detailed_result._doc.image1 = results[0];
                            detailed_result._doc.image2 = results[1];

                            // Send back
                            ws.send(JSON.stringify({msg_type:'result_details',
                                                    success:true,
                                                    results:detailed_result}));
                          });

                          // // If there is an image1_id
                          // if ('image1_id' in detailed_result._doc.process) {

                          //   // Manually populate
                          //   Image.
                          //     findOne({_id:detailed_result._doc.process.image1_id}).
                          //     exec(function(err, image1) {
                          //       if (err) {
                          //         console.error(err);
                          //         return false;
                          //       } else {
                          //         detailed_result._doc.image1 = image1;
                          //         // console.log('POPULATED image1');
                          //         // console.log(detailed_result);
                          //         // Now look for image2
                          //         if ('image2_id' in detailed_result._doc.process) {

                          //           // Manually populate
                          //           Image.
                          //             findOne({_id:detailed_result._doc.process.image1_id}).
                          //             exec(function(err, image2) {
                          //               if (err) {
                          //                 console.error(err);
                          //                 return false;
                          //               } else {
                          //                 detailed_result._doc.image1 = image2;
                          //                 // console.log('POPULATED image2');
                          //                 // console.log(detailed_result);
                          //                 // Send back over the websocket
                          //                 ws.send(JSON.stringify({msg_type:'result_details',
                          //                                         success:true,
                          //                                         results:detailed_result}));
                          //               }
                          //             });
                          //         } else {
                          //           // Send back over the websocket
                          //           ws.send(JSON.stringify({msg_type:'result_details',
                          //                                   success:true,
                          //                                   results:detailed_result}));
                          //         }
                          //       }
                          //     });
                          // } else {
                          //   // Send back over the websocket
                          //   ws.send(JSON.stringify({msg_type:'result_details',
                          //                           success:true,
                          //                           results:detailed_result}));
                          // }
                        // No 'process' in detailed_result._doc
                        } else {
                          // Send back over the websocket
                          ws.send(JSON.stringify({msg_type:'result_details',
                                                  success:true,
                                                  results:detailed_result}));
                        }
                    }

                    // Register activity
                    let new_activity = new Activity({
                      source:'websocket',
                      type:'get_result_details',
                      subtype:data.data_type+'_'+ data.plugin_type,
                      user:ws.session.token._doc._id
                    }).save();
                  }
                });
              break;
          }
        }
      });
    });
}

module.exports = Wss;
<commit_msg>Debugging<commit_after>// Configuration
const config = require('./config');

// Core modules
var http = require('http');
var url = require('url');
var WebSocketServer = require('ws').Server;
// var SocketIo = require('socket.io');
var mongoose = require('./models/mongoose');
Q = require('q');
// Fix the promise issue in Mongoose
// mongoose.Promise = Q.Promise;
var Schema = mongoose.Schema;

var jwt = require('jsonwebtoken');
var uuid = require('node-uuid');

// Redis
var Redis = require('ioredis');

// Import models
var mongoose   = require('./models/mongoose');
const Activity = mongoose.ctrl_conn.model('Activity', require('./models/activity').ActivitySchema);
const Image    = mongoose.ctrl_conn.model('Image', require('./models/image').ImageSchema);
const Result   = mongoose.ctrl_conn.model('Result', require('./models/result').ResultSchema);

// Definitions of result types
var result_type_trans = {
    mx: {
      snap: ['mx:index+strategy'],
      sweep: ['mx:integrate'],
      merge: [],
      mr: [],
      sad: [],
      mad: []
    }
  };

// All the ws_connections
var ws_connections = {};

// Subscribe to redis updates
try {
  var sub = new Redis(config.redis_connection);
} catch (e) {
  console.error("Cannot connect to redis", config.redis_connection);
  throw e;
}

sub.on("message", function (channel, message) {

  console.log("sub channel " + channel);

  // Decode into oject
  let parsed_message = JSON.parse(message);

  // Grab out the session_id
  let session_id = false;
  try {
    session_id = parsed_message.process.session_id;
    console.log('Message session:', session_id);
  }
  catch (e) {
    if (e instanceof TypeError) {
      console.error('Result has no session_id');
    } else {
      console.error(e);
    }
  }

  // Turn message into messages to send to clients
  parse_message(channel, parsed_message)
  .then(function(messages_to_send){
    console.log('messages_to_send', messages_to_send);
    //
    // console.log('Will send', messages_to_send.length, 'messages');
    //
    // Look for websockets that are watching the same session
    if (session_id) {
      Object.keys(ws_connections).forEach(function(socket_id) {
        console.log(ws_connections[socket_id].session);
        if (ws_connections[socket_id].session.session_id === session_id) {
          console.log('Have a live one!');
          messages_to_send.forEach(function(message) {
            console.log(message);
            ws_connections[socket_id].send(JSON.stringify({msg_type:message[0],
                                                           results:message[1]}));
          });
        }
      });
    }
  })
});

// Subscribe to updates
sub.subscribe("RAPD_RESULTS");

parse_message = function(channel, message) {
  console.log('parse_message');

  var deferred = Q.defer();

  // Array to return
  var return_array = [];

  switch (channel) {

    case 'RAPD_RESULTS':

      console.log('RAPD_RESULTS');

      // Do nothing for ECHO
      if (message.command === 'ECHO') {
        console.log('Echo...');
        deferred.resolve(return_array);
      // Do something for not ECHO
      } else {
        // Create a result
        let result = { _id: message.process.result_id,
            data_type: message.plugin.data_type.toLowerCase(),
            parent_id: message.process.parent_id,
            plugin_id: message.plugin.id,
            plugin_type: message.plugin.type.toLowerCase(),
            plugin_version: message.plugin.version,
            projects: [],
            repr: message.process.repr,
            result_id: message._id,
            session_id: message.process.session_id,
            status: message.process.status,
            timestamp: new Date().toISOString()
            };
        return_array.push(['results', [result]]);
        console.log('  Pushed results object onto return array');

        // Create a detailed result

        // Go through possible images and add their details to the result
        let possible_images = ['image', 'image1', 'image2'];
        let index = 0;
        possible_images.forEach(function(image) {

          // The image key we are looking for
          let image_key = image+'_id';
          console.log('  Looking for', image_key);

          // Look for image_ids in process
          if (message.process[image_key]) {
            Image
              .findOne({_id:message.process[image_key]})
              .exec(function(error, image_result) {
                // console.log(image_key, error, result);
                if (error) {
                  console.error(error);
                } else if (image_result) {
                  console.log('Found image data for', image);
                  message[image] = image_result._doc;
                }
                // Done?
                index += 1;
                if (index == 3) {
                  return_array.push(['result_details', message]);
                  console.log('return_array now has length', return_array.length);
                  console.log('return_array', return_array);
                  deferred.resolve(return_array);
                }
              });
          } else {
            // Done?
            index += 1;
            if (index == 3) {
              return_array.push(['result_details', message]);
              console.log('return_array now has length', return_array.length);
              console.log('return_array', return_array);
              deferred.resolve(return_array);
            }
          }
        });
      }

      break;

    default:
      console.log('Don\'t know about this channel.');
      deferred.resolve(return_array);
      break;

  }

  return deferred.promise;
};

/*
 * Populate image data for detailed result
 *
 */
 populate_image = function (detailed_result, image_key) {

  // Create deferred
  var deferred = Q.defer();

  // If there is an image_key in result
  if (image_key in detailed_result._doc.process) {
    // Make sure that the value is not false or undefined
    if (detailed_result._doc.process[image_key]) {
      // Get the image
      Image.
      findOne({_id:detailed_result._doc.process[image_key]}).
      exec(function(err, image1) {
        if (err) {
          console.error(err);
          deferred.resolve(false);
        } else {
          deferred.resolve(image1);
        }
      });
    } else {
      deferred.resolve(false);
    }
  } else {
    deferred.resolve(false);
  }

  // Return promise
  return deferred.promise;
};

// The websocket code
function Wss (opt, callback) {

    // hide "new"
    if (!(this instanceof Wss)) {
      return new Wss(opt)
    }

    var wss = new WebSocketServer({ server: opt.server });

    wss.on('connection', function connection(ws) {

      console.log('Connected');

      // Create a session object
      ws.session = {};

      // Mark the ws and save to list
      ws.id = uuid.v1();
      ws_connections[ws.id] = ws;

      // Create a ping interval to keep websocket connection alive
      let ping_timer = setInterval(function() {
        ws.send('ping');
      }, 45000);

      // Websocket has closed
      ws.on('close', function() {

        console.log('websocket closed');

        // Cancel the ping interval
        clearInterval(ping_timer);

        // Remove the websocket from the storage objects
        delete ws_connections[ws.id];
      });

      ws.on('error', function (err) {
        if (err.code !== 'ECONNRESET') {
          // Ignore ECONNRESET and re throw anything else
          throw err
        }
      });

      // Message incoming from the client
      ws.on('message', function(message) {

        var data = JSON.parse(message);
        // console.log(data);

        // Initializing the websocket
        if (data.request_type === 'initialize' ) {
          jwt.verify(data.token, config.secret, function(err, decoded) {
            if (err) {
              console.error(err);
              ws.send(JSON.stringify({success:false,
                                      message:'Failed to authenticate token.'}));
            } else {

              let now = Date.now()/1000;
              // console.log(now, decoded.iat, decoded.exp, (decoded.exp-now)/(60));
              // The token is valid
              if (decoded.iat <= now && decoded.exp >= now) {
                // Add token to websocket session
                ws.session.token = decoded;
                // console.log(ws.session);
              }
            }
          });
        } else {

          // Guard against non-authorized connections
          if (ws.session === undefined || ws.session === {}) {
            return false;
          }

          switch (data.request_type) {

            // Set the session id for the connected websocket
            case 'set_session':
              console.log('Session set to '+data.session_id);
              this.session.session_id = data.session_id;
              break;

            case 'unset_session':
              this.session.session_id = undefined;
              break;

            // Get results
            case 'get_results':

              console.log('get_results');

              var data_type,
                  data_class;

              [data_type, data_class] = data.data_type.split(':');

              if (data_type === 'mx') {

                if (data_class === 'data') {

                  Result.
                    find({'session_id':mongoose.Types.ObjectId(data.session_id)}).
                    where('result_type').in(['mx:index', 'mx:integrate']).
                    // populate('children').
                    sort('-timestamp').
                    exec(function(err, results) {
                        if (err) {
                          return false;
                        }
                        console.log('Found', results.length, 'results');
                        // Send back over the websocket
                        ws.send(JSON.stringify({msg_type:'results',
                                                results:results}));
                    });
                } else if (data_class === 'snap') {

                  Result.
                    find({'session_id':mongoose.Types.ObjectId(data.session_id)}).
                    where('result_type').in(result_type_trans[data_type][data_class]).
                    // populate('children').
                    sort('-timestamp').
                    exec(function(err, results) {
                        if (err) {
                          return false;
                        }
                        console.log('Found', results.length, 'results');
                        // Send back over the websocket
                        ws.send(JSON.stringify({msg_type:'results',
                                                results:results}));
                    });
                } else if (data_class === 'sweep') {

                  Result.
                    find({'session_id':mongoose.Types.ObjectId(data.session_id)}).
                    where('result_type').in(result_type_trans[data_type][data_class]).
                    // populate('children').
                    sort('-timestamp').
                    exec(function(err, sessions) {
                      if (err) {
                        return false;
                      }
                      console.log('Found', sessions.length, 'results');
                      // Send back over the websocket
                      ws.send(JSON.stringify({msg_type:'results',
                                              results:sessions}));
                    });
                } else if (data_class === 'all') {

                  console.log('data.session_id', data.session_id);

                  Result.
                    find({'session_id':mongoose.Types.ObjectId(data.session_id)}).
                    // populate('children').
                    sort('-timestamp').
                    exec(function(err, results) {
                        if (err)
                            return false;
                        console.log('Found', results.length, 'results');
                        // Send back over the websocket
                        ws.send(JSON.stringify({msg_type:'results',
                                                results:results}));
                    });
                }

              }

              // Register activity
              let new_activity = new Activity({
                source:'websocket',
                type:'get_results',
                subtype:data.data_type+'_'+ data.plugin_type,
                user:ws.session.token._doc._id
              }).save();

              break;

            case 'update_result':

              console.log('update_result');
              console.log(data.result);

              Result.
                update({_id:data.result._id}, data.result).
                exec(function(err, res) {
                  console.log(err);
                  console.log(res);
                });

              break;

            // Get result details
            case 'get_result_details':

              console.log('get_result_details', data);

              // Create a mongoose model for the result
              let name = data.data_type+'_'+ data.plugin_type +'_result';
              let collection_name = name.charAt(0).toUpperCase() + name.slice(1);
              var ResultModel;
              try {
                if (mongoose.ctrl_conn.model(collection_name)) {
                  ResultModel = mongoose.ctrl_conn.model(collection_name);
                }
              } catch(e) {
                if (e.name === 'MissingSchemaError') {
                  let schema = new mongoose.Schema({
                    _id: {
                      type: Schema.ObjectId,
                      auto: true
                      },
                    }, {strict:false});
                  // ResultModel = mongoose.model(collection_name, schema);
                  ResultModel = mongoose.ctrl_conn.model(collection_name, schema);
                }
              }

              // Now get the result
              ResultModel.
                findOne({'process.result_id':mongoose.Types.ObjectId(data._id)}).
                exec(function(err, detailed_result) {
                  // Error
                  if (err) {
                    console.error(err);
                    ws.send(JSON.stringify({msg_type:'result_details',
                                            success:false,
                                            results:err}));
                    return false;
                  // No error
                  } else {
                    // console.log(detailed_result);
                    if (detailed_result) {
                      console.log(Object.keys(detailed_result));
                      console.log(detailed_result._doc);
                      console.log(detailed_result._doc.process);

                        // Make sure there is a process
                        if ('process' in detailed_result._doc) {

                          Q.all([
                            // Image 1
                            populate_image(detailed_result, 'image1_id'),
                            // Image 2
                            populate_image(detailed_result, 'image2_id')
                          ])
                          .then(function(results) {
                            // Assign results to detailed results
                            detailed_result._doc.image1 = results[0];
                            detailed_result._doc.image2 = results[1];

                            // Send back
                            ws.send(JSON.stringify({msg_type:'result_details',
                                                    success:true,
                                                    results:detailed_result}));
                          });

                          // // If there is an image1_id
                          // if ('image1_id' in detailed_result._doc.process) {

                          //   // Manually populate
                          //   Image.
                          //     findOne({_id:detailed_result._doc.process.image1_id}).
                          //     exec(function(err, image1) {
                          //       if (err) {
                          //         console.error(err);
                          //         return false;
                          //       } else {
                          //         detailed_result._doc.image1 = image1;
                          //         // console.log('POPULATED image1');
                          //         // console.log(detailed_result);
                          //         // Now look for image2
                          //         if ('image2_id' in detailed_result._doc.process) {

                          //           // Manually populate
                          //           Image.
                          //             findOne({_id:detailed_result._doc.process.image1_id}).
                          //             exec(function(err, image2) {
                          //               if (err) {
                          //                 console.error(err);
                          //                 return false;
                          //               } else {
                          //                 detailed_result._doc.image1 = image2;
                          //                 // console.log('POPULATED image2');
                          //                 // console.log(detailed_result);
                          //                 // Send back over the websocket
                          //                 ws.send(JSON.stringify({msg_type:'result_details',
                          //                                         success:true,
                          //                                         results:detailed_result}));
                          //               }
                          //             });
                          //         } else {
                          //           // Send back over the websocket
                          //           ws.send(JSON.stringify({msg_type:'result_details',
                          //                                   success:true,
                          //                                   results:detailed_result}));
                          //         }
                          //       }
                          //     });
                          // } else {
                          //   // Send back over the websocket
                          //   ws.send(JSON.stringify({msg_type:'result_details',
                          //                           success:true,
                          //                           results:detailed_result}));
                          // }
                        // No 'process' in detailed_result._doc
                        } else {
                          // Send back over the websocket
                          ws.send(JSON.stringify({msg_type:'result_details',
                                                  success:true,
                                                  results:detailed_result}));
                        }
                    }

                    // Register activity
                    let new_activity = new Activity({
                      source:'websocket',
                      type:'get_result_details',
                      subtype:data.data_type+'_'+ data.plugin_type,
                      user:ws.session.token._doc._id
                    }).save();
                  }
                });
              break;
          }
        }
      });
    });
}

module.exports = Wss;
<|endoftext|>


<commit_before>'use strict';

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };

;(function () {

  var vueTouchFeedback = {};
  var STATE_HOVER = 'is-touch';
  var STATE_CLICK = 'is-click';

  /*
   * 初期設定 config
   */
  var conf = {
    isIE: window.navigator.userAgent.toLowerCase().indexOf('msie') !== -1,
    isTouch: typeof document.ontouchstart !== 'undefined',
    isPointer: typeof window.navigator.pointerEnabled !== 'undefined',
    isMSPoniter: typeof window.navigator.msPointerEnabled !== 'undefined',
    // touch
    touch: {
      enter: 'touchstart',
      leave: 'touchend',
      start: 'touchstart',
      move: 'touchmove',
      end: 'touchend'
    },
    // pointer
    pointer: {
      enter: 'pointerenter',
      leave: 'pointerleave',
      start: 'pointerdown',
      move: 'pointermove',
      end: 'pointerup'
    },
    // pointer
    msPointer: {
      enter: 'MSPointerOver',
      leave: 'MSPointerOut',
      start: 'MSPointerDown',
      move: 'MSPointerMove',
      end: 'MSPointerUp'
    },
    // mouse
    mouse: {
      enter: 'mouseenter',
      leave: 'mouseleave',
      start: 'mousedown',
      move: 'mousemove',
      end: 'mouseup'
    }
  };

  var eventPointer = {
    enter: conf.isTouch ? conf.touch.enter : conf.isPointer ? conf.pointer.enter : conf.isMSPointer ? conf.msPointer.enter : conf.mouse.enter,
    leave: conf.isTouch ? conf.touch.leave : conf.isPointer ? conf.pointer.leave : conf.isMSPointer ? conf.msPointer.leave : conf.mouse.leave,
    start: conf.isTouch ? conf.touch.start : conf.isPointer ? conf.pointer.start : conf.isMSPointer ? conf.msPointer.start : conf.mouse.start,
    move: conf.isTouch ? conf.touch.move : conf.isPointer ? conf.pointer.move : conf.isMSPointer ? conf.msPointer.move : conf.mouse.move,
    end: conf.isTouch ? conf.touch.end : conf.isPointer ? conf.pointer.end : conf.isMSPointer ? conf.msPointer.end : conf.mouse.end
  };

  vueTouchFeedback.install = function (Vue) {
    Vue.directive('touchfeedback', {
      priority: Vue.directive('on').priority,
      TAP: false,

      /**
       * タップ時に発火するイベント
       */
      touchAddClass: function touchAddClass(e) {
        this.classList.add(STATE_HOVER);
      },

      /**
       * タップイベントに対応していないデバイスで
       * クリック時に発火するイベント
       */
      tapedTouchStart: function tapedTouchStart(e) {
        this.TAP = true;
      },
      tapedTouchMove: function tapedTouchMove(e) {
        if (this.TAP) this.TAP = false;
      },
      tapedTouchEnd: function tapedTouchEnd(e) {
        var _this = this;

        if (!this.TAP) {
          return;
        }
        this.classList.add(STATE_CLICK);
        setTimeout(function () {
          _this.classList.remove(STATE_CLICK);
        }, 100);
      },
      tapedTouchEndAnimation: function tapedTouchEndAnimation(e) {
        if (!this.TAP) {
          return;
        }
        this.classList.add(STATE_CLICK);
      },

      // remove
      removeHover: function removeHover() {
        this.classList.remove(STATE_HOVER);
      },
      removeClick: function removeClick() {
        this.classList.remove(STATE_CLICK);
      },
      removeAll: function removeAll() {
        if (this.el.classList.contains(STATE_HOVER)) {
          this.el.classList.remove(STATE_HOVER);
        }
        if (this.el.classList.contains(STATE_CLICK)) {
          this.el.classList.remove(STATE_CLICK);
        }
      },
      bind: function bind() {
        if (this.arg && this.arg !== 'animation') {
          console.warn('[vue-touchfeedback] arg error');
        }

        if (this.arg === 'animation') {
          if (this.modifiers.hover) {
            this.el.addEventListener(eventPointer.enter, this.touchAddClass, false);
            this.el.addEventListener('webkitAnimationEnd', this.removeHover, false);
            this.el.addEventListener('MSAnimationEnd', this.removeHover, false);
            this.el.addEventListener('oanimationend', this.removeHover, false);
            this.el.addEventListener('animationend', this.removeHover, false);
          }
          if (this.modifiers.click) {
            this.el.addEventListener(eventPointer.start, this.tapedTouchStart, false);
            this.el.addEventListener(eventPointer.move, this.tapedTouchMove, false);
            this.el.addEventListener(eventPointer.end, this.tapedTouchEndAnimation, false);
            this.el.addEventListener('webkitAnimationEnd', this.removeClick, false);
            this.el.addEventListener('MSAnimationEnd', this.removeClick, false);
            this.el.addEventListener('oanimationend', this.removeClick, false);
            this.el.addEventListener('animationend', this.removeClick, false);
          }
        } else {
          // Hover
          this.el.addEventListener(eventPointer.enter, this.touchAddClass, false);
          this.el.addEventListener(eventPointer.leave, this.removeHover, false);
          // Click
          this.el.addEventListener(eventPointer.start, this.tapedTouchStart, false);
          this.el.addEventListener(eventPointer.move, this.tapedTouchMove, false);
          this.el.addEventListener(eventPointer.end, this.tapedTouchEnd, false);
        }
      },
      unbind: function unbind() {
        this.removeAll();
        if (this.arg === 'animation') {
          if (this.modifiers.hover) {
            this.el.removeEventListener(eventPointer.enter, this.touchAddClass, false);
            this.el.removeEventListener('webkitAnimationEnd', this.removeHover, false);
            this.el.removeEventListener('MSAnimationEnd', this.removeHover, false);
            this.el.removeEventListener('oanimationend', this.removeHover, false);
            this.el.removeEventListener('animationend', this.removeHover, false);
          }
          if (this.modifiers.click) {
            this.el.removeEventListener(eventPointer.start, this.tapedTouchStart, false);
            this.el.removeEventListener(eventPointer.move, this.tapedTouchMove, false);
            this.el.removeEventListener(eventPointer.end, this.tapedTouchEndAnimation, false);
            this.el.removeEventListener('webkitAnimationEnd', this.removeClick, false);
            this.el.removeEventListener('MSAnimationEnd', this.removeClick, false);
            this.el.removeEventListener('oanimationend', this.removeClick, false);
            this.el.removeEventListener('animationend', this.removeClick, false);
          }
        } else {
          // Hover
          this.el.removeEventListener(eventPointer.enter, this.touchAddClass, false);
          this.el.removeEventListener(eventPointer.leave, this.removeHover, false);
          // Click
          this.el.removeEventListener(eventPointer.start, this.tapedTouchStart, false);
          this.el.removeEventListener(eventPointer.move, this.tapedTouchMove, false);
          this.el.removeEventListener(eventPointer.end, this.tapedTouchEnd, false);
        }
      }
    });
  };

  if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) == 'object') {
    module.exports = vueTouchFeedback;
  } else if (typeof define == 'function' && define.amd) {
    define([], function () {
      return vueTouchFeedback;
    });
  } else if (window.Vue) {
    window.VueTouchFeedback = vueTouchFeedback;
    Vue.use(vueTouchFeedback);
  }
})();
<commit_msg>add license comments<commit_after>'use strict';

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };

/*!
 * vue-touchfeedback.js
 * (c) 2016 tmiame
 * Released under the MIT License.
 */
;(function () {

  var vueTouchFeedback = {};
  var STATE_HOVER = 'is-touch';
  var STATE_CLICK = 'is-click';

  /*
   * 初期設定 config
   */
  var conf = {
    isIE: window.navigator.userAgent.toLowerCase().indexOf('msie') !== -1,
    isTouch: typeof document.ontouchstart !== 'undefined',
    isPointer: typeof window.navigator.pointerEnabled !== 'undefined',
    isMSPoniter: typeof window.navigator.msPointerEnabled !== 'undefined',
    // touch
    touch: {
      enter: 'touchstart',
      leave: 'touchend',
      start: 'touchstart',
      move: 'touchmove',
      end: 'touchend'
    },
    // pointer
    pointer: {
      enter: 'pointerenter',
      leave: 'pointerleave',
      start: 'pointerdown',
      move: 'pointermove',
      end: 'pointerup'
    },
    // pointer
    msPointer: {
      enter: 'MSPointerOver',
      leave: 'MSPointerOut',
      start: 'MSPointerDown',
      move: 'MSPointerMove',
      end: 'MSPointerUp'
    },
    // mouse
    mouse: {
      enter: 'mouseenter',
      leave: 'mouseleave',
      start: 'mousedown',
      move: 'mousemove',
      end: 'mouseup'
    }
  };

  var eventPointer = {
    enter: conf.isTouch ? conf.touch.enter : conf.isPointer ? conf.pointer.enter : conf.isMSPointer ? conf.msPointer.enter : conf.mouse.enter,
    leave: conf.isTouch ? conf.touch.leave : conf.isPointer ? conf.pointer.leave : conf.isMSPointer ? conf.msPointer.leave : conf.mouse.leave,
    start: conf.isTouch ? conf.touch.start : conf.isPointer ? conf.pointer.start : conf.isMSPointer ? conf.msPointer.start : conf.mouse.start,
    move: conf.isTouch ? conf.touch.move : conf.isPointer ? conf.pointer.move : conf.isMSPointer ? conf.msPointer.move : conf.mouse.move,
    end: conf.isTouch ? conf.touch.end : conf.isPointer ? conf.pointer.end : conf.isMSPointer ? conf.msPointer.end : conf.mouse.end
  };

  vueTouchFeedback.install = function (Vue) {
    Vue.directive('touchfeedback', {
      priority: Vue.directive('on').priority,
      TAP: false,

      /**
       * タップ時に発火するイベント
       */
      touchAddClass: function touchAddClass(e) {
        this.classList.add(STATE_HOVER);
      },

      /**
       * タップイベントに対応していないデバイスで
       * クリック時に発火するイベント
       */
      tapedTouchStart: function tapedTouchStart(e) {
        this.TAP = true;
      },
      tapedTouchMove: function tapedTouchMove(e) {
        if (this.TAP) this.TAP = false;
      },
      tapedTouchEnd: function tapedTouchEnd(e) {
        var _this = this;

        if (!this.TAP) {
          return;
        }
        this.classList.add(STATE_CLICK);
        setTimeout(function () {
          _this.classList.remove(STATE_CLICK);
        }, 100);
      },
      tapedTouchEndAnimation: function tapedTouchEndAnimation(e) {
        if (!this.TAP) {
          return;
        }
        this.classList.add(STATE_CLICK);
      },

      // remove
      removeHover: function removeHover() {
        this.classList.remove(STATE_HOVER);
      },
      removeClick: function removeClick() {
        this.classList.remove(STATE_CLICK);
      },
      removeAll: function removeAll() {
        if (this.el.classList.contains(STATE_HOVER)) {
          this.el.classList.remove(STATE_HOVER);
        }
        if (this.el.classList.contains(STATE_CLICK)) {
          this.el.classList.remove(STATE_CLICK);
        }
      },
      bind: function bind() {
        if (this.arg && this.arg !== 'animation') {
          console.warn('[vue-touchfeedback] arg error');
        }

        if (this.arg === 'animation') {
          if (this.modifiers.hover) {
            this.el.addEventListener(eventPointer.enter, this.touchAddClass, false);
            this.el.addEventListener('webkitAnimationEnd', this.removeHover, false);
            this.el.addEventListener('MSAnimationEnd', this.removeHover, false);
            this.el.addEventListener('oanimationend', this.removeHover, false);
            this.el.addEventListener('animationend', this.removeHover, false);
          }
          if (this.modifiers.click) {
            this.el.addEventListener(eventPointer.start, this.tapedTouchStart, false);
            this.el.addEventListener(eventPointer.move, this.tapedTouchMove, false);
            this.el.addEventListener(eventPointer.end, this.tapedTouchEndAnimation, false);
            this.el.addEventListener('webkitAnimationEnd', this.removeClick, false);
            this.el.addEventListener('MSAnimationEnd', this.removeClick, false);
            this.el.addEventListener('oanimationend', this.removeClick, false);
            this.el.addEventListener('animationend', this.removeClick, false);
          }
        } else {
          // Hover
          this.el.addEventListener(eventPointer.enter, this.touchAddClass, false);
          this.el.addEventListener(eventPointer.leave, this.removeHover, false);
          // Click
          this.el.addEventListener(eventPointer.start, this.tapedTouchStart, false);
          this.el.addEventListener(eventPointer.move, this.tapedTouchMove, false);
          this.el.addEventListener(eventPointer.end, this.tapedTouchEnd, false);
        }
      },
      unbind: function unbind() {
        this.removeAll();
        if (this.arg === 'animation') {
          if (this.modifiers.hover) {
            this.el.removeEventListener(eventPointer.enter, this.touchAddClass, false);
            this.el.removeEventListener('webkitAnimationEnd', this.removeHover, false);
            this.el.removeEventListener('MSAnimationEnd', this.removeHover, false);
            this.el.removeEventListener('oanimationend', this.removeHover, false);
            this.el.removeEventListener('animationend', this.removeHover, false);
          }
          if (this.modifiers.click) {
            this.el.removeEventListener(eventPointer.start, this.tapedTouchStart, false);
            this.el.removeEventListener(eventPointer.move, this.tapedTouchMove, false);
            this.el.removeEventListener(eventPointer.end, this.tapedTouchEndAnimation, false);
            this.el.removeEventListener('webkitAnimationEnd', this.removeClick, false);
            this.el.removeEventListener('MSAnimationEnd', this.removeClick, false);
            this.el.removeEventListener('oanimationend', this.removeClick, false);
            this.el.removeEventListener('animationend', this.removeClick, false);
          }
        } else {
          // Hover
          this.el.removeEventListener(eventPointer.enter, this.touchAddClass, false);
          this.el.removeEventListener(eventPointer.leave, this.removeHover, false);
          // Click
          this.el.removeEventListener(eventPointer.start, this.tapedTouchStart, false);
          this.el.removeEventListener(eventPointer.move, this.tapedTouchMove, false);
          this.el.removeEventListener(eventPointer.end, this.tapedTouchEnd, false);
        }
      }
    });
  };

  if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) == 'object') {
    module.exports = vueTouchFeedback;
  } else if (typeof define == 'function' && define.amd) {
    define([], function () {
      return vueTouchFeedback;
    });
  } else if (window.Vue) {
    window.VueTouchFeedback = vueTouchFeedback;
    Vue.use(vueTouchFeedback);
  }
})();
<|endoftext|>


<commit_before>/*
 *  Copyright (c) Codiad & Kent Safranski (codiad.com), distributed
 *  as-is and without warranty under the MIT License. See
 *  [root]/license.txt for more. This information must remain intact.
 */

(function(global, $) {

    var EditSession = require('ace/edit_session')
        .EditSession;
    var UndoManager = require('ace/undomanager')
        .UndoManager;

    var codiad = global.codiad;

    $(function() {
        codiad.active.init();
    });

    //////////////////////////////////////////////////////////////////
    //
    // Active Files Component for Codiad
    // ---------------------------------
    // Track and manage EditSession instaces of files being edited.
    //
    //////////////////////////////////////////////////////////////////

    codiad.active = {

        controller: 'components/active/controller.php',

        // Path to EditSession instance mapping
        sessions: {},

        //////////////////////////////////////////////////////////////////
        //
        // Check if a file is open.
        //
        // Parameters:
        //   path - {String}
        //
        //////////////////////////////////////////////////////////////////

        isOpen: function(path) {
            return !!this.sessions[path];
        },

        open: function(path, content, inBackground) {
            var _this = this;
            if (this.isOpen(path)) {
                this.focus(path);
                return;
            }
            var ext = codiad.filemanager.getExtension(path);
            var mode = codiad.editor.selectMode(ext);
            var _this = this;

            var fn = function() {
                //var Mode = require('ace/mode/' + mode)
                //    .Mode;

                // TODO: Ask for user confirmation before recovering
                // And maybe show a diff
                var draft = _this.checkDraft(path);
                if (draft) {
                    content = draft;
                    codiad.message.success('Recovered unsaved content for : ' + path);
                }

                //var session = new EditSession(content, new Mode());
                var session = new EditSession(content);
                session.setMode("ace/mode/" + mode);
                session.setUndoManager(new UndoManager());

                session.path = path;
                _this.sessions[path] = session;
                if (!inBackground) {
                    codiad.editor.setSession(session);
                }
                _this.add(path, session);
            };

            // Assuming the mode file has no dependencies
            $.loadScript('components/editor/ace-editor/mode-' + mode + '.js',
            fn);

        },

        init: function() {

            var _this = this;

            _this.createTabDropdownMenu();

            // Focus
            $('#tab-dropdown-menu a')
                .live('click', function() {
                _this.focus($(this)
                    .attr('data-path'));
            });

            // Tab Focus
            $('.tab-list li.tab-item>a.label')
                .live('mousedown', function() {
                _this.focus($(this).parent('li').attr('data-path'));
            });

            // Remove
            $('#tab-dropdown-menu a>span')
                .live('click', function(e) {
                e.stopPropagation();
                /* Get the active editor before removing anything. Remove the
                 * tab, then put back the focus on the previously active
                 * editor if it was not removed. */
                var activePath = _this.getPath();
                var pathToRemove = $(this).parent('a').attr('data-path');
                _this.remove(pathToRemove);
                if (activePath !== null && activePath !== pathToRemove) {
                    _this.focus(activePath);
                }
            });

            // Tab Remove
            $('.tab-list a.close')
                .live('click', function(e) {
                e.stopPropagation();
                /* Get the active editor before removing anything. Remove the
                 * tab, then put back the focus on the previously active
                 * editor if it was not removed. */
                var activePath = _this.getPath();
                var pathToRemove = $(this).parent('li').attr('data-path');
                _this.remove(pathToRemove);
                if (activePath !== null && activePath !== pathToRemove) {
                    _this.focus(activePath);
                }
            });

            // Sortable
            $('#tab-dropdown-menu')
                .sortable({
                placeholder: 'active-sort-placeholder',
                tolerance: 'intersect',
                start: function(e, ui) {
                    ui.placeholder.height(ui.item.height());
                }
            });

            // Tab Sortable
            $('.tab-list')
                .sortable({
                items: '> li',
                axis: 'x',
                tolerance: 'pointer',
                containment: 'parent',
                start: function(e, ui) {
                    ui.placeholder.css('background', 'transparent');
                    ui.helper.css('width', '200px');
                },
                stop: function(e, ui) {
                    // Reset css
                    ui.item.css('z-index', '')
                    ui.item.css('position', '')
                }
            });
            /* Woaw, so tricky! At initialization, the .tab-list is empty, so
             * it is not marked as float so it is not detected as an horizontal
             * list by the sortable plugin. Workaround is to mark it as
             * floating at initialization time. See bug report
             * http://bugs.jqueryui.com/ticket/6702. */
            $('.tab-list').data('sortable').floating = true;

            // Manage the dropdown menu for tab
            $(window).resize(function() {

                if(_this.isTabListOverflowed()) {
                    var tab = $('.tab-list li:last-child');
                    if(tab.length == 1) _this.moveTabToDropdownMenu(tab);
                }
                else
                {
                    if(!_this.isTabListOverflowed(true))
                    {
                        var menu = $('#tab-dropdown-menu li:first-child');
                        if(menu.length == 1) _this.moveDropdownMenuToTab(menu);
                    }
                }

                if($('#tab-dropdown-menu li').length > 0)
                {
                    $('#tab-dropdown').show();
                }
                else
                {
                    $('#tab-dropdown').hide();
                }
            });

            // Open saved-state active files on load
            $.get(_this.controller + '?action=list', function(data) {
                var listResponse = codiad.jsend.parse(data);
                if (listResponse !== null) {
                    $.each(listResponse, function(index, value) {
                        codiad.filemanager.openFile(value);
                    });
                    // Run resize command to fix render issues
                    codiad.editor.resize();
                }
            });

            // Run resize on window resize
            $(window)
                .on('resize', function() {
                codiad.editor.resize();
            });

            // Prompt if a user tries to close window without saving all filess
            window.onbeforeunload = function(e) {
                if ($('#active-files a.changed')
                    .length > 0) {
                    var e = e || window.event;
                    var errMsg = 'You have unsaved files.';

                    // For IE and Firefox prior to version 4
                    if (e) {
                        e.returnValue = errMsg;
                    }

                    // For rest
                    return errMsg;
                }
            };
        },

        //////////////////////////////////////////////////////////////////
        // Drafts
        //////////////////////////////////////////////////////////////////

        checkDraft: function(path) {
            var draft = localStorage.getItem(path);
            if (draft !== null) {
                return draft;
            } else {
                return false;
            }
        },

        removeDraft: function(path) {
            localStorage.removeItem(path);
        },

        //////////////////////////////////////////////////////////////////
        // Get active editor path
        //////////////////////////////////////////////////////////////////

        getPath: function() {
            try {
                return codiad.editor.getActive()
                    .getSession()
                    .path;
            } catch (e) {
                return null;
            }
        },

        //////////////////////////////////////////////////////////////////
        // Check if opened by another user
        //////////////////////////////////////////////////////////////////

        check: function(path) {
            $.get(this.controller + '?action=check&path=' + path,

            function(data) {
                var checkResponse = codiad.jsend.parse(data);
            });
        },

        //////////////////////////////////////////////////////////////////
        // Add newly opened file to list
        //////////////////////////////////////////////////////////////////

        add: function(path, session) {
            if(!this.isTabListOverflowed(true))
            {
                var thumb = this.createTabThumb(path);
                $('.tab-list').append(thumb);
                session.thumb = thumb;
            }
            else
            {
                var thumb = this.createMenuThumb(path);
                $('#tab-dropdown-menu').append(thumb);
                session.thumb = thumb;
            }

            $.get(this.controller + '?action=add&path=' + path);

            this.focus(path);
            // Mark draft as changed
            if (this.checkDraft(path)) {
                this.markChanged(path);
            }
        },

        //////////////////////////////////////////////////////////////////
        // Focus on opened file
        //////////////////////////////////////////////////////////////////

        focus: function(path) {
            this.highlightEntry(path);
            var session = this.sessions[path];
            codiad.editor.setSession(session);
            this.check(path);
        },

        highlightEntry: function(path) {
            $('.tab-list li')
                .removeClass('active');
            this.sessions[path].thumb.addClass('active');
        },

        //////////////////////////////////////////////////////////////////
        // Mark changed
        //////////////////////////////////////////////////////////////////

        markChanged: function(path) {
            this.sessions[path].thumb.addClass('changed');
        },

        //////////////////////////////////////////////////////////////////
        // Save active editor
        //////////////////////////////////////////////////////////////////

        save: function(path) {
            var _this = this;
            if ((path && !this.isOpen(path)) || (!path && !codiad.editor.getActive())) {
                codiad.message.error('No Open Files to save');
                return;
            }
            var session;
            if (path) session = this.sessions[path];
            else session = codiad.editor.getActive()
                .getSession();
            var content = session.getValue();
            var path = session.path;
            codiad.filemanager.saveFile(path, content, {
                success: function() {
                    session.thumb.removeClass('changed');
                    _this.removeDraft(path);
                }
            });
        },

        //////////////////////////////////////////////////////////////////
        // Remove file
        //////////////////////////////////////////////////////////////////

        remove: function(path) {
            if (!this.isOpen(path)) return;
            var session = this.sessions[path];
            var closeFile = true;
            if (session.thumb.hasClass('changed')) {
                codiad.modal.load(450, 'components/active/dialog.php?action=confirm&path=' + path);
                closeFile = false;
            }
            if (closeFile) {
                this.close(path);
            }
        },

        close: function(path) {
            var session = this.sessions[path];
            session.thumb.remove();
            var nextThumb = $('.tab-list li[data-path]');
            if (nextThumb.length == 0) {
                codiad.editor.exterminate();
            } else {
                $(nextThumb[0])
                    .addClass('active');
                var nextPath = nextThumb.attr('data-path');
                var nextSession = this.sessions[nextPath];
                codiad.editor.removeSession(session, nextSession);
            }
            delete this.sessions[path];
            $.get(this.controller + '?action=remove&path=' + path);
            this.removeDraft(path);
        },

        //////////////////////////////////////////////////////////////////
        // Process rename
        //////////////////////////////////////////////////////////////////

        rename: function(oldPath, newPath) {
            var switchSessions = function(oldPath, newPath) {
                var thumb = this.sessions[oldPath].thumb;
                thumb.attr('data-path', newPath);
                thumb.find('.label')
                    .text(newPath.substring(1));
                this.sessions[newPath] = this.sessions[oldPath];
                this.sessions[newPath].path = newPath;
                this.sessions[oldPath] = undefined;
            };
            if (this.sessions[oldPath]) {
                // A file was renamed
                switchSessions.apply(this, [oldPath, newPath]);
                // pass new sessions instance to setactive
                for (var k = 0; k < codiad.editor.instances.length; k++) {
                    if (codiad.editor.instances[k].getSession().path === newPath) {
                        codiad.editor.setActive(codiad.editor.instances[k]);
                    }
                }

                var newSession = this.sessions[newPath];

                // Change Editor Mode
                var ext = codiad.filemanager.getExtension(newPath);
                var mode = codiad.editor.selectMode(ext);

                // handle async mode change
                var fn = function(){
                   codiad.editor.setModeDisplay(newSession);
                   newSession.removeListener('changeMode', fn);
                }

                newSession.on("changeMode", fn);
                newSession.setMode("ace/mode/" + mode);

            } else {
                // A folder was renamed
                var newKey;
                for (var key in this.sessions) {
                    newKey = key.replace(oldPath, newPath);
                    if (newKey !== key) {
                        switchSessions.apply(this, [key, newKey]);
                    }
                }
            }
            $.get(this.controller + '?action=rename&old_path=' + oldPath + '&new_path=' + newPath);
        },

        //////////////////////////////////////////////////////////////////
        // Open in Browser
        //////////////////////////////////////////////////////////////////

        openInBrowser: function() {
            var path = this.getPath();
            if (path) {
                codiad.filemanager.openInBrowser(path);
            } else {
                codiad.message.error('No Open Files');
            }
        },

        //////////////////////////////////////////////////////////////////
        // Get Selected Text
        //////////////////////////////////////////////////////////////////

        getSelectedText: function() {
            var path = this.getPath();
            var session = this.sessions[path];

            if (path && this.isOpen(path)) {
                return session.getTextRange(
                codiad.editor.getActive()
                    .getSelectionRange());
            } else {
                codiad.message.error('No Open Files or Selected Text');
            }
        },

        //////////////////////////////////////////////////////////////////
        // Insert Text
        //////////////////////////////////////////////////////////////////

        insertText: function(val) {
            codiad.editor.getActive()
                .insert(val);
        },

        //////////////////////////////////////////////////////////////////
        // Goto Line
        //////////////////////////////////////////////////////////////////

        gotoLine: function(line) {
            codiad.editor.getActive()
                .gotoLine(line, 0, true);
        },

        //////////////////////////////////////////////////////////////////
        // Move Up (Key Combo)
        //////////////////////////////////////////////////////////////////

        move: function(dir) {

            var num = $('#active-files a')
                .length;
            if (num > 1) {
                if (dir == 'up') {
                    // Move Up or rotate to bottom
                    newActive = $('#active-files li a.active')
                        .parent('li')
                        .prev('li')
                        .children('a')
                        .attr('data-path');
                    if (!newActive) {
                        newActive = $('#active-files li:last-child a')
                            .attr('data-path');
                    }

                } else {
                    // Move down or rotate to top
                    newActive = $('#active-files li a.active')
                        .parent('li')
                        .next('li')
                        .children('a')
                        .attr('data-path');
                    if (!newActive) {
                        newActive = $('#active-files li:first-child a')
                            .attr('data-path');
                    }

                }

                this.focus(newActive);
            }

        },

        //////////////////////////////////////////////////////////////////
        // Dropdown Menu
        //////////////////////////////////////////////////////////////////

        createTabDropdownMenu: function(){
            var _this = this;
            var _tabMenu = $('#tab-dropdown-menu');

            console.log(codiad.dropdown);
            codiad.dropdown.initMenuHandler($('#tab-dropdown-button'),_tabMenu);
        },

        moveTabToDropdownMenu: function(tab){
            tab.remove();
            path = tab.attr('data-path');

            var thumb = this.createMenuThumb(path);
            $('#tab-dropdown-menu').append(thumb);
        },

        moveDropdownMenuToTab: function(menu){
            menu.remove();
            path = menu.attr('data-path');

            var thumb = this.createTabThumb(path);
            $('.tab-list').append(thumb);
        },

        isTabListOverflowed: function(includeFictiveTab){
            if(typeof includeFictiveTab == 'undefined'){
                includeFictiveTab = false;
            }

            var tab = $('.tab-list li:last-child');
            if(tab.length == 0) return false;

            var coef = 1;
            if(includeFictiveTab) coef = 2;

            return (tab.position().left + coef*tab.outerWidth() >= $('.tab-list').width() - 320);
        },

        //////////////////////////////////////////////////////////////////
        // Factory
        //////////////////////////////////////////////////////////////////

        createTabThumb: function(path) {
            return $('<li class="tab-item" data-path="'+path+'"><a class="label" title="'+path+'">' + path.substring(1) + '</a><a class="close">x</a></li>');
        },

        createMenuThumb: function(path) {
            return thumb = $('<li data-path="'+path+'"><a title="'+path+'"><span></span><div class="label">' + path.substring(1) + '</div></a></li>');
        },

    };

})(this, jQuery);
<commit_msg>Fix thumb member when switching active to tab or menu.<commit_after>/*
 *  Copyright (c) Codiad & Kent Safranski (codiad.com), distributed
 *  as-is and without warranty under the MIT License. See
 *  [root]/license.txt for more. This information must remain intact.
 */

(function(global, $) {

    var EditSession = require('ace/edit_session')
        .EditSession;
    var UndoManager = require('ace/undomanager')
        .UndoManager;

    var codiad = global.codiad;

    $(function() {
        codiad.active.init();
    });

    //////////////////////////////////////////////////////////////////
    //
    // Active Files Component for Codiad
    // ---------------------------------
    // Track and manage EditSession instaces of files being edited.
    //
    //////////////////////////////////////////////////////////////////

    codiad.active = {

        controller: 'components/active/controller.php',

        // Path to EditSession instance mapping
        sessions: {},

        //////////////////////////////////////////////////////////////////
        //
        // Check if a file is open.
        //
        // Parameters:
        //   path - {String}
        //
        //////////////////////////////////////////////////////////////////

        isOpen: function(path) {
            return !!this.sessions[path];
        },

        open: function(path, content, inBackground) {
            var _this = this;
            if (this.isOpen(path)) {
                this.focus(path);
                return;
            }
            var ext = codiad.filemanager.getExtension(path);
            var mode = codiad.editor.selectMode(ext);
            var _this = this;

            var fn = function() {
                //var Mode = require('ace/mode/' + mode)
                //    .Mode;

                // TODO: Ask for user confirmation before recovering
                // And maybe show a diff
                var draft = _this.checkDraft(path);
                if (draft) {
                    content = draft;
                    codiad.message.success('Recovered unsaved content for : ' + path);
                }

                //var session = new EditSession(content, new Mode());
                var session = new EditSession(content);
                session.setMode("ace/mode/" + mode);
                session.setUndoManager(new UndoManager());

                session.path = path;
                _this.sessions[path] = session;
                if (!inBackground) {
                    codiad.editor.setSession(session);
                }
                _this.add(path, session);
            };

            // Assuming the mode file has no dependencies
            $.loadScript('components/editor/ace-editor/mode-' + mode + '.js',
            fn);

        },

        init: function() {

            var _this = this;

            _this.createTabDropdownMenu();

            // Focus
            $('#tab-dropdown-menu a')
                .live('click', function() {
                _this.focus($(this)
                    .attr('data-path'));
            });

            // Tab Focus
            $('.tab-list li.tab-item>a.label')
                .live('mousedown', function() {
                _this.focus($(this).parent('li').attr('data-path'));
            });

            // Remove
            $('#tab-dropdown-menu a>span')
                .live('click', function(e) {
                e.stopPropagation();
                /* Get the active editor before removing anything. Remove the
                 * tab, then put back the focus on the previously active
                 * editor if it was not removed. */
                var activePath = _this.getPath();
                var pathToRemove = $(this).parent('a').attr('data-path');
                _this.remove(pathToRemove);
                if (activePath !== null && activePath !== pathToRemove) {
                    _this.focus(activePath);
                }
            });

            // Tab Remove
            $('.tab-list a.close')
                .live('click', function(e) {
                e.stopPropagation();
                /* Get the active editor before removing anything. Remove the
                 * tab, then put back the focus on the previously active
                 * editor if it was not removed. */
                var activePath = _this.getPath();
                var pathToRemove = $(this).parent('li').attr('data-path');
                _this.remove(pathToRemove);
                if (activePath !== null && activePath !== pathToRemove) {
                    _this.focus(activePath);
                }
            });

            // Sortable
            $('#tab-dropdown-menu')
                .sortable({
                placeholder: 'active-sort-placeholder',
                tolerance: 'intersect',
                start: function(e, ui) {
                    ui.placeholder.height(ui.item.height());
                }
            });

            // Tab Sortable
            $('.tab-list')
                .sortable({
                items: '> li',
                axis: 'x',
                tolerance: 'pointer',
                containment: 'parent',
                start: function(e, ui) {
                    ui.placeholder.css('background', 'transparent');
                    ui.helper.css('width', '200px');
                },
                stop: function(e, ui) {
                    // Reset css
                    ui.item.css('z-index', '')
                    ui.item.css('position', '')
                }
            });
            /* Woaw, so tricky! At initialization, the .tab-list is empty, so
             * it is not marked as float so it is not detected as an horizontal
             * list by the sortable plugin. Workaround is to mark it as
             * floating at initialization time. See bug report
             * http://bugs.jqueryui.com/ticket/6702. */
            $('.tab-list').data('sortable').floating = true;

            // Manage the dropdown menu for tab
            $(window).resize(function() {

                if(_this.isTabListOverflowed()) {
                    var tab = $('.tab-list li:last-child');
                    if(tab.length == 1) _this.moveTabToDropdownMenu(tab);
                }
                else
                {
                    if(!_this.isTabListOverflowed(true))
                    {
                        var menu = $('#tab-dropdown-menu li:first-child');
                        if(menu.length == 1) _this.moveDropdownMenuToTab(menu);
                    }
                }

                if($('#tab-dropdown-menu li').length > 0)
                {
                    $('#tab-dropdown').show();
                }
                else
                {
                    $('#tab-dropdown').hide();
                }
            });

            // Open saved-state active files on load
            $.get(_this.controller + '?action=list', function(data) {
                var listResponse = codiad.jsend.parse(data);
                if (listResponse !== null) {
                    $.each(listResponse, function(index, value) {
                        codiad.filemanager.openFile(value);
                    });
                    // Run resize command to fix render issues
                    codiad.editor.resize();
                }
            });

            // Run resize on window resize
            $(window)
                .on('resize', function() {
                codiad.editor.resize();
            });

            // Prompt if a user tries to close window without saving all filess
            window.onbeforeunload = function(e) {
                if ($('#active-files a.changed')
                    .length > 0) {
                    var e = e || window.event;
                    var errMsg = 'You have unsaved files.';

                    // For IE and Firefox prior to version 4
                    if (e) {
                        e.returnValue = errMsg;
                    }

                    // For rest
                    return errMsg;
                }
            };
        },

        //////////////////////////////////////////////////////////////////
        // Drafts
        //////////////////////////////////////////////////////////////////

        checkDraft: function(path) {
            var draft = localStorage.getItem(path);
            if (draft !== null) {
                return draft;
            } else {
                return false;
            }
        },

        removeDraft: function(path) {
            localStorage.removeItem(path);
        },

        //////////////////////////////////////////////////////////////////
        // Get active editor path
        //////////////////////////////////////////////////////////////////

        getPath: function() {
            try {
                return codiad.editor.getActive()
                    .getSession()
                    .path;
            } catch (e) {
                return null;
            }
        },

        //////////////////////////////////////////////////////////////////
        // Check if opened by another user
        //////////////////////////////////////////////////////////////////

        check: function(path) {
            $.get(this.controller + '?action=check&path=' + path,

            function(data) {
                var checkResponse = codiad.jsend.parse(data);
            });
        },

        //////////////////////////////////////////////////////////////////
        // Add newly opened file to list
        //////////////////////////////////////////////////////////////////

        add: function(path, session) {
            if(!this.isTabListOverflowed(true))
            {
                var thumb = this.createTabThumb(path);
                $('.tab-list').append(thumb);
                session.thumb = thumb;
            }
            else
            {
                var thumb = this.createMenuThumb(path);
                $('#tab-dropdown-menu').append(thumb);
                session.thumb = thumb;
            }

            $.get(this.controller + '?action=add&path=' + path);

            this.focus(path);
            // Mark draft as changed
            if (this.checkDraft(path)) {
                this.markChanged(path);
            }
        },

        //////////////////////////////////////////////////////////////////
        // Focus on opened file
        //////////////////////////////////////////////////////////////////

        focus: function(path) {
            this.highlightEntry(path);
            var session = this.sessions[path];
            codiad.editor.setSession(session);
            this.check(path);
        },

        highlightEntry: function(path) {
            $('.tab-list li')
                .removeClass('active');
            this.sessions[path].thumb.addClass('active');
        },

        //////////////////////////////////////////////////////////////////
        // Mark changed
        //////////////////////////////////////////////////////////////////

        markChanged: function(path) {
            this.sessions[path].thumb.addClass('changed');
        },

        //////////////////////////////////////////////////////////////////
        // Save active editor
        //////////////////////////////////////////////////////////////////

        save: function(path) {
            var _this = this;
            if ((path && !this.isOpen(path)) || (!path && !codiad.editor.getActive())) {
                codiad.message.error('No Open Files to save');
                return;
            }
            var session;
            if (path) session = this.sessions[path];
            else session = codiad.editor.getActive()
                .getSession();
            var content = session.getValue();
            var path = session.path;
            codiad.filemanager.saveFile(path, content, {
                success: function() {
                    session.thumb.removeClass('changed');
                    _this.removeDraft(path);
                }
            });
        },

        //////////////////////////////////////////////////////////////////
        // Remove file
        //////////////////////////////////////////////////////////////////

        remove: function(path) {
            if (!this.isOpen(path)) return;
            var session = this.sessions[path];
            var closeFile = true;
            if (session.thumb.hasClass('changed')) {
                codiad.modal.load(450, 'components/active/dialog.php?action=confirm&path=' + path);
                closeFile = false;
            }
            if (closeFile) {
                this.close(path);
            }
        },

        close: function(path) {
            var session = this.sessions[path];
            session.thumb.remove();
            var nextThumb = $('.tab-list li[data-path]');
            if (nextThumb.length == 0) {
                codiad.editor.exterminate();
            } else {
                $(nextThumb[0])
                    .addClass('active');
                var nextPath = nextThumb.attr('data-path');
                var nextSession = this.sessions[nextPath];
                codiad.editor.removeSession(session, nextSession);
            }
            delete this.sessions[path];
            $.get(this.controller + '?action=remove&path=' + path);
            this.removeDraft(path);
        },

        //////////////////////////////////////////////////////////////////
        // Process rename
        //////////////////////////////////////////////////////////////////

        rename: function(oldPath, newPath) {
            var switchSessions = function(oldPath, newPath) {
                var thumb = this.sessions[oldPath].thumb;
                thumb.attr('data-path', newPath);
                thumb.find('.label')
                    .text(newPath.substring(1));
                this.sessions[newPath] = this.sessions[oldPath];
                this.sessions[newPath].path = newPath;
                this.sessions[oldPath] = undefined;
            };
            if (this.sessions[oldPath]) {
                // A file was renamed
                switchSessions.apply(this, [oldPath, newPath]);
                // pass new sessions instance to setactive
                for (var k = 0; k < codiad.editor.instances.length; k++) {
                    if (codiad.editor.instances[k].getSession().path === newPath) {
                        codiad.editor.setActive(codiad.editor.instances[k]);
                    }
                }

                var newSession = this.sessions[newPath];

                // Change Editor Mode
                var ext = codiad.filemanager.getExtension(newPath);
                var mode = codiad.editor.selectMode(ext);

                // handle async mode change
                var fn = function(){
                   codiad.editor.setModeDisplay(newSession);
                   newSession.removeListener('changeMode', fn);
                }

                newSession.on("changeMode", fn);
                newSession.setMode("ace/mode/" + mode);

            } else {
                // A folder was renamed
                var newKey;
                for (var key in this.sessions) {
                    newKey = key.replace(oldPath, newPath);
                    if (newKey !== key) {
                        switchSessions.apply(this, [key, newKey]);
                    }
                }
            }
            $.get(this.controller + '?action=rename&old_path=' + oldPath + '&new_path=' + newPath);
        },

        //////////////////////////////////////////////////////////////////
        // Open in Browser
        //////////////////////////////////////////////////////////////////

        openInBrowser: function() {
            var path = this.getPath();
            if (path) {
                codiad.filemanager.openInBrowser(path);
            } else {
                codiad.message.error('No Open Files');
            }
        },

        //////////////////////////////////////////////////////////////////
        // Get Selected Text
        //////////////////////////////////////////////////////////////////

        getSelectedText: function() {
            var path = this.getPath();
            var session = this.sessions[path];

            if (path && this.isOpen(path)) {
                return session.getTextRange(
                codiad.editor.getActive()
                    .getSelectionRange());
            } else {
                codiad.message.error('No Open Files or Selected Text');
            }
        },

        //////////////////////////////////////////////////////////////////
        // Insert Text
        //////////////////////////////////////////////////////////////////

        insertText: function(val) {
            codiad.editor.getActive()
                .insert(val);
        },

        //////////////////////////////////////////////////////////////////
        // Goto Line
        //////////////////////////////////////////////////////////////////

        gotoLine: function(line) {
            codiad.editor.getActive()
                .gotoLine(line, 0, true);
        },

        //////////////////////////////////////////////////////////////////
        // Move Up (Key Combo)
        //////////////////////////////////////////////////////////////////

        move: function(dir) {

            var num = $('#active-files a')
                .length;
            if (num > 1) {
                if (dir == 'up') {
                    // Move Up or rotate to bottom
                    newActive = $('#active-files li a.active')
                        .parent('li')
                        .prev('li')
                        .children('a')
                        .attr('data-path');
                    if (!newActive) {
                        newActive = $('#active-files li:last-child a')
                            .attr('data-path');
                    }

                } else {
                    // Move down or rotate to top
                    newActive = $('#active-files li a.active')
                        .parent('li')
                        .next('li')
                        .children('a')
                        .attr('data-path');
                    if (!newActive) {
                        newActive = $('#active-files li:first-child a')
                            .attr('data-path');
                    }

                }

                this.focus(newActive);
            }

        },

        //////////////////////////////////////////////////////////////////
        // Dropdown Menu
        //////////////////////////////////////////////////////////////////

        createTabDropdownMenu: function(){
            var _this = this;
            var _tabMenu = $('#tab-dropdown-menu');

            console.log(codiad.dropdown);
            codiad.dropdown.initMenuHandler($('#tab-dropdown-button'),_tabMenu);
        },

        moveTabToDropdownMenu: function(tab){
            tab.remove();
            path = tab.attr('data-path');

            var thumb = this.createMenuThumb(path);
            $('#tab-dropdown-menu').append(thumb);
            this.sessions[path].thumb = thumb;
        },

        moveDropdownMenuToTab: function(menu){
            menu.remove();
            path = menu.attr('data-path');

            var thumb = this.createTabThumb(path);
            $('.tab-list').append(thumb);
            this.sessions[path].thumb = thumb;
        },

        isTabListOverflowed: function(includeFictiveTab){
            if(typeof includeFictiveTab == 'undefined'){
                includeFictiveTab = false;
            }

            var tab = $('.tab-list li:last-child');
            if(tab.length == 0) return false;

            var coef = 1;
            if(includeFictiveTab) coef = 2;

            return (tab.position().left + coef*tab.outerWidth() >= $('.tab-list').width() - 320);
        },

        //////////////////////////////////////////////////////////////////
        // Factory
        //////////////////////////////////////////////////////////////////

        createTabThumb: function(path) {
            return $('<li class="tab-item" data-path="'+path+'"><a class="label" title="'+path+'">' + path.substring(1) + '</a><a class="close">x</a></li>');
        },

        createMenuThumb: function(path) {
            return thumb = $('<li data-path="'+path+'"><a title="'+path+'"><span></span><div class="label">' + path.substring(1) + '</div></a></li>');
        },

    };

})(this, jQuery);
<|endoftext|>