diff --git a/CHANGELOG.md b/CHANGELOG.md index 8650847b..4d5988e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # [develop](https://github.com/adhearsion/punchblock) + * Change: Remove support for FreeSWITCH translator on Inbound EventSocket # [v2.7.0](https://github.com/adhearsion/punchblock/compare/v2.6.0...v2.7.0) - [2015-06-09](https://rubygems.org/gems/punchblock/versions/2.7.0) * Feature: Support for Asterisk 13 (AMI v2) diff --git a/README.markdown b/README.markdown index 08b941bf..1f9d47f5 100644 --- a/README.markdown +++ b/README.markdown @@ -21,7 +21,6 @@ The best available usage documentation available for Punchblock is by example, i * Rayo * Asterisk (AMI & AsyncAGI) -* FreeSWITCH (Inbound Event Socket) ## Links: * [Source](https://github.com/adhearsion/punchblock) diff --git a/lib/punchblock.rb b/lib/punchblock.rb index 660c433f..145edee7 100644 --- a/lib/punchblock.rb +++ b/lib/punchblock.rb @@ -45,7 +45,7 @@ def reset_logger # # Get a new Punchblock client with a connection attached # - # @param [Symbol] type the connection type (eg :XMPP, :asterisk, :freeswitch) + # @param [Symbol] type the connection type (eg :XMPP, :asterisk) # @param [Hash] options the options to pass to the connection (credentials, etc # # @return [Punchblock::Client] a punchblock client object diff --git a/lib/punchblock/connection.rb b/lib/punchblock/connection.rb index 4e15ba9c..d9669209 100644 --- a/lib/punchblock/connection.rb +++ b/lib/punchblock/connection.rb @@ -6,7 +6,6 @@ module Connection autoload :Asterisk autoload :Connected - autoload :Freeswitch autoload :GenericConnection autoload :XMPP end diff --git a/lib/punchblock/connection/freeswitch.rb b/lib/punchblock/connection/freeswitch.rb deleted file mode 100644 index c0016b80..00000000 --- a/lib/punchblock/connection/freeswitch.rb +++ /dev/null @@ -1,53 +0,0 @@ -# encoding: utf-8 - -require 'ruby_fs' - -module Punchblock - module Connection - class Freeswitch < GenericConnection - attr_reader :translator, :stream - attr_accessor :event_handler - - def initialize(options = {}) - @translator = Translator::Freeswitch.new self - @stream_options = options.values_at(:host, :port, :password) - @stream = new_fs_stream - super() - end - - def run - pb_logger.debug "Starting the RubyFS stream" - start_stream - raise DisconnectedError - end - - def stop - stream.shutdown - translator.terminate - end - - def write(command, options) - translator.async.execute_command command, options - end - - def handle_event(event) - event_handler.call event if event_handler.respond_to?(:call) - end - - private - - def new_fs_stream - RubyFS::Stream.new(*@stream_options, lambda { |e| translator.async.handle_es_event e }, event_mask) - end - - def event_mask - %w{CHANNEL_PARK CHANNEL_ANSWER CHANNEL_STATE CHANNEL_HANGUP CHANNEL_BRIDGE CHANNEL_UNBRIDGE CHANNEL_EXECUTE_COMPLETE DTMF RECORD_STOP} - end - - def start_stream - @stream = new_fs_stream unless @stream.alive? - @stream.run - end - end - end -end diff --git a/lib/punchblock/translator.rb b/lib/punchblock/translator.rb index 7fe0e3cb..443a1417 100644 --- a/lib/punchblock/translator.rb +++ b/lib/punchblock/translator.rb @@ -7,7 +7,6 @@ module Translator OptionError = Class.new Punchblock::Error autoload :Asterisk - autoload :Freeswitch autoload :DTMFRecognizer autoload :InputComponent diff --git a/lib/punchblock/translator/freeswitch.rb b/lib/punchblock/translator/freeswitch.rb deleted file mode 100644 index a0c43437..00000000 --- a/lib/punchblock/translator/freeswitch.rb +++ /dev/null @@ -1,164 +0,0 @@ -# encoding: utf-8 - -require 'celluloid' -require 'ruby_fs' - -module Punchblock - module Translator - class Freeswitch - include Celluloid - include HasGuardedHandlers - include DeadActorSafety - - extend ActorHasGuardedHandlers - execute_guarded_handlers_on_receiver - - extend ActiveSupport::Autoload - - autoload :Call - autoload :Component - - attr_reader :connection, :calls - - trap_exit :actor_died - - finalizer :finalize - - def initialize(connection) - @connection = connection - @calls, @components = {}, {} - setup_handlers - end - - def register_call(call) - @calls[call.id] ||= call - end - - def deregister_call(id) - @calls.delete id - end - - def call_with_id(call_id) - @calls[call_id] - end - - def register_component(component) - @components[component.id] ||= component - end - - def component_with_id(component_id) - @components[component_id] - end - - def setup_handlers - register_handler :es, RubyFS::Stream::Connected do - handle_pb_event Connection::Connected.new - throw :halt - end - - register_handler :es, RubyFS::Stream::Disconnected do - throw :halt - end - - register_handler :es, :event_name => 'CHANNEL_PARK' do |event| - throw :pass if es_event_known_call? event - call = Call.new event[:unique_id], current_actor, event.content.select { |k,v| k.to_s =~ /variable/ }, stream - link call - register_call call - call.async.send_offer - end - - register_handler :es, :event_name => ['CHANNEL_BRIDGE', 'CHANNEL_UNBRIDGE'], [:has_key?, :other_leg_unique_id] => true do |event| - call = call_with_id event[:other_leg_unique_id] - call.async.handle_es_event event if call - throw :pass - end - - register_handler :es, lambda { |event| es_event_known_call? event } do |event| - call = call_with_id event[:unique_id] - call.async.handle_es_event event - end - end - - def stream - connection.stream - end - - def finalize - @calls.values.each do |call| - safe_from_dead_actors do - call.terminate - end - end - end - - def handle_es_event(event) - trigger_handler :es, event - end - exclusive :handle_es_event - - def handle_pb_event(event) - connection.handle_event event - end - - def execute_command(command, options = {}) - command.request! - - command.target_call_id ||= options[:call_id] - command.component_id ||= options[:component_id] - - if command.target_call_id - execute_call_command command - elsif command.component_id - execute_component_command command - else - execute_global_command command - end - end - - def execute_call_command(command) - if call = call_with_id(command.target_call_id) - call.async.execute_command command - else - command.response = ProtocolError.new.setup :item_not_found, "Could not find a call with ID #{command.target_call_id}", command.target_call_id - end - end - - def execute_component_command(command) - if (component = component_with_id(command.component_id)) - component.async.execute_command command - else - command.response = ProtocolError.new.setup :item_not_found, "Could not find a component with ID #{command.component_id}", command.target_call_id, command.component_id - end - end - - def execute_global_command(command) - case command - when Punchblock::Command::Dial - call = Call.new_link Punchblock.new_uuid, current_actor, nil, stream - register_call call - call.async.dial command - else - command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command" - end - end - - def actor_died(actor, reason) - return unless reason - pb_logger.error "A linked actor (#{actor.inspect}) died due to #{reason.inspect}" - if id = @calls.key(actor) - @calls.delete id - end_event = Punchblock::Event::End.new :target_call_id => id, - :reason => :error - handle_pb_event end_event - end - end - - private - - def es_event_known_call?(event) - event[:unique_id] && call_with_id(event[:unique_id]) - end - end - end -end diff --git a/lib/punchblock/translator/freeswitch/call.rb b/lib/punchblock/translator/freeswitch/call.rb deleted file mode 100644 index 2e86f81d..00000000 --- a/lib/punchblock/translator/freeswitch/call.rb +++ /dev/null @@ -1,279 +0,0 @@ -# encoding: utf-8 - -module Punchblock - module Translator - class Freeswitch - class Call - include HasGuardedHandlers - include Celluloid - include DeadActorSafety - - extend ActorHasGuardedHandlers - execute_guarded_handlers_on_receiver - - HANGUP_CAUSE_TO_END_REASON = Hash.new :error - - HANGUP_CAUSE_TO_END_REASON['USER_BUSY'] = :busy - HANGUP_CAUSE_TO_END_REASON['MANAGER_REQUEST'] = :hangup_command - - %w{ - NORMAL_CLEARING ORIGINATOR_CANCEL SYSTEM_SHUTDOWN - BLIND_TRANSFER ATTENDED_TRANSFER PICKED_OFF NORMAL_UNSPECIFIED - }.each { |c| HANGUP_CAUSE_TO_END_REASON[c] = :hangup } - - %w{ - NO_USER_RESPONSE NO_ANSWER SUBSCRIBER_ABSENT ALLOTTED_TIMEOUT - MEDIA_TIMEOUT PROGRESS_TIMEOUT - }.each { |c| HANGUP_CAUSE_TO_END_REASON[c] = :timeout } - - %w{CALL_REJECTED NUMBER_CHANGED - REDIRECTION_TO_NEW_DESTINATION FACILITY_REJECTED NORMAL_CIRCUIT_CONGESTION - SWITCH_CONGESTION USER_NOT_REGISTERED FACILITY_NOT_SUBSCRIBED - OUTGOING_CALL_BARRED INCOMING_CALL_BARRED BEARERCAPABILITY_NOTAUTH - BEARERCAPABILITY_NOTAVAIL SERVICE_UNAVAILABLE BEARERCAPABILITY_NOTIMPL - CHAN_NOT_IMPLEMENTED FACILITY_NOT_IMPLEMENTED SERVICE_NOT_IMPLEMENTED - }.each { |c| HANGUP_CAUSE_TO_END_REASON[c] = :reject } - - REJECT_TO_HANGUP_REASON = Hash.new 'NORMAL_TEMPORARY_FAILURE' - REJECT_TO_HANGUP_REASON.merge! :busy => 'USER_BUSY', :decline => 'CALL_REJECTED' - - attr_reader :id, :translator, :es_env, :direction, :stream - - trap_exit :actor_died - - def initialize(id, translator, es_env = nil, stream = nil) - @id, @translator, @stream = id, translator, stream - @es_env = es_env || {} - @components = {} - @pending_joins, @pending_unjoins = {}, {} - @answered = false - setup_handlers - end - - def register_component(component) - @components[component.id] ||= component - end - - def component_with_id(component_id) - @components[component_id] - end - - def send_offer - @direction = :inbound - send_pb_event offer_event - end - - def to_s - "#<#{self.class}:#{id}>" - end - alias :inspect :to_s - - def setup_handlers - register_handler :es, :event_name => 'CHANNEL_ANSWER' do - @answered = true - send_pb_event Event::Answered.new - throw :pass - end - - register_handler :es, :event_name => 'CHANNEL_STATE', [:[], :channel_call_state] => 'RINGING' do - send_pb_event Event::Ringing.new - end - - register_handler :es, :event_name => 'CHANNEL_HANGUP' do |event| - @components.dup.each_pair do |id, component| - safe_from_dead_actors do - component.call_ended if component.alive? - end - end - send_end_event HANGUP_CAUSE_TO_END_REASON[event[:hangup_cause]] - end - - register_handler :es, :event_name => 'CHANNEL_BRIDGE' do |event| - command = @pending_joins[event[:other_leg_unique_id]] - command.response = true if command - - other_call_uri = event[:unique_id] == id ? event[:other_leg_unique_id] : event[:unique_id] - send_pb_event Event::Joined.new(:call_uri => other_call_uri) - end - - register_handler :es, :event_name => 'CHANNEL_UNBRIDGE' do |event| - command = @pending_unjoins[event[:other_leg_unique_id]] - command.response = true if command - - other_call_uri = event[:unique_id] == id ? event[:other_leg_unique_id] : event[:unique_id] - send_pb_event Event::Unjoined.new(:call_uri => other_call_uri) - end - - register_handler :es, [:has_key?, :scope_variable_punchblock_component_id] => true do |event| - if component = component_with_id(event[:scope_variable_punchblock_component_id]) - safe_from_dead_actors { component.handle_es_event event if component.alive? } - end - throw :pass - end - end - - def handle_es_event(event) - trigger_handler :es, event - end - - def application(*args) - stream.application id, *args - end - - def sendmsg(*args) - stream.sendmsg id, *args - end - - def uuid_foo(app, args = '') - stream.bgapi "uuid_#{app} #{id} #{args}" - end - - def dial(dial_command) - @direction = :outbound - - cid_number, cid_name = dial_command.from, nil - if dial_command.from - dial_command.from.match(/(?.*)<(?.*)>/) do |m| - cid_name = m[:cid_name].strip - cid_number = m[:cid_number] - end - end - - options = { - :return_ring_ready => true, - :origination_uuid => id - } - options[:origination_caller_id_number] = "'#{cid_number}'" if cid_number.present? - options[:origination_caller_id_name] = "'#{cid_name}'" if cid_name.present? - options[:originate_timeout] = dial_command.timeout/1000 if dial_command.timeout - dial_command.headers.each do |name, value| - options["sip_h_#{name}"] = "'#{value}'" - end - opts = options.inject([]) do |a, (k, v)| - a << "#{k}=#{v}" - end.join(',') - - stream.bgapi "originate {#{opts}}#{dial_command.to} &park()" - - dial_command.response = Ref.new uri: id - end - - def outbound? - direction == :outbound - end - - def inbound? - direction == :inbound - end - - def answered? - @answered - end - - def execute_command(command) - if command.component_id - if component = component_with_id(command.component_id) - component.execute_command command - else - command.response = ProtocolError.new.setup :item_not_found, "Could not find a component with ID #{command.component_id} for call #{id}", id, command.component_id - end - end - case command - when Command::Accept - application 'respond', '180 Ringing' - command.response = true - when Command::Answer - if answered? - command.response = true - else - command_id = Punchblock.new_uuid - register_tmp_handler :es, :event_name => 'CHANNEL_ANSWER', [:[], :scope_variable_punchblock_command_id] => command_id do - command.response = true - end - application 'answer', "%[punchblock_command_id=#{command_id}]" - end - when Command::Hangup - hangup - command.response = true - when Command::Join - @pending_joins[command.call_uri] = command - uuid_foo :bridge, command.call_uri - when Command::Unjoin - @pending_unjoins[command.call_uri] = command - uuid_foo :transfer, '-both park inline' - when Command::Reject - hangup REJECT_TO_HANGUP_REASON[command.reason] - command.response = true - when Punchblock::Component::Output - media_renderer = command.renderer || :freeswitch - case media_renderer.to_s - when 'freeswitch', 'native' - execute_component Component::Output, command - when 'flite' - execute_component Component::FliteOutput, command - else - execute_component Component::TTSOutput, command - end - when Punchblock::Component::Input - execute_component Component::Input, command - when Punchblock::Component::Record - execute_component Component::Record, command - else - command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command for call #{id}", id - end - end - - def hangup(reason = 'MANAGER_REQUEST') - sendmsg :call_command => 'hangup', :hangup_cause => reason - end - - def logger_id - "#{self.class}: #{id}" - end - - def actor_died(actor, reason) - return unless reason - pb_logger.error "A linked actor (#{actor.inspect}) died due to #{reason.inspect}" - if id = @components.key(actor) - @components.delete id - complete_event = Punchblock::Event::Complete.new :component_id => id, source_uri: id, :reason => Punchblock::Event::Complete::Error.new - send_pb_event complete_event - end - end - - private - - def send_end_event(reason) - send_pb_event Event::End.new(:reason => reason) - translator.deregister_call id - terminate - end - - def execute_component(type, command, *execute_args) - type.new_link(command, current_actor).tap do |component| - register_component component - component.execute(*execute_args) - end - end - - def send_pb_event(event) - event.target_call_id = id - translator.handle_pb_event event - end - - def offer_event - Event::Offer.new :to => es_env[:variable_sip_to_uri], - :from => "#{es_env[:variable_effective_caller_id_name]} <#{es_env[:variable_sip_from_uri]}>", - :headers => headers - end - - def headers - es_env.to_a.inject({}) do |accumulator, element| - accumulator['X-' + element[0].to_s] = element[1] || '' - accumulator - end - end - end - end - end -end diff --git a/lib/punchblock/translator/freeswitch/component.rb b/lib/punchblock/translator/freeswitch/component.rb deleted file mode 100644 index b7cdc5fd..00000000 --- a/lib/punchblock/translator/freeswitch/component.rb +++ /dev/null @@ -1,93 +0,0 @@ -# encoding: utf-8 - -module Punchblock - module Translator - class Freeswitch - module Component - extend ActiveSupport::Autoload - - autoload :AbstractOutput - autoload :FliteOutput - autoload :Input - autoload :Output - autoload :Record - autoload :TTSOutput - - class Component - include Celluloid - include DeadActorSafety - include HasGuardedHandlers - - extend ActorHasGuardedHandlers - execute_guarded_handlers_on_receiver - - attr_reader :id, :call, :call_id - - def initialize(component_node, call = nil) - @component_node, @call = component_node, call - @call_id = safe_from_dead_actors { call.id } if call - @id = Punchblock.new_uuid - @complete = false - setup - end - - def setup - end - - def execute_command(command) - command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command for component #{id}", call_id, id - end - - def handle_es_event(event) - trigger_handler :es, event - end - - def send_complete_event(reason, recording = nil) - return if @complete - @complete = true - event = Punchblock::Event::Complete.new reason: reason, recording: recording - send_event event - terminate - end - - def send_event(event) - event.component_id = id - event.target_call_id = call_id - event.source_uri = id - safe_from_dead_actors { translator.handle_pb_event event } - end - - def logger_id - "#{self.class}: #{call_id ? "Call ID: #{call_id}, Component ID: #{id}" : id}" - end - - def call_ended - send_complete_event Punchblock::Event::Complete::Hangup.new - end - - def application(appname, options = nil) - call.application appname, "%[punchblock_component_id=#{id}]#{options}" - end - - private - - def translator - call.translator - end - - def set_node_response(value) - @component_node.response = value - end - - def send_ref - set_node_response Ref.new uri: id - end - - def with_error(name, text) - set_node_response ProtocolError.new.setup(name, text) - end - end - end - end - end -end diff --git a/lib/punchblock/translator/freeswitch/component/input.rb b/lib/punchblock/translator/freeswitch/component/input.rb deleted file mode 100644 index 723fb752..00000000 --- a/lib/punchblock/translator/freeswitch/component/input.rb +++ /dev/null @@ -1,35 +0,0 @@ -# encoding: utf-8 - -module Punchblock - module Translator - class Freeswitch - module Component - class Input < Component - - include InputComponent - - def execute - super - @dtmf_handler_id = register_dtmf_event_handler - end - - private - - def register_dtmf_event_handler - component = current_actor - call.register_handler :es, :event_name => 'DTMF' do |event| - safe_from_dead_actors do - component.process_dtmf event[:dtmf_digit] - end - end - end - - def unregister_dtmf_event_handler - call.unregister_handler :es, @dtmf_handler_id if instance_variable_defined?(:@dtmf_handler_id) - rescue Celluloid::DeadActorError - end - end - end - end - end -end diff --git a/lib/punchblock/translator/freeswitch/component/record.rb b/lib/punchblock/translator/freeswitch/component/record.rb deleted file mode 100644 index 60dd84cc..00000000 --- a/lib/punchblock/translator/freeswitch/component/record.rb +++ /dev/null @@ -1,93 +0,0 @@ -# encoding: utf-8 - -module Punchblock - module Translator - class Freeswitch - module Component - class Record < Component - RECORDING_BASE_PATH = '/var/punchblock/record' - - def setup - @complete_reason = nil - end - - def execute - max_duration = @component_node.max_duration || -1 - initial_timeout = @component_node.initial_timeout || -1 - final_timeout = @component_node.final_timeout || -1 - - raise OptionError, 'A start-beep value of true is unsupported.' if @component_node.start_beep - raise OptionError, 'A start-paused value of true is unsupported.' if @component_node.start_paused - raise OptionError, 'A max-duration value that is negative (and not -1) is invalid.' unless max_duration >= -1 - - @format = @component_node.format || 'wav' - - component = current_actor - call.register_handler :es, :event_name => 'RECORD_STOP', [:[], :record_file_path] => filename do |event| - component.finished - end - - record_args = ['start', filename] - record_args << max_duration/1000 unless max_duration == -1 - - direction = case @component_node.direction - when :send then :RECORD_WRITE_ONLY - when :recv then :RECORD_READ_ONLY - else :RECORD_STEREO - end - setvar direction, true - - setvar :RECORD_INITIAL_TIMEOUT_MS, initial_timeout > -1 ? initial_timeout : 0 - setvar :RECORD_FINAL_TIMEOUT_MS, final_timeout > -1 ? final_timeout : 0 - - call.uuid_foo :record, record_args.join(' ') - send_ref - rescue OptionError => e - with_error 'option error', e.message - end - - def execute_command(command) - case command - when Punchblock::Component::Stop - call.uuid_foo :record, "stop #{filename}" - @complete_reason = stop_reason - command.response = true - else - super - end - end - - def finished - send_complete_event(@complete_reason || max_duration_reason) - end - - private - - def setvar(key, value) - call.uuid_foo :setvar, "#{key} #{value}" - end - - def filename - File.join RECORDING_BASE_PATH, [id, @format].join('.') - end - - def recording - Punchblock::Component::Record::Recording.new :uri => "file://#{filename}" - end - - def stop_reason - Punchblock::Event::Complete::Stop.new - end - - def max_duration_reason - Punchblock::Component::Record::Complete::MaxDuration.new - end - - def send_complete_event(reason) - super reason, recording - end - end - end - end - end -end diff --git a/spec/punchblock_spec.rb b/spec/punchblock_spec.rb index 298ddd71..0c064a8e 100644 --- a/spec/punchblock_spec.rb +++ b/spec/punchblock_spec.rb @@ -34,16 +34,6 @@ end end - context 'with :freeswitch' do - it 'sets up an Freeswitch connection, passing options, and a client with the connection attached' do - options = {:username => 'foo', :password => 'bar'} - expect(Punchblock::Connection::Freeswitch).to receive(:new).once.with(options).and_return mock_connection - client = Punchblock.client_with_connection :freeswitch, options - expect(client).to be_a Punchblock::Client - expect(client.connection).to be mock_connection - end - end - context 'with :yate' do it 'raises ArgumentError' do options = {:username => 'foo', :password => 'bar'}