Source: protocol/channel.js

/**
 * Mobizen - Mobizen/Socket/Channel
 *
 * IMPORTANT NOTE: This file is licensed only for use in providing the RSUPPORT services,
 *
 * @copyright 2015 RSUPPORT CO., LTD. All rights reserved.
 * @license Dedicated to the public domain.
 * @author Joon Kyoung <firejune@gmail.com>
 *
 */

(function(exports) {
  "use strict";

  /** @typedef {Mobizen} */
  var Mobizen = exports.Mobizen || (exports.Mobizen = {});

  /**
   * @classdesc A Base Class of Socket Channels.
   *
   * @constructs Channel
   * @memberof Mobizen.Socket
   * @param {Mobizen.Socket} socket - A provider of Mobizen/Socket API.
   * @param {string} name - The name of channel of socket.
   * @requires {@linkplain Mobizen.Socket.Channel~Task}
   *
   */
  function Channel(socket, name) {
    console.info('Socket.' + capitalize(name));

    /**
     * The name of channel of socket.
     * @type {string}
     */
    this.name = name;
    /**
     * A provider of Mobizen/Socket API.
     * @typedef {Mobizen.Socket}
     */
    this.socket = socket;
    /**
     * A provider of the web protocol API.
     * @typedef {Protocol}
     */
    this.protocol = socket.protocol;

    /**
     * Store of the tasks.
     * @type {object}
     */
    this.tasks = {};
    /**
     * Store of the channel feature.
     * @type {object}
     */
    this.specific = {};
  }

  Channel.prototype = /** @lends Mobizen.Socket.Channel# */ {
    /**
     * The task of a sequence.
     *
     * @param {string} cmd
     * @param {string} event
     * @param {*} [arg1] - receive data 1
     * @param {*} [arg2] - receive data 2
     * @param {*} [arg3] - receive data 3
     * @return {boolean} excuted
     */
    task: function(cmd, event, arg1, arg2, arg3) {
      var task = this.tasks[cmd];
      if (task) {
        switch(event) {
          case 'destroy':
            task.onend(arg1, arg2, arg3);
            break;
          case 'data':
            task.ondata(arg1, arg2, arg3);
            break;
          case 'error':
            task.onerror(arg1, arg2, arg3);
            break;
          default:
            task.emit(event, arg1, arg2, arg3);
            break;
        }

        // destroy task
        if (task.type == 'request' || event == 'destroy') {
          task.off();
          delete this.tasks[cmd];
        }

        return true;
      }

      console.debug('Not executed a event', {event: event, cmd: cmd, channel: this.name});
      return false;
    },

    /**
     * Implement data transfer sequence.
     *
     * @param {string} [action] - operational signals
     * @param {object} options - sequence options
     * @param {*} [options.params] - send data of operational packet
     * @param {string} [options.type] - transfer type `request` or `stream`
     * @param {string} [options.channel] - channel of socket
     * @param {number} [options.timeout] - add an error event of timeout
     * @param {string|function} [options.pause] - send pause signal
     * @param {string|function} [options.resume] - send resume signal
     * @param {string|function} [options.cancel] - send cancel signal
     * @return {Mobizen.Socket.Channel~Task} a task of transfer sequence
     *
     * @example
     * ```
     * var task = this.sequence('some.command', {
     *     type: 'stream',
     *     params: {
     *       kind: kind,
     *       id: id
     *     },
     *     timeout: 24000,
     *     pause: 'some.command.pause',
     *     resume: 'some.command.resume',
     *     cancel: 'some.command.cancel'
     *   }).on('data', function(data) {
     *     // do somthing
     *   }).on('end', function(data) {
     *     // do somthing
     *   }).on('error', function(type) {
     *     // do somthing
     *   });
     *
     * // send pause signal while stream progress
     * task.pause();
     * // send resume signal while stream progress
     * task.resume();
     * // send cancel signal while stream progress
     * task.cancel();
     * ```
     * @todo EventEmitter의 `pipe()` `unpipe()` 메서드는 스트림 타입인 경우에만 이곳에서 확장
     */
    sequence: function(action, options) {
      if (!options.channel) {
        options.channel = this.name;
      }

      if (!options.type) {
        options.type =  'request';
      }

      if (typeof options.pause == 'string') {
        options.pause = this.send.bind(this, options.pause);
      }

      if (typeof options.resume == 'string') {
        options.resume = this.send.bind(this, options.resume);
      }

      if (typeof options.cancel == 'string') {
        options.cancel = this.send.bind(this, options.cancel);
      }

      if (action) {
        // operational signals
        this.send(action, options.params);
      }

      return new Task(options);
    },

    /**
     * Responsable packet transfer.
     *
     * @param {string} cmd
     * @param {*} data
     * @param {object} [options] - sequence options
     * @param {*} [options.params] - send data of operational packet
     * @param {string} [options.type] - transfer type `request` or `stream`
     * @param {string} [options.channel] - channel of socket
     * @param {number} [options.timeout] - add an error event of timeout
     * @param {string|function} [options.cancel] - send cancel signal
     * @return {Mobizen.Socket.Channel~Task} a task of transfer sequence
     *
     * @example
     * ```
     * var task = this.request('some.command', {
     *     kind: kind,
     *     id: id
     *   }).on('end', function(data) {
     *     // do somthing
     *   }).on('error', function(type) {
     *     // do somthing
     *   });
     * ```
     */
    request: function(cmd, data, options) {
      return this.tasks[cmd] = this.sequence(cmd, extend({
        type: 'request',
        params: data
      }, options));
    },

    /**
     * Streamable packet transfer.
     *
     * @param {string} cmd
     * @param {*} data
     * @param {object} [options] - sequence options
     * @param {*} [options.params] - send data of operational packet
     * @param {string} [options.type] - transfer type `request` or `stream`
     * @param {string} [options.channel] - channel of socket
     * @param {number} [options.timeout] - add an error event of timeout
     * @param {string|function} [options.pause] - send pause signal
     * @param {string|function} [options.resume] - send resume signal
     * @param {string|function} [options.cancel] - send cancel signal
     * @return {Mobizen.Socket.Channel~Task} a task of transfer sequence
     *
     * @example
     * ```
     * var task = this.stream('some.command', {
     *     kind: kind,
     *     id: id
     *   }, {
     *     timeout: 24000,
     *     pause: 'some.command.pause',
     *     resume: 'some.command.resume',
     *     cancel: function() {
     *       this.send('some.command.cancel', {type: 1});
     *     }.bind(this)
     *   }).on('data', function(data) {
     *     // do somthing
     *   }).on('end', function(data) {
     *     // do somthing
     *   }).on('error', function(type) {
     *     // do somthing
     *   });
     *
     * // send pause signal while stream progress
     * task.pause();
     * // send resume signal while stream progress
     * task.resume();
     * // send cancel signal while stream progress
     * task.cancel();
     * ```
     */
    stream: function(cmd, data, options) {
      return this.tasks[cmd] = this.sequence(cmd, extend({
        type: 'stream',
        params: data
      }, options));
    },

    /**
     * @param {string} action
     * @param {object} data
     * @return {Mobizen.Socket.Channel~Task} event emmiter
     */
    file: function(action, data) {
      this.file.mode = action;
      return (this.tasks['file.' + action] = this.sequence('file.' + action, {
        type: 'stream',
        params: data,
        cancel: action == 'find' ? 'file.find.cancel' : function() {
          var id = this.tasks['file.copymove'] && this.tasks['file.copymove'].id;
          if (id !== undefined) {
            this.send('file.cancel', {id: id});
          } else {
            this.send('file.cancel');
          }
        }.bind(this)
      }));
    },

    /**
     * @fires Mobizen.Socket.Channel#media․thumbnail․load
     * @fires Mobizen.Socket.Channel#media․thumbnail․timeout
     * @param {string} type
     * @param {object} data
     * @return {Mobizen.Socket.Channel~Task} event emmiter
     */
    preview: function(type, data) {
      //console.log('Channel.preview', type, data);

      var task
        , kind = {
          'photos': 4,
          'videos': 5,
          'music': 6
        }[type]
        , id = kind == 6 && data.albumId || data.id;

      if (typeof id != 'number') {
        return console.error('WTF?', type, data);
      }

      this.tasks['preview.' + kind + '-' + id] = task = this.sequence('media.thumbnail', {
        type: 'request',
        params: {
          kind: kind,
          id: id
        },
        /**
         * Timeout Error
         * @event Mobizen.Socket.Channel#media․thumbnail․error
         * @type {Mobizen.Event}
         * @param {string} type
         */
        timeout: 24000
      })

      .on('error'
      /**
       * @listens Mobizen.Socket.Channel#media․thumbnail․error
       * @memberof Mobizen.Socket.Channel~
       * @param {string} type
       */
      , function onerrormediathumbnail(type) {
        if (type == 'timeout') {
          /**
           * @event Mobizen.Socket.Channel#media․thumbnail․timeout
           * @type {Mobizen.Event}
           * @param {object} data
           */
          task.emit('timeout', task.data);
        }
      })

      .on('end'
      /**
       * @listens Mobizen.Socket.Channel.Data#media․thumbnail․end
       * @listens Mobizen.Socket.Channel.Local#media․thumbnail․end
       * @memberof Mobizen.Socket.Channel~
       * @param {object} data
       */
      , function onendmediathumbnail(data) {
        // Extending thumbnail object to current data
        if (task.data && data && data.thumbnail) {
          task.data.thumbnail = data;
        }
        /**
         * @event Mobizen.Socket.Channel#media․thumbnail․load
         * @type {Mobizen.Event}
         * @param {object} data
         */
        task.emit('load', task.data);
      });

      task.data = data;

      return task;
    },

    /**
     * @return {Mobizen.Socket.Channel~Task} event emmiter
     */
    notify: function() {
      return this.tasks.notify || (this.tasks.notify = this.sequence('process', {
        type: 'stream'
      }));
    },

    /**
     * @virtual
     * @param {object} payload
     * @param {*} [data]
     */
    error: function(payload, data) {
      console.error('Fatal: What is this a packet form ' + this.name + ' channel?', payload, data);
    },

    /**
     * @virtual
     */
    send: function() {
      console.error('Fatal: Not Implemented');
    },

    /**
     * @virtual
     */
    unbind: function() {
      console.error('Fatal: Not Implemented');
    }
  };


  /**
   * Inner functions
   */

  /**
   * @classdesc The Task Manager of Sequence.
   *
   * @constructs Task
   * @extends Mobizen.Event
   * @memberof Mobizen.Socket.Channel~
   *
   * @param {object} options - The task options.
   * @param {!string} options.type - The transfer type `request` or `stream`.
   * @param {!string} options.channel - The name of channel of socket.
   * @param {number} [options.timeout] - Add an error event of timeout.
   * @param {function} [options.pause] - Add a method of pause.
   * @param {function} [options.resume] - Add a method of resume.
   * @param {function} [options.cancel] - Add a method of cancel.
   *
   */
  function Task(options) {
    //console.info('Channel.Task', options.action);

    Task.super_.call(this);

    /**
     * The name of channel of socket.
     * @type {!string}
     */
    this.channel = options.channel;

    /**
     * The transfer type `request` or `stream`.
     * @type {!string}
     */
    this.type = options.type;

    /**
     * Add a method of pause if use otherwise just void.
     * @type {!function}
     */
    this.pause = options.pause || function() {};

    /**
     * Add a method of resume if use otherwise just void.
     * @type {!function}
     */
    this.resume = options.resume || function() {};

    /**
     * Add a method of cancel if use otherwise just void.
     * @type {!function}
     */
    this.cancel = options.cancel || function() {};

    if (options.timeout) {
      /**
       * Add an error event of timeout if use.
       * @type {?number}
       */
      this.timer = setTimeout(this.onerror.bind(this, 'timeout'), options.timeout);
    }
  }

  Mobizen.Inherits(Task, Mobizen.Event, /** @lends Mobizen.Socket.Channel~Task# */ {
    /**
     * @param {*} [arg1] - receive data 1
     * @param {*} [arg2] - receive data 2
     * @param {*} [arg3] - receive data 3
     */
    ondata: function(arg1, arg2, arg3) {
      clearTimeout(this.timer);
      this.emit('data', arg1, arg2, arg3);
    },

    /**
     * @param {*} [arg1] - receive data 1
     * @param {*} [arg2] - receive data 2
     * @param {*} [arg3] - receive data 3
     */
    onend: function(arg1, arg2, arg3) {
      clearTimeout(this.timer);
      this.emit('end', arg1, arg2, arg3);
    },

    /**
     * @param {string} type - typeof error
     * @param {*} [data] - receive data
     */
    onerror: function(type, data) {
      clearTimeout(this.timer);
      this.emit('error', type, data);
    },
  });

  /**
   * @memberof Mobizen.Socket.Channel~
   * @param {string} str
   * @return {string} capitalized
   */
  function capitalize(str) {
    var arr = str.split(/\s|-/);

    str = '';

    for (var i = 0; i < arr.length; i++) {
      str += arr[i].charAt(0).toUpperCase() + arr[i].slice(1);
    }

    return str;
  }

  /**
   * Object Extend.
   * @memberof Mobizen.Socket.Channel~
   * @param {object} destination - A destination object.
   * @param {object} [source] - A source object.
   * @return {object} Extended object.
   */
  function extend(destination, source) {
    if (!source) return destination;

    for (var p in source)
      if (source.hasOwnProperty(p))
        destination[p] = source[p];

    return destination;
  }


  /*
   * Exporting modules
   */

  Mobizen.Channel = Channel;

})(this);
comments powered by Disqus