<commit_before># encoding: utf-8

require 'has_guarded_handlers'
require 'thread'
require 'active_support/hash_with_indifferent_access'

module Adhearsion
  ##
  # Encapsulates call-related data and behavior.
  #
  class Call

    Hangup          = Class.new Adhearsion::Error
    CommandTimeout  = Class.new Adhearsion::Error
    ExpiredError    = Class.new Celluloid::DeadActorError

    include Celluloid
    include HasGuardedHandlers

    execute_block_on_receiver :register_handler, :register_tmp_handler, :register_handler_with_priority, :register_event_handler, :on_joined, :on_unjoined, :on_end, :execute_controller
    finalizer :finalize

    def self.new(*args, &block)
      super.tap do |proxy|
        def proxy.method_missing(*args)
          super
        rescue Celluloid::DeadActorError
          raise ExpiredError, "This call is expired and is no longer accessible"
        end
      end
    end

    attr_reader :end_reason, :controllers, :variables, :start_time, :end_time

    delegate :[], :[]=, :to => :variables
    delegate :to, :from, :to => :offer, :allow_nil => true

    def initialize(offer = nil)
      register_initial_handlers

      @offer        = nil
      @tags         = []
      @commands     = CommandRegistry.new
      @variables    = HashWithIndifferentAccess.new
      @controllers  = []
      @end_reason   = nil
      @end_blocker  = Celluloid::Condition.new
      @peers        = {}
      @duration     = nil

      self << offer if offer
    end

    #
    # @return [String, nil] The globally unique ID for the call
    #
    def id
      offer.target_call_id if offer
    end
    alias :to_s :id

    #
    # @return [String, nil] The domain on which the call resides
    #
    def domain
      offer.domain if offer
    end

    #
    # @return [String, nil] The uri at which the call resides
    #
    def uri
      return nil unless id
      s = ""
      s << transport << ":" if transport
      s << id
      s << "@" << domain if domain
      s
    end

    #
    # @return [Array] The set of labels with which this call has been tagged.
    #
    def tags
      @tags.clone
    end

    #
    # Tag a call with an arbitrary label
    #
    # @param [String, Symbol] label String or Symbol with which to tag this call
    #
    def tag(label)
      abort ArgumentError.new "Tag must be a String or Symbol" unless [String, Symbol].include?(label.class)
      @tags << label
    end

    #
    # Remove a label
    #
    # @param [String, Symbol] label
    #
    def remove_tag(label)
      @tags.reject! { |tag| tag == label }
    end

    #
    # Establish if the call is tagged with the provided label
    #
    # @param [String, Symbol] label
    #
    def tagged_with?(label)
      @tags.include? label
    end

    #
    # Hash of joined peers
    # @return [Hash<String => Adhearsion::Call>]
    #
    def peers
      @peers.clone
    end

    #
    # Wait for the call to end. Returns immediately if the call has already ended, else blocks until it does so.
    # @return [Symbol] the reason for the call ending
    #
    def wait_for_end
      if end_reason
        end_reason
      else
        @end_blocker.wait
      end
    end

    def register_event_handler(*guards, &block)
      register_handler :event, *guards, &block
    end

    def deliver_message(message)
      logger.debug "Receiving message: #{message.inspect}"
      catching_standard_errors { trigger_handler :event, message }
    end
    alias << deliver_message

    def commands
      @commands.clone
    end

    # @private
    def register_initial_handlers
      register_event_handler Punchblock::Event::Offer do |offer|
        @offer  = offer
        @client = offer.client
        @start_time = Time.now
        throw :pass
      end

      register_event_handler Punchblock::HasHeaders do |event|
        merge_headers event.headers
        throw :pass
      end

      on_joined do |event|
        if event.call_uri
          target = event.call_uri
          type = :call
        else
          target = event.mixer_name
          type = :mixer
        end
        logger.info "Joined to #{type} #{target}"
        @peers[target] = Adhearsion.active_calls[target]
        signal :joined, target
      end

      on_unjoined do |event|
        if event.call_uri
          target = event.call_uri
          type = :call
        else
          target = event.mixer_name
          type = :mixer
        end
        logger.info "Unjoined from #{type} #{target}"
        @peers.delete target
        signal :unjoined, target
      end

      on_end do |event|
        logger.info "Call ended due to #{event.reason}"
        @end_time = Time.now
        @duration = @end_time - @start_time if @start_time
        clear_from_active_calls
        @end_reason = event.reason
        @end_blocker.broadcast event.reason
        @commands.terminate
        after(Adhearsion.config.platform.after_hangup_lifetime) { terminate }
        throw :pass
      end
    end

    # @return [Float] The call duration until the current time, or until the call was disconnected, whichever is earlier
    def duration
      if @duration
        @duration
      elsif @start_time
        Time.now - @start_time
      else
        0.0
      end
    end

    ##
    # Registers a callback for when this call is joined to another call or a mixer
    #
    # @param [Call, String, Hash, nil] target the target to guard on. May be a Call object, a call ID (String, Hash) or a mixer name (Hash)
    # @option target [String] call_uri The call ID to guard on
    # @option target [String] mixer_name The mixer name to guard on
    #
    def on_joined(target = nil, &block)
      register_event_handler Punchblock::Event::Joined, *guards_for_target(target) do |event|
        block.call event
        throw :pass
      end
    end

    ##
    # Registers a callback for when this call is unjoined from another call or a mixer
    #
    # @param [Call, String, Hash, nil] target the target to guard on. May be a Call object, a call ID (String, Hash) or a mixer name (Hash)
    # @option target [String] call_uri The call ID to guard on
    # @option target [String] mixer_name The mixer name to guard on
    #
    def on_unjoined(target = nil, &block)
      register_event_handler Punchblock::Event::Unjoined, *guards_for_target(target) do |event|
        block.call event
        throw :pass
      end
    end

    # @private
    def guards_for_target(target)
      target ? [join_options_with_target(target)] : []
    end

    def on_end(&block)
      register_event_handler Punchblock::Event::End do |event|
        block.call event
        throw :pass
      end
    end

    #
    # @return [Boolean] if the call is currently active or not (disconnected)
    #
    def active?
      !end_reason
    end

    def accept(headers = nil)
      @accept_command ||= write_and_await_response Punchblock::Command::Accept.new(:headers => headers)
    end

    def answer(headers = nil)
      write_and_await_response Punchblock::Command::Answer.new(:headers => headers)
    end

    def reject(reason = :busy, headers = nil)
      write_and_await_response Punchblock::Command::Reject.new(:reason => reason, :headers => headers)
      Adhearsion::Events.trigger_immediately :call_rejected, call: current_actor, reason: reason
    end

    def hangup(headers = nil)
      return false unless active?
      logger.info "Hanging up"
      @end_reason = true
      write_and_await_response Punchblock::Command::Hangup.new(:headers => headers)
    end

    # @private
    def clear_from_active_calls
      Adhearsion.active_calls.remove_inactive_call current_actor
    end

    ##
    # Joins this call to another call or a mixer
    #
    # @param [Call, String, Hash] target the target to join to. May be a Call object, a call ID (String, Hash) or a mixer name (Hash)
    # @option target [String] call_uri The call ID to join to
    # @option target [String] mixer_name The mixer to join to
    # @param [Hash, Optional] options further options to be joined with
    #
    def join(target, options = {})
      logger.info "Joining to #{target}"
      command = Punchblock::Command::Join.new options.merge(join_options_with_target(target))
      write_and_await_response command
    end

    ##
    # Unjoins this call from another call or a mixer
    #
    # @param [Call, String, Hash] target the target to unjoin from. May be a Call object, a call ID (String, Hash) or a mixer name (Hash)
    # @option target [String] call_uri The call ID to unjoin from
    # @option target [String] mixer_name The mixer to unjoin from
    #
    def unjoin(target)
      logger.info "Unjoining from #{target}"
      command = Punchblock::Command::Unjoin.new join_options_with_target(target)
      write_and_await_response command
    end

    # @private
    def join_options_with_target(target)
      case target
      when Call
        { :call_uri => target.uri }
      when String
        { :call_uri => "#{transport}:#{target}@#{domain}" }
      when Hash
        abort ArgumentError.new "You cannot specify both a call URI and mixer name" if target.has_key?(:call_uri) && target.has_key?(:mixer_name)
        target
      else
        abort ArgumentError.new "Don't know how to join to #{target.inspect}"
      end
    end

    def wait_for_joined(expected_target)
      target = nil
      until target == expected_target do
        target = wait :joined
      end
    end

    def wait_for_unjoined(expected_target)
      target = nil
      until target == expected_target do
        target = wait :unjoined
      end
    end

    def mute
      write_and_await_response Punchblock::Command::Mute.new
    end

    def unmute
      write_and_await_response Punchblock::Command::Unmute.new
    end

    # @private
    def write_and_await_response(command, timeout = 60)
      @commands << command
      write_command command

      response = defer { command.response timeout }
      case response
      when Punchblock::ProtocolError
        if response.name == :item_not_found
          abort Hangup.new(@end_reason)
        else
          abort response
        end
      when Exception
        abort response
      end

      command
    rescue Timeout::Error
      abort CommandTimeout.new(command.to_s)
    ensure
      @commands.delete command
    end

    # @private
    def write_command(command)
      abort Hangup.new(@end_reason) unless active? || command.is_a?(Punchblock::Command::Hangup)
      merge_headers command.headers if command.respond_to? :headers
      logger.debug "Executing command #{command.inspect}"
      client.execute_command command, call_id: id, domain: domain, async: true
    end

    # @private
    def logger_id
      "#{self.class}: #{id}@#{domain}"
    end
    # @private
    def inspect
      attrs = [:offer, :end_reason, :commands, :variables, :controllers, :to, :from].map do |attr|
        "#{attr}=#{send(attr).inspect}"
      end
      "#<#{self.class}:#{id}@#{domain} #{attrs.join ', '}>"
    end

    def execute_controller(controller = nil, completion_callback = nil, &block)
      raise ArgumentError, "Cannot supply a controller and a block at the same time" if controller && block_given?
      controller ||= CallController.new current_actor, &block
      logger.info "Executing controller #{controller.inspect}"
      controller.bg_exec completion_callback
    end

    # @private
    def register_controller(controller)
      @controllers << controller
    end

    # @private
    def pause_controllers
      controllers.each(&:pause!)
    end

    # @private
    def resume_controllers
      controllers.each(&:resume!)
    end

    private

    def offer
      @offer
    end

    def client
      @client
    end

    def transport
      offer.transport if offer
    end

    def merge_headers(headers)
      headers.each do |name, value|
        variables[name.to_s.downcase.gsub('-', '_')] = value
      end
    end

    def finalize
      ::Logging::Repository.instance.delete logger_id
    end

    # @private
    class CommandRegistry < Array
      def terminate
        hangup = Hangup.new
        each { |command| command.response = hangup if command.requested? }
      end
    end

  end
