punchblock 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore CHANGED
@@ -7,7 +7,6 @@ pkg/*
7
7
  .rvmrc
8
8
  .yardoc
9
9
  doc
10
- log/*
11
10
  spec/reports
12
11
  vendor
13
12
  .rbx/
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # develop
2
2
 
3
+ # v0.7.0 - 2011-11-22
4
+ * Bugfix: Some spec mistakes
5
+ * Feature: Allow execution of actions against global components on Asterisk
6
+ * API change: The console has been removed
7
+ * API change: Components no longer expose a FutureResource at #complete_event, and instead wrap its API in the same way as #response and #response=. Any consumer code which does some_component.complete_event.resource or some_component.complete_event.resource= should now use some_component.complete_event and some_component.complete_event=
8
+ * Feature: Added the max-silence attribute to the Input component
9
+ * Bugfix: Bump the Celluloid dependency to avoid spec failures on JRuby and monkey-patching for mockability
10
+ * API change: Event handlers registered on components are no longer triggered by incoming events internally to Punchblock. These events must be consumed via a Client's event handlers or event queue and manually triggered on a component using ComponentNode#trigger_event_handler
11
+
3
12
  # v0.6.2
4
13
  # Feature: Added basic support for running Punchblock apps on Asterisk. Calls coming in to AsyncAGI result in the client receiving an Offer, hangup events are sent, and accept/answer/hangup commands work.
5
14
  # API change: The logger is now set using Punchblock.logger= rather than as a hash key to Connection.new
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard 'rspec', :version => 2, :cli => '--format documentation' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec/" }
5
+ end
@@ -53,8 +53,8 @@ module Punchblock
53
53
  pb_logger.debug "Executing command: #{command.inspect} with options #{options.inspect}"
54
54
  async = options.has_key?(:async) ? options.delete(:async) : true
55
55
  command.client = self
56
- if command.respond_to?(:register_event_handler)
57
- command.register_event_handler do |event|
56
+ if command.respond_to?(:register_handler)
57
+ command.register_handler :internal do |event|
58
58
  trigger_handler :event, event
59
59
  end
60
60
  end
@@ -0,0 +1,72 @@
1
+ module Punchblock
2
+ module Component
3
+ class ComponentNode < CommandNode
4
+ include HasGuardedHandlers
5
+
6
+ def initialize(*args)
7
+ super
8
+ @complete_event_resource = FutureResource.new
9
+ register_internal_handlers
10
+ end
11
+
12
+ def register_internal_handlers
13
+ register_handler :internal, Event::Complete do |event|
14
+ self.complete_event = event
15
+ throw :pass
16
+ end
17
+ end
18
+
19
+ def add_event(event)
20
+ trigger_handler :internal, event
21
+ end
22
+
23
+ def trigger_event_handler(event)
24
+ trigger_handler :event, event
25
+ end
26
+
27
+ def register_event_handler(*guards, &block)
28
+ register_handler :event, *guards, &block
29
+ end
30
+
31
+ def write_action(action)
32
+ client.execute_command action, :call_id => call_id, :component_id => component_id
33
+ action
34
+ end
35
+
36
+ def response=(other)
37
+ if other.is_a?(Ref)
38
+ @component_id = other.id
39
+ client.register_component self if client
40
+ end
41
+ super
42
+ end
43
+
44
+ def complete_event(timeout = nil)
45
+ @complete_event_resource.resource timeout
46
+ end
47
+
48
+ def complete_event=(other)
49
+ return if @complete_event_resource.set_yet?
50
+ @complete_event_resource.resource = other
51
+ complete!
52
+ end
53
+
54
+ ##
55
+ # Create an Rayo stop message
56
+ #
57
+ # @return [Stop] an Rayo stop message
58
+ #
59
+ def stop_action
60
+ Stop.new :component_id => component_id, :call_id => call_id
61
+ end
62
+
63
+ ##
64
+ # Sends an Rayo stop message for the current component
65
+ #
66
+ def stop!(options = {})
67
+ raise InvalidActionError, "Cannot stop a #{self.class.name.split("::").last} that is not executing" unless executing?
68
+ stop_action.tap { |action| write_action action }
69
+ end
70
+ end
71
+ end
72
+ end
@@ -50,6 +50,20 @@ module Punchblock
50
50
  write_attr :'max-digits', other
51
51
  end
52
52
 
53
+ ##
54
+ # @return [Integer] the amount of time in milliseconds that an input command will wait until considered that a silence becomes a NO-MATCH
55
+ #
56
+ def max_silence
57
+ read_attr :'max-silence', :to_i
58
+ end
59
+
60
+ ##
61
+ # @param [Integer] other the amount of time in milliseconds that an input command will wait until considered that a silence becomes a NO-MATCH
62
+ #
63
+ def max_silence=(other)
64
+ write_attr :'max-silence', other
65
+ end
66
+
53
67
  ##
54
68
  # @return [Float] Confidence with which to consider a response acceptable
55
69
  #
@@ -0,0 +1,7 @@
1
+ module Punchblock
2
+ module Component
3
+ class Stop < CommandNode # :nodoc:
4
+ register :stop, :core
5
+ end
6
+ end
7
+ end
@@ -151,12 +151,12 @@ module Punchblock
151
151
  end
152
152
 
153
153
  def register_hold_status_handlers
154
- register_event_handler OnHold do |event|
154
+ register_handler :internal, OnHold do |event|
155
155
  onhold!
156
156
  throw :pass
157
157
  end
158
158
 
159
- register_event_handler OffHold do |event|
159
+ register_handler :internal, OffHold do |event|
160
160
  offhold!
161
161
  throw :pass
162
162
  end
@@ -3,74 +3,13 @@ module Punchblock
3
3
  extend ActiveSupport::Autoload
4
4
 
5
5
  autoload :Asterisk
6
+ autoload :ComponentNode
6
7
  autoload :Input
7
8
  autoload :Output
8
9
  autoload :Record
10
+ autoload :Stop
9
11
  autoload :Tropo
10
12
 
11
13
  InvalidActionError = Class.new StandardError
12
-
13
- class ComponentNode < CommandNode
14
- include HasGuardedHandlers
15
-
16
- attr_accessor :complete_event
17
-
18
- def initialize(*args)
19
- super
20
- @complete_event = FutureResource.new
21
- register_initial_handlers
22
- end
23
-
24
- def register_initial_handlers
25
- register_event_handler Event::Complete do |event|
26
- complete!
27
- complete_event.resource = event
28
- throw :pass
29
- end
30
- end
31
-
32
- def add_event(event)
33
- event.original_component = self
34
- trigger_handler :event, event
35
- end
36
-
37
- def register_event_handler(*guards, &block)
38
- register_handler :event, *guards, &block
39
- end
40
-
41
- def write_action(action)
42
- client.execute_command action, :call_id => call_id, :component_id => component_id
43
- action
44
- end
45
-
46
- def response=(other)
47
- if other.is_a?(Ref)
48
- @component_id = other.id
49
- client.register_component self if client
50
- end
51
- super
52
- end
53
-
54
- ##
55
- # Create an Rayo stop message
56
- #
57
- # @return [Stop] an Rayo stop message
58
- #
59
- def stop_action
60
- Stop.new :component_id => component_id, :call_id => call_id
61
- end
62
-
63
- ##
64
- # Sends an Rayo stop message for the current component
65
- #
66
- def stop!(options = {})
67
- raise InvalidActionError, "Cannot stop a #{self.class.name.split("::").last} that is not executing" unless executing?
68
- stop_action.tap { |action| write_action action }
69
- end
70
- end
71
-
72
- class Stop < CommandNode # :nodoc:
73
- register :stop, :core
74
- end
75
14
  end # Component
76
15
  end # Punchblock
@@ -13,7 +13,7 @@ module Punchblock
13
13
  end
14
14
 
15
15
  def run
16
- logger.debug "Starting the RubyAMI client"
16
+ pb_logger.debug "Starting the RubyAMI client"
17
17
  ami_client.start
18
18
  end
19
19
 
@@ -23,10 +23,6 @@ module Punchblock
23
23
  @components[component_id]
24
24
  end
25
25
 
26
- def execute_component_command(command)
27
- component_with_id(command.component_id).execute_command! command
28
- end
29
-
30
26
  def send_offer
31
27
  send_pb_event offer_event
32
28
  end
@@ -42,12 +38,17 @@ module Punchblock
42
38
  if component = component_with_id(ami_event['CommandID'])
43
39
  pb_logger.debug "Found component #{component.id} for event. Forwarding event..."
44
40
  component.handle_ami_event! ami_event
41
+ else
42
+ pb_logger.debug "Could not find component for AMI event: #{ami_event}"
45
43
  end
46
44
  end
47
45
  end
48
46
 
49
47
  def execute_command(command)
50
48
  pb_logger.debug "Executing command: #{command.inspect}"
49
+ if command.component_id
50
+ component_with_id(command.component_id).execute_command! command
51
+ end
51
52
  case command
52
53
  when Command::Accept
53
54
  send_agi_action 'EXEC RINGING' do |response|
@@ -70,7 +71,7 @@ module Punchblock
70
71
  pb_logger.debug "Sending AGI action #{command}"
71
72
  @current_agi_command = Punchblock::Component::Asterisk::AGI::Command.new :name => command, :call_id => id
72
73
  @current_agi_command.request!
73
- @current_agi_command.register_event_handler Punchblock::Event::Complete do |e|
74
+ @current_agi_command.register_handler :internal, Punchblock::Event::Complete do |e|
74
75
  pb_logger.debug "AGI action received complete event #{e.inspect}"
75
76
  block.call e
76
77
  end
@@ -1,5 +1,4 @@
1
1
  require 'celluloid'
2
- require 'punchblock/core_ext/celluloid'
3
2
  require 'ruby_ami'
4
3
 
5
4
  module Punchblock
@@ -71,14 +70,14 @@ module Punchblock
71
70
  def execute_command(command, options = {})
72
71
  pb_logger.debug "Executing command #{command.inspect}"
73
72
  command.request!
74
- if command.call_id || options[:call_id]
75
- command.call_id ||= options[:call_id]
76
- if command.component_id || options[:component_id]
77
- command.component_id ||= options[:component_id]
78
- execute_component_command command
79
- else
80
- execute_call_command command
81
- end
73
+
74
+ command.call_id ||= options[:call_id]
75
+ command.component_id ||= options[:component_id]
76
+
77
+ if command.call_id
78
+ execute_call_command command
79
+ elsif command.component_id
80
+ execute_component_command command
82
81
  else
83
82
  execute_global_command command
84
83
  end
@@ -89,12 +88,12 @@ module Punchblock
89
88
  end
90
89
 
91
90
  def execute_component_command(command)
92
- call_with_id(command.call_id).execute_component_command! command
91
+ component_with_id(command.component_id).execute_command! command
93
92
  end
94
93
 
95
94
  def execute_global_command(command)
96
95
  component = Component::Asterisk::AMIAction.new command, current_actor
97
- # register_component component
96
+ register_component component
98
97
  component.execute!
99
98
  end
100
99
 
@@ -1,3 +1,3 @@
1
1
  module Punchblock
2
- VERSION = "0.6.2"
2
+ VERSION = "0.7.0"
3
3
  end
data/lib/punchblock.rb CHANGED
@@ -15,7 +15,6 @@ module Punchblock
15
15
  autoload :CommandNode
16
16
  autoload :Component
17
17
  autoload :Connection
18
- autoload :DSL
19
18
  autoload :HasHeaders
20
19
  autoload :Header
21
20
  autoload :MediaContainer
data/punchblock.gemspec CHANGED
@@ -24,13 +24,12 @@ Gem::Specification.new do |s|
24
24
 
25
25
  s.add_runtime_dependency %q<niceogiri>, [">= 0.0.4"]
26
26
  s.add_runtime_dependency %q<blather>, [">= 0.5.7"]
27
- s.add_runtime_dependency %q<pry>, [">= 0.8.3"]
28
27
  s.add_runtime_dependency %q<activesupport>, [">= 2.1.0"]
29
28
  s.add_runtime_dependency %q<state_machine>, [">= 1.0.1"]
30
29
  s.add_runtime_dependency %q<future-resource>, [">= 0.0.2"]
31
30
  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"]
31
+ s.add_runtime_dependency %q<celluloid>, [">= 0.6.0"]
32
+ s.add_runtime_dependency %q<ruby_ami>, [">= 0.1.3"]
34
33
 
35
34
  s.add_development_dependency %q<bundler>, ["~> 1.0.0"]
36
35
  s.add_development_dependency %q<rspec>, [">= 2.5.0"]
@@ -41,4 +40,5 @@ Gem::Specification.new do |s|
41
40
  s.add_development_dependency %q<mocha>, [">= 0"]
42
41
  s.add_development_dependency %q<i18n>, [">= 0"]
43
42
  s.add_development_dependency %q<countdownlatch>, [">= 0"]
43
+ s.add_development_dependency %q<guard-rspec>
44
44
  end
@@ -1,17 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
- %w{
4
- blather/client/dsl
5
- punchblock/core_ext/blather/stanza
6
- punchblock/core_ext/blather/stanza/presence
7
- }.each { |f| require f }
8
-
9
3
  module Punchblock
10
4
  module Component
11
5
  describe ComponentNode do
12
- it "should not initially have a complete event set" do
13
- subject.complete_event.set_yet?.should == false
14
- end
6
+ it { should be_new }
15
7
 
16
8
  describe "#add_event" do
17
9
  let(:event) { Event::Complete.new }
@@ -23,16 +15,10 @@ module Punchblock
23
15
 
24
16
  let(:add_event) { subject.add_event event }
25
17
 
26
- it "should set the original component on the event" do
27
- add_event
28
- event.original_component.should == subject
29
- end
30
-
31
18
  describe "with a complete event" do
32
19
  it "should set the complete event resource" do
33
20
  add_event
34
- subject.complete_event.set_yet?.should == true
35
- subject.complete_event.resource.should == event
21
+ subject.complete_event(0.5).should == event
36
22
  end
37
23
 
38
24
  it "should call #complete!" do
@@ -46,9 +32,18 @@ module Punchblock
46
32
 
47
33
  it "should not set the complete event resource" do
48
34
  add_event
49
- subject.complete_event.set_yet?.should == false
35
+ subject.should_not be_complete
50
36
  end
51
37
  end
38
+ end # #add_event
39
+
40
+ describe "#trigger_event_handler" do
41
+ let(:event) { Event::Complete.new }
42
+
43
+ before do
44
+ subject.request!
45
+ subject.execute!
46
+ end
52
47
 
53
48
  describe "with an event handler set" do
54
49
  let(:handler) { mock 'Response' }
@@ -59,10 +54,10 @@ module Punchblock
59
54
  end
60
55
 
61
56
  it "should trigger the callback" do
62
- add_event
57
+ subject.trigger_event_handler event
63
58
  end
64
59
  end
65
- end # #add_event
60
+ end # #trigger_event_handler
66
61
 
67
62
  describe "#response=" do
68
63
  before do
@@ -84,6 +79,24 @@ module Punchblock
84
79
  subject.client.find_component_by_id(component_id).should be subject
85
80
  end
86
81
  end
82
+
83
+ describe "#complete_event=" do
84
+ before do
85
+ subject.request!
86
+ subject.execute!
87
+ end
88
+
89
+ it "should set the command to executing status" do
90
+ subject.complete_event = :foo
91
+ subject.should be_complete
92
+ end
93
+
94
+ it "should be a no-op if the response has already been set" do
95
+ subject.complete_event = :foo
96
+ lambda { subject.complete_event = :bar }.should_not raise_error
97
+ subject.complete_event(0.5).should == :foo
98
+ end
99
+ end
87
100
  end # ComponentNode
88
101
  end # Component
89
102
  end # Punchblock
@@ -13,6 +13,7 @@ module Punchblock
13
13
  :mode => :speech,
14
14
  :terminator => '#',
15
15
  :max_digits => 10,
16
+ :max_silence => 1000,
16
17
  :recognizer => 'en-US',
17
18
  :initial_timeout => 2000,
18
19
  :inter_digit_timeout => 2000,
@@ -27,6 +28,7 @@ module Punchblock
27
28
  its(:mode) { should == :speech }
28
29
  its(:terminator) { should == '#' }
29
30
  its(:max_digits) { should == 10 }
31
+ its(:max_silence) { should == 1000 }
30
32
  its(:recognizer) { should == 'en-US' }
31
33
  its(:initial_timeout) { should == 2000 }
32
34
  its(:inter_digit_timeout) { should == 2000 }
@@ -44,6 +46,7 @@ module Punchblock
44
46
  mode="speech"
45
47
  terminator="#"
46
48
  max-digits="10"
49
+ max-silence="1000"
47
50
  recognizer="en-US"
48
51
  initial-timeout="2000"
49
52
  inter-digit-timeout="2000"
@@ -67,6 +70,7 @@ module Punchblock
67
70
  its(:mode) { should == :speech }
68
71
  its(:terminator) { should == '#' }
69
72
  its(:max_digits) { should == 10 }
73
+ its(:max_silence) { should == 1000 }
70
74
  its(:recognizer) { should == 'en-US' }
71
75
  its(:initial_timeout) { should == 2000 }
72
76
  its(:inter_digit_timeout) { should == 2000 }
@@ -14,7 +14,9 @@ module Punchblock
14
14
 
15
15
  let(:mock_event_handler) { stub_everything 'Event Handler' }
16
16
 
17
- subject { Asterisk.new options }
17
+ let(:connection) { Asterisk.new options }
18
+
19
+ subject { connection }
18
20
 
19
21
  before do
20
22
  subject.event_handler = mock_event_handler
@@ -22,6 +24,8 @@ module Punchblock
22
24
 
23
25
  its(:ami_client) { should be_a RubyAMI::Client }
24
26
 
27
+ after { connection.translator.terminate }
28
+
25
29
  it 'should set the connection on the translator' do
26
30
  subject.translator.connection.should be subject
27
31
  end
@@ -76,7 +76,7 @@ module Punchblock
76
76
  MSG
77
77
 
78
78
  connection.__send__ :handle_presence, example_complete
79
- say.complete_event.resource.source.should == say
79
+ say.complete_event(0.5).source.should == say
80
80
 
81
81
  say.component_id.should == 'fgh4590'
82
82
  end
@@ -112,14 +112,12 @@ module Punchblock
112
112
  end
113
113
 
114
114
  let(:ami_event) do
115
- RubyAMI::Event.new("AGIExec").tap do |e|
115
+ RubyAMI::Event.new("AsyncAGI").tap do |e|
116
116
  e["SubEvent"] = "End"
117
117
  e["Channel"] = "SIP/1234-00000000"
118
- e["CommandId"] = component.id
118
+ e["CommandID"] = component.id
119
119
  e["Command"] = "EXEC ANSWER"
120
- e["ResultCode"] = "200"
121
- e["Result"] = "Success"
122
- e["Data"] = "FOO"
120
+ e["Result"] = "200%20result=123%20(timeout)%0A"
123
121
  end
124
122
  end
125
123
 
@@ -152,7 +150,7 @@ module Punchblock
152
150
 
153
151
  it "should send an EXEC RINGING AGI command and set the command's response" do
154
152
  subject.execute_command command
155
- agi_command = subject.actor_subject.instance_variable_get(:'@current_agi_command')
153
+ agi_command = subject.wrapped_object.instance_variable_get(:'@current_agi_command')
156
154
  agi_command.name.should == "EXEC RINGING"
157
155
  agi_command.execute!
158
156
  agi_command.add_event expected_agi_complete_event
@@ -165,7 +163,7 @@ module Punchblock
165
163
 
166
164
  it "should send an EXEC ANSWER AGI command and set the command's response" do
167
165
  subject.execute_command command
168
- agi_command = subject.actor_subject.instance_variable_get(:'@current_agi_command')
166
+ agi_command = subject.wrapped_object.instance_variable_get(:'@current_agi_command')
169
167
  agi_command.name.should == "EXEC ANSWER"
170
168
  agi_command.execute!
171
169
  agi_command.add_event expected_agi_complete_event
@@ -178,7 +176,7 @@ module Punchblock
178
176
 
179
177
  it "should send a Hangup AMI command and set the command's response" do
180
178
  subject.execute_command command
181
- ami_action = subject.actor_subject.instance_variable_get(:'@current_ami_action')
179
+ ami_action = subject.wrapped_object.instance_variable_get(:'@current_ami_action')
182
180
  ami_action.name.should == "hangup"
183
181
  ami_action << RubyAMI::Response.new
184
182
  command.response(0.5).should be true
@@ -198,12 +196,31 @@ module Punchblock
198
196
  subject.execute_command command
199
197
  end
200
198
  end
199
+
200
+ context 'with a component command' do
201
+ let(:component_id) { 'foobar' }
202
+
203
+ let :command do
204
+ Punchblock::Component::Stop.new :component_id => component_id
205
+ end
206
+
207
+ let :mock_component do
208
+ mock 'Component', :id => component_id
209
+ end
210
+
211
+ before { subject.register_component mock_component }
212
+
213
+ it 'should send the command to the component for execution' do
214
+ mock_component.expects(:execute_command!).once
215
+ subject.execute_command command
216
+ end
217
+ end
201
218
  end
202
219
 
203
220
  describe '#send_agi_action' do
204
221
  it 'should send an appropriate AsyncAGI AMI action' do
205
222
  pending
206
- subject.actor_subject.expects(:send_ami_action).once.with('AGI', 'Command' => 'FOO', 'Channel' => subject.channel)
223
+ subject.wrapped_object.expects(:send_ami_action).once.with('AGI', 'Command' => 'FOO', 'Channel' => subject.channel)
207
224
  subject.send_agi_action 'FOO'
208
225
  end
209
226
  end
@@ -93,7 +93,7 @@ module Punchblock
93
93
 
94
94
  command.should be_complete
95
95
 
96
- complete_event = command.complete_event.resource(0.5)
96
+ complete_event = command.complete_event 0.5
97
97
 
98
98
  complete_event.component_id.should == subject.id
99
99
  complete_event.reason.should == expected_complete_reason
@@ -61,7 +61,7 @@ module Punchblock
61
61
 
62
62
  command.should be_complete
63
63
 
64
- complete_event = command.complete_event.resource(0.5)
64
+ complete_event = command.complete_event 0.5
65
65
 
66
66
  complete_event.component_id.should == subject.id
67
67
  complete_event.reason.should == expected_complete_reason
@@ -115,7 +115,7 @@ module Punchblock
115
115
  end
116
116
 
117
117
  it 'should send events to the component node' do
118
- command.register_event_handler Punchblock::Event::Asterisk::AMI::Event do |event|
118
+ command.register_handler :internal, Punchblock::Event::Asterisk::AMI::Event do |event|
119
119
  @event = event
120
120
  end
121
121
  subject.action << event
@@ -128,7 +128,7 @@ module Punchblock
128
128
  subject.action << response
129
129
  subject.action << terminating_event
130
130
 
131
- command.complete_event.resource(0.5).reason.should == expected_complete_reason
131
+ command.complete_event(0.5).reason.should == expected_complete_reason
132
132
  end
133
133
  end
134
134
 
@@ -144,7 +144,7 @@ module Punchblock
144
144
  it 'should send a complete event to the component node' do
145
145
  subject.action << error
146
146
 
147
- complete_event = command.complete_event.resource(0.5)
147
+ complete_event = command.complete_event 0.5
148
148
 
149
149
  complete_event.component_id.should == subject.id
150
150
  complete_event.reason.should == expected_complete_reason