<commit_before>kd              = require 'kd'
_               = require 'lodash'
getNick         = require 'app/util/nick'
isMyChannel     = require 'app/util/isMyChannel'

OpenTokService  = require './services/opentok'
ParticipantType = require './types/participant'
ViewModel       = require './viewmodel'
helper          = require './helper'

module.exports = class VideoCollaborationModel extends kd.Object

  defaultState:
    audio               : off
    video               : off
    publishing          : off
    active              : no
    connected           : no
    maxConnectionCount  : 999
    activeParticipant   : null
    selectedParticipant : null
    connectionCount     : 0

  # @param {SocialChannel} options.channel
  # @param {BaseChatVideoView} options.view
  constructor: (options = {}, data) ->

    super options, data

    @state = _.assign {}, @defaultState, options.state

    { @channel, @view } = options

    @session     = null
    @publisher   = null
    @stream      = null
    @subscribers = {}

    # attach this model and the view into a view model, it can track the model
    # for necessary events and reacts to it.
    @_videoModel = new ViewModel { @view, model: this }

    # instantiate the service and when it's ready, start this model's
    # intitalization process.
    @_service = OpenTokService.getInstance()
    @_service.whenReady @bound 'init'


  ###*
   * Returns the view.
   *
   * @return {KDView} view
  ###
  getView: -> @view


  ###*
   * Returns the channel.
   *
   * @return {SocialChannel} channel
  ###
  getChannel: -> @channel


  isMySession: -> isMyChannel @channel


  ###*
   * Connects to service, then delegates to necessary method.
   *
   * TODO: Error Handling.
  ###
  init: ->

    @_service.connect @channel,
      success : @bound 'handleSessionConnected'
      error   : (err) -> console.error err


  ###*
   * Handler for session connected.
   * Binds the session, session events and updates the state.
   *
   * @param {OT.Session} session
   * @emits VideoCollaborationModel~SessionConnected
  ###
  handleSessionConnected: (session) ->

    @session = session
    @bindSessionEvents session
    @setState { connected: yes }
    @emit 'SessionConnected', session

    @enableVideo { error: (err) => console.error err }  if helper.isVideoActive @channel


  ###*
   * Registers callbacks for service events.
   *
   * @param {OT.Session} session
   * @listens OT.Session~streamCreated
   * @listens OT.Session~streamDestroyed
   * @listens OT.Session~connectionCreated
   * @listens OT.Session~sessionDisconnected
   * @emits VideoCollaborationModel~HostKickedLoggedInUser
   * @emits VideoCollaborationModel~VideoCollaborationEnded
   * @emits VideoCollaborationModel~VideoCollaborationEndedByNetwork
   * @emits VideoCollaborationModel~VideoParticipantConnected
   * @emits VideoCollaborationModel~VideoParticipantDisconnected
  ###
  bindSessionEvents: (session) ->

    session.on 'streamCreated', (event) =>
      @subscribeToStream session, event.stream

    session.on 'streamDestroyed', (event) =>
      { connection } = event.stream
      @setParticipantLeft connection.connectionId

    session.on 'connectionCreated', (event) =>
      # We don't want to deal with own user's events, we basically want to
      # abstract out logged-in user out of the equation here.
      return  if @isMyConnection event.connection

      # this is gonna be a fake `ParticipantType.Subscriber` like object for
      # views to start drawing/rendering for this connection, because our
      # system doesn't necessarily require people to be publishint to session
      # to mark them as active. For example, showing participant heads on ide
      # chat window when video active we are filtering people who are connected
      # to session as actives. Participant heads doesn't care if you are
      # publishing or not, since user is hearing the video and seeing other
      # users he/she needs to be labeled as active/connected.
      subscriber = @registerDefaultSubscriber event.connection

      @incrementConnectionCount()

      @emit 'ParticipantConnected', subscriber

    session.on 'connectionDestroyed', (event) =>
      return  if @isMyConnection event.connection

      # get the name before unregistering.
      { nick } = @subscribers[event.connection.id]

      @unregisterSubscriber event.connection.id

      @decrementConnectionCount()

      @emit 'ParticipantDisconnected', helper.defaultSubscriber nick

    session.on 'sessionDisconnected', (event) =>
      @setState { connected: no }
      eventName = switch event.reason
        when 'forceDisconnected'   then 'HostKickedLoggedInUser'
        when 'clientDisconnected'  then 'VideoCollaborationEnded'
        when 'networkDisconnected' then 'VideoCollaborationEndedByNetwork'

      @emit eventName

    session.on 'signal:end', =>
      # when a signal comes here it means that it could have reached to other
      # users and because of that at this stack we may have extra
      # `streamDestroyed` events. Since stopPublishing will trigger the events
      # that eventually nullify the publisher in this stack, this defer tries
      # to overcome that problem. ~Umut
      kd.utils.defer => @stopPublishing
        success : @bound 'handleStopSuccess'
        error   : (err) -> console.error err

    session.on 'signal:start', => @enableVideo { error: (err) => console.error err }

    # this event only comes to the user who has been muted, so need to make a
    # filtering here.
    session.on 'signal:mute', => @setAudioState off


  ###*
   * Subscribes to given session's given stream.
   *
   * It uses helper method to subscribe, it's success callback will return the
   * `OT.Subscriber` object. From that on, the conversion from `OT.Subscriber`
   * to `ParticipantType.Subscriber` and other jobs happen in success handler.
   *
   * @param {OT.Session} session
   * @param {OT.Stream} stream
  ###
  subscribeToStream: (session, stream) ->

    helper.subscribeToStream session, stream, @getView().getContainer(),
      success : (subscriber) => @setParticipantJoined stream.name, subscriber
      error   : (err) -> console.error err


  ###*
   * Initial call to startPublishing method. `startPublishing` is stateless.
   * This method handles initial automatic publishing with setting some default
   * options for host, and participant to be different.
   *
   * @param {object} callbacks
  ###
  enableVideo: (callbacks) ->

    @setActive()

    success = (publisher) =>
      @handlePublishSuccess publisher
      callbacks.success? publisher