end
<commit_msg>[DOC] Add extra API documentation to Call<commit_after># encoding: utf-8

require 'has_guarded_handlers'
require 'thread'
require 'active_support/hash_with_indifferent_access'

module Adhearsion
  ##
  # Encapsulates call-related data and behavior.
  #
  class Call

    Hangup          = Class.new Adhearsion::Error
    CommandTimeout  = Class.new Adhearsion::Error
    ExpiredError    = Class.new Celluloid::DeadActorError

    include Celluloid
    include HasGuardedHandlers

    execute_block_on_receiver :register_handler, :register_tmp_handler, :register_handler_with_priority, :register_event_handler, :on_joined, :on_unjoined, :on_end, :execute_controller
    finalizer :finalize

    def self.new(*args, &block)
      super.tap do |proxy|
        def proxy.method_missing(*args)
          super
        rescue Celluloid::DeadActorError
          raise ExpiredError, "This call is expired and is no longer accessible"
        end
      end
    end

    # @return [Symbol] the reason for the call ending
    attr_reader :end_reason

    # @return [Array<Adhearsion::CallController>] the set of call controllers executing on the call
    attr_reader :controllers

    # @return [Hash<String => String>] a collection of SIP headers set during the call
    attr_reader :variables

    # @return [Time] the time at which the call began. For inbound calls this is the time at which the call was offered to Adhearsion. For outbound calls it is the time at which the remote party answered.
    attr_reader :start_time

    # @return [Time] the time at which the call began. For inbound calls this is the time at which the call was offered to Adhearsion. For outbound calls it is the time at which the remote party answered.
    attr_reader :end_time

    delegate :[], :[]=, :to => :variables
    delegate :to, :from, :to => :offer, :allow_nil => true

    def initialize(offer = nil)
      register_initial_handlers

      @offer        = nil
      @tags         = []
      @commands     = CommandRegistry.new
      @variables    = HashWithIndifferentAccess.new
      @controllers  = []
      @end_reason   = nil
      @end_blocker  = Celluloid::Condition.new
      @peers        = {}
      @duration     = nil

      self << offer if offer
    end

    #
    # @return [String, nil] The globally unique ID for the call
    #
    def id
      offer.target_call_id if offer
    end
    alias :to_s :id

    #
    # @return [String, nil] The domain on which the call resides
    #
    def domain
      offer.domain if offer
    end

    #
    # @return [String, nil] The uri at which the call resides
    #
    def uri
      return nil unless id
      s = ""
      s << transport << ":" if transport
      s << id
      s << "@" << domain if domain
      s
    end

    #
    # @return [Array] The set of labels with which this call has been tagged.
    #
    def tags
      @tags.clone
    end

    #
    # Tag a call with an arbitrary label
    #
    # @param [String, Symbol] label String or Symbol with which to tag this call
    #
    def tag(label)
      abort ArgumentError.new "Tag must be a String or Symbol" unless [String, Symbol].include?(label.class)
      @tags << label
    end

    #
    # Remove a label
    #
    # @param [String, Symbol] label
    #
    def remove_tag(label)
      @tags.reject! { |tag| tag == label }
    end

    #
    # Establish if the call is tagged with the provided label
    #
    # @param [String, Symbol] label
    #
    def tagged_with?(label)
      @tags.include? label
    end

    #
    # Hash of joined peers
    # @return [Hash<String => Adhearsion::Call>]
    #
    def peers
      @peers.clone
    end

    #
    # Wait for the call to end. Returns immediately if the call has already ended, else blocks until it does so.
    # @return [Symbol] the reason for the call ending
    #
    def wait_for_end
      if end_reason
        end_reason
      else
        @end_blocker.wait
      end
    end

    def register_event_handler(*guards, &block)
      register_handler :event, *guards, &block
    end

    def deliver_message(message)
      logger.debug "Receiving message: #{message.inspect}"
      catching_standard_errors { trigger_handler :event, message }
    end
    alias << deliver_message

    def commands
      @commands.clone
    end

    # @private
    def register_initial_handlers
      register_event_handler Punchblock::Event::Offer do |offer|
        @offer  = offer
        @client = offer.client
        @start_time = Time.now
        throw :pass
      end

      register_event_handler Punchblock::HasHeaders do |event|
        merge_headers event.headers
        throw :pass
      end

      on_joined do |event|
        if event.call_uri
          target = event.call_uri
          type = :call
        else
          target = event.mixer_name
          type = :mixer
        end
        logger.info "Joined to #{type} #{target}"
        @peers[target] = Adhearsion.active_calls[target]
        signal :joined, target
      end

      on_unjoined do |event|
        if event.call_uri
          target = event.call_uri
          type = :call
        else
          target = event.mixer_name
          type = :mixer
        end
        logger.info "Unjoined from #{type} #{target}"
        @peers.delete target
        signal :unjoined, target
      end

      on_end do |event|
        logger.info "Call ended due to #{event.reason}"
        @end_time = Time.now
        @duration = @end_time - @start_time if @start_time
        clear_from_active_calls
        @end_reason = event.reason
        @end_blocker.broadcast event.reason
        @commands.terminate
        after(Adhearsion.config.platform.after_hangup_lifetime) { terminate }
        throw :pass
      end
    end

    # @return [Float] The call duration until the current time, or until the call was disconnected, whichever is earlier
    def duration
      if @duration
        @duration
      elsif @start_time
        Time.now - @start_time
      else
        0.0
      end
    end

    ##
    # Registers a callback for when this call is joined to another call or a mixer
    #
    # @param [Call, String, Hash, nil] target the target to guard on. May be a Call object, a call ID (String, Hash) or a mixer name (Hash)
    # @option target [String] call_uri The call ID to guard on
    # @option target [String] mixer_name The mixer name to guard on
    #
    def on_joined(target = nil, &block)
      register_event_handler Punchblock::Event::Joined, *guards_for_target(target) do |event|
        block.call event
        throw :pass
      end
    end

    ##
    # Registers a callback for when this call is unjoined from another call or a mixer
    #
    # @param [Call, String, Hash, nil] target the target to guard on. May be a Call object, a call ID (String, Hash) or a mixer name (Hash)
    # @option target [String] call_uri The call ID to guard on
    # @option target [String] mixer_name The mixer name to guard on
    #
    def on_unjoined(target = nil, &block)
      register_event_handler Punchblock::Event::Unjoined, *guards_for_target(target) do |event|
        block.call event
        throw :pass
      end
    end

    # @private
    def guards_for_target(target)
      target ? [join_options_with_target(target)] : []
    end

    def on_end(&block)
      register_event_handler Punchblock::Event::End do |event|
        block.call event
        throw :pass
      end
    end

    #
    # @return [Boolean] if the call is currently active or not (disconnected)
    #
    def active?
      !end_reason
    end

    def accept(headers = nil)
      @accept_command ||= write_and_await_response Punchblock::Command::Accept.new(:headers => headers)
    end

    def answer(headers = nil)
      write_and_await_response Punchblock::Command::Answer.new(:headers => headers)
    end

    def reject(reason = :busy, headers = nil)
      write_and_await_response Punchblock::Command::Reject.new(:reason => reason, :headers => headers)
      Adhearsion::Events.trigger_immediately :call_rejected, call: current_actor, reason: reason
    end

    def hangup(headers = nil)
      return false unless active?
      logger.info "Hanging up"
      @end_reason = true
      write_and_await_response Punchblock::Command::Hangup.new(:headers => headers)
    end

    # @private
    def clear_from_active_calls
      Adhearsion.active_calls.remove_inactive_call current_actor
    end

    ##
    # Joins this call to another call or a mixer
    #
    # @param [Call, String, Hash] target the target to join to. May be a Call object, a call ID (String, Hash) or a mixer name (Hash)
    # @option target [String] call_uri The call ID to join to
    # @option target [String] mixer_name The mixer to join to
    # @param [Hash, Optional] options further options to be joined with
    #
    def join(target, options = {})
      logger.info "Joining to #{target}"
      command = Punchblock::Command::Join.new options.merge(join_options_with_target(target))
      write_and_await_response command
    end

    ##
    # Unjoins this call from another call or a mixer
    #
    # @param [Call, String, Hash] target the target to unjoin from. May be a Call object, a call ID (String, Hash) or a mixer name (Hash)
    # @option target [String] call_uri The call ID to unjoin from
    # @option target [String] mixer_name The mixer to unjoin from
    #
    def unjoin(target)
      logger.info "Unjoining from #{target}"
      command = Punchblock::Command::Unjoin.new join_options_with_target(target)
      write_and_await_response command
    end

    # @private
    def join_options_with_target(target)
      case target
      when Call
        { :call_uri => target.uri }
      when String
        { :call_uri => "#{transport}:#{target}@#{domain}" }
      when Hash
        abort ArgumentError.new "You cannot specify both a call URI and mixer name" if target.has_key?(:call_uri) && target.has_key?(:mixer_name)
        target
      else
        abort ArgumentError.new "Don't know how to join to #{target.inspect}"
      end
    end

    def wait_for_joined(expected_target)
      target = nil
      until target == expected_target do
        target = wait :joined
      end
    end

    def wait_for_unjoined(expected_target)
      target = nil
      until target == expected_target do
        target = wait :unjoined
      end
    end

    def mute
      write_and_await_response Punchblock::Command::Mute.new
    end

    def unmute
      write_and_await_response Punchblock::Command::Unmute.new
    end

    # @private
    def write_and_await_response(command, timeout = 60)
      @commands << command
      write_command command

      response = defer { command.response timeout }
      case response
      when Punchblock::ProtocolError
        if response.name == :item_not_found
          abort Hangup.new(@end_reason)
        else
          abort response
        end
      when Exception
        abort response
      end

      command
    rescue Timeout::Error
      abort CommandTimeout.new(command.to_s)
    ensure
      @commands.delete command
    end

    # @private
    def write_command(command)
      abort Hangup.new(@end_reason) unless active? || command.is_a?(Punchblock::Command::Hangup)
      merge_headers command.headers if command.respond_to? :headers
      logger.debug "Executing command #{command.inspect}"
      client.execute_command command, call_id: id, domain: domain, async: true
    end

    # @private
    def logger_id
      "#{self.class}: #{id}@#{domain}"
    end
    # @private
    def inspect
      attrs = [:offer, :end_reason, :commands, :variables, :controllers, :to, :from].map do |attr|
        "#{attr}=#{send(attr).inspect}"
      end
      "#<#{self.class}:#{id}@#{domain} #{attrs.join ', '}>"
    end

    def execute_controller(controller = nil, completion_callback = nil, &block)
      raise ArgumentError, "Cannot supply a controller and a block at the same time" if controller && block_given?
      controller ||= CallController.new current_actor, &block
      logger.info "Executing controller #{controller.inspect}"
      controller.bg_exec completion_callback
    end

    # @private
    def register_controller(controller)
      @controllers << controller
    end

    # @private
    def pause_controllers
      controllers.each(&:pause!)
    end

    # @private
    def resume_controllers
      controllers.each(&:resume!)
    end

    private

    def offer
      @offer
    end

    def client
      @client
    end

    def transport
      offer.transport if offer
    end

    def merge_headers(headers)
      headers.each do |name, value|
        variables[name.to_s.downcase.gsub('-', '_')] = value
      end
    end

    def finalize
      ::Logging::Repository.instance.delete logger_id
    end

    # @private
    class CommandRegistry < Array
      def terminate
        hangup = Hangup.new
        each { |command| command.response = hangup if command.requested? }
      end
    end

  end
end

{'fraction_non_alphanumeric': 0.08280229566594102, 'fraction_numerical': 0.00039580447259054025, 'mean_word_length': 2.4431725265739983, 'pattern_counts': {'":': 2, '<': 51, '<?xml version=': 0, '>': 32, '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': '4189650', 'n_tokens_mistral': 7551, 'n_tokens_neox': 7394, 'n_words': 2345}