punchblock 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/CHANGELOG.md +5 -0
  2. data/LICENSE.txt +3 -1
  3. data/bin/punchblock-console +19 -1
  4. data/lib/punchblock.rb +11 -4
  5. data/lib/punchblock/command/reject.rb +6 -2
  6. data/lib/punchblock/component.rb +1 -0
  7. data/lib/punchblock/component/asterisk.rb +10 -0
  8. data/lib/punchblock/component/asterisk/agi.rb +11 -0
  9. data/lib/punchblock/component/asterisk/agi/command.rb +157 -0
  10. data/lib/punchblock/component/asterisk/ami.rb +12 -0
  11. data/lib/punchblock/component/asterisk/ami/action.rb +144 -0
  12. data/lib/punchblock/component/input.rb +2 -2
  13. data/lib/punchblock/connection.rb +1 -0
  14. data/lib/punchblock/connection/asterisk.rb +31 -0
  15. data/lib/punchblock/core_ext/celluloid.rb +11 -0
  16. data/lib/punchblock/event.rb +1 -1
  17. data/lib/punchblock/event/asterisk.rb +9 -0
  18. data/lib/punchblock/event/asterisk/ami.rb +11 -0
  19. data/lib/punchblock/event/asterisk/ami/event.rb +66 -0
  20. data/lib/punchblock/event/complete.rb +20 -0
  21. data/lib/punchblock/event/dtmf.rb +19 -0
  22. data/lib/punchblock/event/end.rb +23 -0
  23. data/lib/punchblock/event/offer.rb +23 -0
  24. data/lib/punchblock/header.rb +4 -44
  25. data/lib/punchblock/key_value_pair_node.rb +50 -0
  26. data/lib/punchblock/rayo_node.rb +1 -1
  27. data/lib/punchblock/ref.rb +6 -0
  28. data/lib/punchblock/translator.rb +7 -0
  29. data/lib/punchblock/translator/asterisk.rb +74 -0
  30. data/lib/punchblock/translator/asterisk/ami_action.rb +86 -0
  31. data/lib/punchblock/translator/asterisk/call.rb +25 -0
  32. data/lib/punchblock/translator/asterisk/component.rb +11 -0
  33. data/lib/punchblock/version.rb +1 -1
  34. data/punchblock.gemspec +3 -1
  35. data/spec/punchblock/command/accept_spec.rb +8 -0
  36. data/spec/punchblock/command/answer_spec.rb +8 -0
  37. data/spec/punchblock/command/dial_spec.rb +22 -2
  38. data/spec/punchblock/command/hangup_spec.rb +8 -0
  39. data/spec/punchblock/command/join_spec.rb +21 -0
  40. data/spec/punchblock/command/mute_spec.rb +8 -0
  41. data/spec/punchblock/command/redirect_spec.rb +21 -0
  42. data/spec/punchblock/command/reject_spec.rb +19 -8
  43. data/spec/punchblock/command/unjoin_spec.rb +17 -0
  44. data/spec/punchblock/command/unmute_spec.rb +8 -0
  45. data/spec/punchblock/component/asterisk/agi/command_spec.rb +102 -0
  46. data/spec/punchblock/component/asterisk/ami/action_spec.rb +118 -0
  47. data/spec/punchblock/component/input_spec.rb +40 -0
  48. data/spec/punchblock/component/output_spec.rb +28 -0
  49. data/spec/punchblock/component/record_spec.rb +27 -0
  50. data/spec/punchblock/connection/asterisk_spec.rb +69 -0
  51. data/spec/punchblock/event/asterisk/ami/event_spec.rb +60 -0
  52. data/spec/punchblock/event/complete_spec.rb +8 -0
  53. data/spec/punchblock/event/dtmf_spec.rb +8 -0
  54. data/spec/punchblock/event/end_spec.rb +8 -0
  55. data/spec/punchblock/event/offer_spec.rb +15 -2
  56. data/spec/punchblock/ref_spec.rb +6 -0
  57. data/spec/punchblock/translator/asterisk/ami_action_spec.rb +149 -0
  58. data/spec/punchblock/translator/asterisk/call_spec.rb +18 -0
  59. data/spec/punchblock/translator/asterisk/component_spec.rb +11 -0
  60. data/spec/punchblock/translator/asterisk_spec.rb +150 -0
  61. data/spec/spec_helper.rb +42 -0
  62. metadata +92 -42
  63. data/lib/punchblock/event/info.rb +0 -15
  64. data/spec/punchblock/event/info_spec.rb +0 -30