<commit_msg>video/model: move event handlers to their own methods<commit_after>kd              = require 'kd'
_               = require 'lodash'
getNick         = require 'app/util/nick'
isMyChannel     = require 'app/util/isMyChannel'

OpenTokService  = require './services/opentok'
ParticipantType = require './types/participant'
ViewModel       = require './viewmodel'
helper          = require './helper'

module.exports = class VideoCollaborationModel extends kd.Object

  defaultState:
    audio               : off
    video               : off
    publishing          : off
    active              : no
    connected           : no
    maxConnectionCount  : 999
    activeParticipant   : null
    selectedParticipant : null
    connectionCount     : 0

  # @param {SocialChannel} options.channel
  # @param {BaseChatVideoView} options.view
  constructor: (options = {}, data) ->

    super options, data

    @state = _.assign {}, @defaultState, options.state

    { @channel, @view } = options

    @session     = null
    @publisher   = null
    @stream      = null
    @subscribers = {}

    # attach this model and the view into a view model, it can track the model
    # for necessary events and reacts to it.
    @_videoModel = new ViewModel { @view, model: this }

    # instantiate the service and when it's ready, start this model's
    # intitalization process.
    @_service = OpenTokService.getInstance()
    @_service.whenReady @bound 'init'


  ###*
   * Returns the view.
   *
   * @return {KDView} view
  ###
  getView: -> @view


  ###*
   * Returns the channel.
   *
   * @return {SocialChannel} channel
  ###
  getChannel: -> @channel


  isMySession: -> isMyChannel @channel


  ###*
   * Connects to service, then delegates to necessary method.
   *
   * TODO: Error Handling.
  ###
  init: ->

    @_service.connect @channel,
      success : @bound 'handleSessionConnected'
      error   : (err) -> console.error err


  ###*
   * Handler for session connected.
   * Binds the session, session events and updates the state.
   *
   * @param {OT.Session} session
   * @emits VideoCollaborationModel~SessionConnected
  ###
  handleSessionConnected: (session) ->

    @session = session
    @bindSessionEvents session
    @setState { connected: yes }
    @emit 'SessionConnected', session

    @enableVideo { error: (err) => console.error err }  if helper.isVideoActive @channel


  ###*
   * Registers callbacks for service events.
   *
   * @param {OT.Session} session
   * @emits VideoCollaborationModel~HostKickedLoggedInUser
   * @emits VideoCollaborationModel~VideoCollaborationEnded
   * @emits VideoCollaborationModel~VideoCollaborationEndedByNetwork
  ###
  bindSessionEvents: (session) ->

    session.on 'connectionCreated'   , @bound 'onConnectionCreated'
    session.on 'streamCreated'       , @bound 'onStreamCreated'
    session.on 'streamDestroyed'     , @bound 'onStreamDestroyed'
    session.on 'connectionDestroyed' , @bound 'onConnectionDestroyed'

    session.on 'sessionDisconnected', (event) =>
      @setState { connected: no }
      eventName = switch event.reason
        when 'forceDisconnected'   then 'HostKickedLoggedInUser'
        when 'clientDisconnected'  then 'VideoCollaborationEnded'
        when 'networkDisconnected' then 'VideoCollaborationEndedByNetwork'

      @emit eventName

    session.on 'signal:end', =>
      # when a signal comes here it means that it could have reached to other
      # users and because of that at this stack we may have extra
      # `streamDestroyed` events. Since stopPublishing will trigger the events
      # that eventually nullify the publisher in this stack, this defer tries
      # to overcome that problem. ~Umut
      kd.utils.defer => @stopPublishing
        success : @bound 'handleStopSuccess'
        error   : (err) -> console.error err

    session.on 'signal:start', => @enableVideo { error: (err) => console.error err }

    # this event only comes to the user who has been muted, so need to make a
    # filtering here.
    session.on 'signal:mute', => @setAudioState off


  ###*
   * Opentok session's `connectionCreated` event handler.
   *
   * @param {OT.ConnectionEvent} event
   * @emits VideoCollaborationModel~ParticipantConnected
  ###
  onConnectionCreated: (event) ->
    # We don't want to deal with own user's events, we basically want to
    # abstract out logged-in user out of the equation here.
    return  if @isMyConnection event.connection

    # this is gonna be a fake `ParticipantType.Subscriber` like object for
    # views to start drawing/rendering for this connection, because our
    # system doesn't necessarily require people to be publishint to session
    # to mark them as active. For example, showing participant heads on ide
    # chat window when video active we are filtering people who are connected
    # to session as actives. Participant heads doesn't care if you are
    # publishing or not, since user is hearing the video and seeing other
    # users he/she needs to be labeled as active/connected.
    subscriber = @registerDefaultSubscriber event.connection

    @incrementConnectionCount()

    @emit 'ParticipantConnected', subscriber


  ###*
   * Opentok session's `connectionCreated` event handler.
   *
   * @param {OT.ConnectionEvent} event
   * @emits VideoCollaborationModel~ParticipantDisconnected
  ###
  onConnectionDestroyed: (event) ->

    { connection } = event

    return  if @isMyConnection connection

    @setParticipantLeft connection.id
    @decrementConnectionCount()

    nick = helper.getNicknameFromConnection connection

    @emit 'ParticipantDisconnected', helper.defaultSubscriber nick


  ###*
   * Opentok session's `streamCreated` event handler.
   *
   * @param {OT.StreamEvent} event
  ###
  onStreamCreated: (event)->

    @subscribeToStream session, event.stream


  ###*
   * Opentok session's `streamDestroyed` event handler.
   *
   * @param {OT.StreamEvent} event
  ###
  onStreamDestroyed: (event) ->
    { connection } = event.stream
    @setParticipantLeft connection.connectionId


  ###*
   * Subscribes to given session's given stream.
   *
   * It uses helper method to subscribe, it's success callback will return the
   * `OT.Subscriber` object. From that on, the conversion from `OT.Subscriber`
   * to `ParticipantType.Subscriber` and other jobs happen in success handler.
   *
   * @param {OT.Session} session
   * @param {OT.Stream} stream
  ###
  subscribeToStream: (session, stream) ->

    helper.subscribeToStream session, stream, @getView().getContainer(),
      success : (subscriber) => @setParticipantJoined stream.name, subscriber
      error   : (err) -> console.error err


  ###*
   * Initial call to startPublishing method. `startPublishing` is stateless.
   * This method handles initial automatic publishing with setting some default
   * options for host, and participant to be different.
   *
   * @param {object} callbacks
  ###
  enableVideo: (callbacks) ->

    @setActive()

    success = (publisher) =>
      @handlePublishSuccess publisher
      callbacks.success? publisher

{'fraction_non_alphanumeric': 0.09144478393714535, 'fraction_numerical': 0.0005819874872690237, 'mean_word_length': 3.0539663815983484, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 53, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7312031', 'n_tokens_mistral': 3928, 'n_tokens_neox': 3734, 'n_words': 1217}