@@ -0,0 +1,74 @@
1
+ require 'celluloid'
2
+ require 'punchblock/core_ext/celluloid'
3
+ require 'ruby_ami'
4
+
5
+ module Punchblock
6
+ module Translator
7
+ class Asterisk
8
+ include Celluloid
9
+
10
+ extend ActiveSupport::Autoload
11
+
12
+ autoload :AMIAction
13
+ autoload :Call
14
+ autoload :Component
15
+
16
+ attr_reader :ami_client, :connection
17
+
18
+ def initialize(ami_client, connection)
19
+ @ami_client, @connection = ami_client, connection
20
+ @calls, @components = {}, {}
21
+ end
22
+
23
+ def register_call(call)
24
+ @calls[call.id] ||= call
25
+ end
26
+
27
+ def call_with_id(call_id)
28
+ @calls[call_id]
29
+ end
30
+
31
+ def register_component(component)
32
+ @components[component.id] ||= component
33
+ end
34
+
35
+ def component_with_id(component_id)
36
+ @components[component_id]
37
+ end
38
+
39
+ def handle_ami_event(event)
40
+ return unless event.is_a? RubyAMI::Event
41
+ connection.handle_event Event::Asterisk::AMI::Event.new(:name => event.name, :attributes => event.headers)
42
+ end
43
+
44
+ def execute_command(command, options = {})
45
+ command.request!
46
+ if command.call_id || options[:call_id]
47
+ command.call_id ||= options[:call_id]
48
+ if command.component_id || options[:component_id]
49
+ command.component_id ||= options[:component_id]
50
+ execute_component_command command
51
+ else
52
+ execute_call_command command
53
+ end
54
+ else
55
+ execute_global_command command
56
+ end
57
+ end
58
+
59
+ def execute_call_command(command)
60
+ call_with_id(command.call_id).execute_command command
61
+ end
62
+
63
+ def execute_component_command(command)
64
+ call_with_id(command.call_id).execute_component_command command
65
+ end
66
+
67
+ def execute_global_command(command)
68
+ component = AMIAction.new command, ami_client
69
+ # register_component component
70
+ component.execute!
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,86 @@
1
+ module Punchblock
2
+ module Translator
3
+ class Asterisk
4
+ class AMIAction < Component
5
+ attr_reader :action
6
+
7
+ def initialize(component_node, ami_client)
8
+ @component_node, @ami_client = component_node, ami_client
9
+ @action = create_action
10
+ @id = @action.action_id
11
+ end
12
+
13
+ def execute
14
+ send_action
15
+ send_ref
16
+ end
17
+
18
+ private
19
+
20
+ def create_action
21
+ headers = {}
22
+ @component_node.params_hash.each_pair do |key, value|
23
+ headers[key.to_s.capitalize] = value
24
+ end
25
+ RubyAMI::Action.new @component_node.name, headers do |response|
26
+ handle_response response
27
+ end
28
+ end
29
+
30
+ def send_action
31
+ @ami_client.send_action @action
32
+ end
33
+
34
+ def send_ref
35
+ @component_node.response = Ref.new :id => @action.action_id
36
+ end
37
+
38
+ def handle_response(response)
39
+ case response
40
+ when RubyAMI::Error
41
+ send_event error_event(response)
42
+ when RubyAMI::Response
43
+ send_events
44
+ send_event complete_event(response)
45
+ end
46
+ end
47
+
48
+ def error_event(response)
49
+ Punchblock::Event::Complete.new.tap do |c|
50
+ c.reason = Punchblock::Event::Complete::Error.new :details => response.message
51
+ end
52
+ end
53
+
54
+ def complete_event(response)
55
+ headers = response.headers
56
+ headers.merge! @extra_complete_attributes if @extra_complete_attributes
57
+ headers.delete 'ActionID'
58
+ Punchblock::Event::Complete.new.tap do |c|
59
+ c.reason = Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new :message => headers.delete('Message'), :attributes => headers
60
+ end
61
+ end
62
+
63
+ def send_events
64
+ return unless @action.has_causal_events?
65
+ @action.events.each do |e|
66
+ if e.name.downcase == @action.causal_event_terminator_name
67
+ @extra_complete_attributes = e.headers
68
+ else
69
+ send_event pb_event_from_ami_event(e)
70
+ end
71
+ end
72
+ end
73
+
74
+ def pb_event_from_ami_event(ami_event)
75
+ headers = ami_event.headers
76
+ headers.delete 'ActionID'
77
+ Event::Asterisk::AMI::Event.new :name => ami_event.name, :attributes => headers
78
+ end
79
+
80
+ def send_event(event)
81
+ @component_node.add_event event
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,25 @@
1
+ module Punchblock
2
+ module Translator
3
+ class Asterisk
4
+ class Call
5
+ include Celluloid
6
+
7
+ def initialize
8
+ @components = {}
9
+ end
10
+
11
+ def register_component(component)
12
+ @components[component.id] ||= component
13
+ end
14
+
15
+ def component_with_id(component_id)
16
+ @components[component_id]
17
+ end
18
+
19
+ def execute_component_command(command)
20
+ component_with_id(command.component_id).execute_command command
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ module Punchblock
2
+ module Translator
3
+ class Asterisk
4
+ class Component
5
+ include Celluloid
6
+
7
+ attr_reader :id
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module Punchblock
2
- VERSION = "0.5.1"
2
+ VERSION = "0.6.0"
3
3
  end
data/punchblock.gemspec CHANGED
@@ -29,9 +29,11 @@ Gem::Specification.new do |s|
29
29
  s.add_runtime_dependency %q<state_machine>, [">= 1.0.1"]
30
30
  s.add_runtime_dependency %q<future-resource>, [">= 0.0.2"]
31
31
  s.add_runtime_dependency %q<has-guarded-handlers>, [">= 0.1.0"]
32
+ s.add_runtime_dependency %q<celluloid>, [">= 0.5.0"]
33
+ s.add_runtime_dependency %q<ruby_ami>, [">= 0.1.2"]
32
34
 
33
35
  s.add_development_dependency %q<bundler>, ["~> 1.0.0"]
34
- s.add_development_dependency %q<rspec>, ["~> 2.3.0"]
36
+ s.add_development_dependency %q<rspec>, [">= 2.5.0"]
35
37
  s.add_development_dependency %q<ci_reporter>, [">= 1.6.3"]
36
38
  s.add_development_dependency %q<yard>, ["~> 0.6.0"]
37
39
  s.add_development_dependency %q<rcov>, [">= 0"]
@@ -8,6 +8,14 @@ module Punchblock
8
8
  end
9
9
 
10
10
  it_should_behave_like 'command_headers'
11
+
12
+ describe "from a stanza" do
13
+ let(:stanza) { '<accept xmlns="urn:xmpp:rayo:1"/>' }
14
+
15
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
16
+
17
+ it { should be_instance_of Accept }
18
+ end
11
19
  end
12
20
  end
13
21
  end # Punchblock
@@ -8,6 +8,14 @@ module Punchblock
8
8
  end
9
9
 
10
10
  it_should_behave_like 'command_headers'
11
+
12
+ describe "from a stanza" do
13
+ let(:stanza) { '<answer xmlns="urn:xmpp:rayo:1"/>' }
14
+
15
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
16
+
17
+ it { should be_instance_of Answer }
18
+ end
11
19
  end
12
20
  end
13
21
  end # Punchblock
@@ -14,9 +14,9 @@ module Punchblock
14
14
  RayoNode.class_from_registration(:dial, 'urn:xmpp:rayo:1').should == Dial
15
15
  end
16
16
 
17
- describe "when setting options in initializer" do
18
- let(:join_params) { {:other_call_id => 'abc123'} }
17
+ let(:join_params) { {:other_call_id => 'abc123'} }
19
18
 
19
+ describe "when setting options in initializer" do
20
20
  subject { Dial.new :to => 'tel:+14155551212', :from => 'tel:+13035551212', :headers => { :x_skill => 'agent', :x_customer_id => 8877 }, :join => join_params }
21
21
 
22
22
  it_should_behave_like 'command_headers'
@@ -26,6 +26,26 @@ module Punchblock
26
26
  its(:join) { should == Join.new(join_params) }
27
27
  end
28
28
 
29
+ describe "from a stanza" do
30
+ let :stanza do
31
+ <<-MESSAGE
32
+ <dial to='tel:+14155551212' from='tel:+13035551212' xmlns='urn:xmpp:rayo:1'>
33
+ <join call-id="abc123" />
34
+ <header name="x-skill" value="agent" />
35
+ <header name="x-customer-id" value="8877" />
36
+ </dial>
37
+ MESSAGE
38
+ end
39
+
40
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
41
+
42
+ it { should be_instance_of Dial }
43
+
44
+ its(:to) { should == 'tel:+14155551212' }
45
+ its(:from) { should == 'tel:+13035551212' }
46
+ its(:join) { should == Join.new(join_params) }
47
+ end
48
+
29
49
  describe "#response=" do
30
50
  before { subject.request! }
31
51
 
@@ -8,6 +8,14 @@ module Punchblock
8
8
  end
9
9
 
10
10
  it_should_behave_like 'command_headers'
11
+
12
+ describe "from a stanza" do
13
+ let(:stanza) { '<hangup xmlns="urn:xmpp:rayo:1"/>' }
14
+
15
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
16
+
17
+ it { should be_instance_of Hangup }
18
+ end
11
19
  end
12
20
  end
13
21
  end # Punchblock
@@ -16,6 +16,27 @@ module Punchblock
16
16
  its(:direction) { should == :duplex }
17
17
  its(:media) { should == :bridge }
18
18
  end
19
+
20
+ describe "from a stanza" do
21
+ let :stanza do
22
+ <<-MESSAGE
23
+ <join xmlns="urn:xmpp:rayo:1"
24
+ call-id="abc123"
25
+ mixer-id="blah"
26
+ direction="duplex"
27
+ media="bridge" />
28
+ MESSAGE
29
+ end
30
+
31
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
32
+
33
+ it { should be_instance_of Join }
34
+
35
+ its(:other_call_id) { should == 'abc123' }
36
+ its(:mixer_id) { should == 'blah' }
37
+ its(:direction) { should == :duplex }
38
+ its(:media) { should == :bridge }
39
+ end
19
40
  end
20
41
  end
21
42
  end # Punchblock
@@ -6,6 +6,14 @@ module Punchblock
6
6
  it 'registers itself' do
7
7
  RayoNode.class_from_registration(:mute, 'urn:xmpp:rayo:1').should == Mute
8
8
  end
9
+
10
+ describe "from a stanza" do
11
+ let(:stanza) { '<mute xmlns="urn:xmpp:rayo:1"/>' }
12
+
13
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
14
+
15
+ it { should be_instance_of Mute }
16
+ end
9
17
  end
10
18
  end
11
19
  end # Punchblock
@@ -14,6 +14,27 @@ module Punchblock
14
14
 
15
15
  its(:to) { should == 'tel:+14045551234' }
16
16
  end
17
+
18
+ describe "from a stanza" do
19
+ let :stanza do
20
+ <<-MESSAGE
21
+ <redirect xmlns='urn:xmpp:rayo:1'
22
+ to='tel:+14045551234'>
23
+ <!-- Signaling (e.g. SIP) Headers -->
24
+ <header name="x-skill" value="agent" />
25
+ <header name="x-customer-id" value="8877" />
26
+ </redirect>
27
+ MESSAGE
28
+ end
29
+
30
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
31
+
32
+ it { should be_instance_of Redirect }
33
+
34
+ it_should_behave_like 'command_headers'
35
+
36
+ its(:to) { should == 'tel:+14045551234' }
37
+ end
17
38
  end # Redirect
18
39
  end # Command
19
40
  end # Punchblock
@@ -15,19 +15,30 @@ module Punchblock
15
15
  its(:reason) { should == :busy }
16
16
  end
17
17
 
18
+ describe "from a stanza" do
19
+ let :stanza do
20
+ <<-MESSAGE
21
+ <reject xmlns='urn:xmpp:rayo:1'>
22
+ <busy />
23
+ <!-- Sample Headers (optional) -->
24
+ <header name="x-reason-internal" value="bad-skill" />
25
+ </reject>
26
+ MESSAGE
27
+ end
28
+
29
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
30
+
31
+ it { should be_instance_of Reject }
32
+
33
+ its(:reason) { should == :busy }
34
+ its(:headers_hash) { should == { :x_reason_internal => 'bad-skill' } }
35
+ end
36
+
18
37
  describe "with the reason" do
19
38
  [:decline, :busy, :error].each do |reason|
20
39
  describe reason do
21
40
  subject { Reject.new :reason => reason }
22
41
 
23
- let :expected_message do
24
- <<-MESSAGE
25
- <reject xmlns="urn:xmpp:rayo:1">
26
- <#{reason}/>
27
- </reject>
28
- MESSAGE
29
- end
30
-
31
42
  its(:reason) { should == reason }
32
43
  end
33
44
  end
@@ -14,6 +14,23 @@ module Punchblock
14
14
  its(:other_call_id) { should == 'abc123' }
15
15
  its(:mixer_id) { should == 'blah' }
16
16
  end
17
+
18
+ describe "from a stanza" do
19
+ let :stanza do
20
+ <<-MESSAGE
21
+ <unjoin xmlns="urn:xmpp:rayo:1"
22
+ call-id="abc123"
23
+ mixer-id="blah" />
24
+ MESSAGE
25
+ end
26
+
27
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
28
+
29
+ it { should be_instance_of Unjoin }
30
+
31
+ its(:other_call_id) { should == 'abc123' }
32
+ its(:mixer_id) { should == 'blah' }
33
+ end
17
34
  end
18
35
  end
19
36
  end # Punchblock