punchblock 0.4.3 → 0.5.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/CHANGELOG.md +3 -0
- data/bin/punchblock-console +11 -10
- data/lib/punchblock.rb +2 -1
- data/lib/punchblock/client.rb +64 -0
- data/lib/punchblock/client/component_registry.rb +22 -0
- data/lib/punchblock/command/dial.rb +1 -4
- data/lib/punchblock/component.rb +6 -8
- data/lib/punchblock/connection.rb +4 -206
- data/lib/punchblock/connection/connected.rb +15 -0
- data/lib/punchblock/connection/xmpp.rb +161 -0
- data/lib/punchblock/dsl.rb +4 -4
- data/lib/punchblock/rayo_node.rb +2 -2
- data/lib/punchblock/version.rb +1 -1
- data/punchblock.gemspec +1 -1
- data/spec/punchblock/client/component_registry_spec.rb +15 -0
- data/spec/punchblock/client_spec.rb +125 -0
- data/spec/punchblock/command/dial_spec.rb +1 -8
- data/spec/punchblock/component/input_spec.rb +3 -2
- data/spec/punchblock/component/output_spec.rb +10 -9
- data/spec/punchblock/component/record_spec.rb +5 -4
- data/spec/punchblock/component/tropo/ask_spec.rb +3 -2
- data/spec/punchblock/component/tropo/conference_spec.rb +6 -5
- data/spec/punchblock/component/tropo/say_spec.rb +5 -4
- data/spec/punchblock/component/tropo/transfer_spec.rb +3 -2
- data/spec/punchblock/component_spec.rb +3 -9
- data/spec/punchblock/connection/xmpp_spec.rb +201 -0
- metadata +45 -38
- data/lib/punchblock/generic_connection.rb +0 -18
- data/spec/punchblock/connection_spec.rb +0 -216
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# develop
|
|
2
2
|
|
|
3
|
+
# v0.5.0
|
|
4
|
+
* Refactoring/API change: Client and connection level concerns are now separated, and one must create a Connection to be passed to a Client on creation. Client now has the choice between an event queue and guarded event handlers.
|
|
5
|
+
|
|
3
6
|
# v0.4.3
|
|
4
7
|
* Feature: Support for meta-data on recordings (size & duration)
|
|
5
8
|
* Feature: Allow specifying all of Blather's setup options (required to use PB as an XMPP component)
|
data/bin/punchblock-console
CHANGED
|
@@ -54,18 +54,19 @@ options[:transport_logger] = Logger.new options.delete(:transport_log_file)
|
|
|
54
54
|
options[:transport_logger].level = Logger::DEBUG
|
|
55
55
|
options[:transport_logger].debug "Starting up..."
|
|
56
56
|
|
|
57
|
-
connection = Connection.new options
|
|
57
|
+
connection = Connection::XMPP.new options
|
|
58
|
+
client = Client.new :connection => connection
|
|
58
59
|
|
|
59
60
|
[:INT, :TERM].each do |signal|
|
|
60
61
|
trap signal do
|
|
61
62
|
puts "Shutting down!"
|
|
62
|
-
|
|
63
|
+
client.stop
|
|
63
64
|
end
|
|
64
65
|
end
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
client_thread = Thread.new do
|
|
67
68
|
begin
|
|
68
|
-
|
|
69
|
+
client.run
|
|
69
70
|
rescue => e
|
|
70
71
|
puts "Exception in XMPP thread! #{e.message}"
|
|
71
72
|
puts e.backtrace.join("\t\n")
|
|
@@ -80,8 +81,8 @@ CALL_QUEUES = {}
|
|
|
80
81
|
# the queue.
|
|
81
82
|
Thread.new do
|
|
82
83
|
loop do
|
|
83
|
-
event =
|
|
84
|
-
if event ==
|
|
84
|
+
event = client.event_queue.pop
|
|
85
|
+
if event == Connection::Connected
|
|
85
86
|
puts event
|
|
86
87
|
puts "Waiting for a call..."
|
|
87
88
|
next
|
|
@@ -92,7 +93,7 @@ Thread.new do
|
|
|
92
93
|
raise "Duplicate call ID for #{event.call_id}" if CALL_QUEUES.has_key?(event.call_id)
|
|
93
94
|
CALL_QUEUES[event.call_id] = Queue.new
|
|
94
95
|
CALL_QUEUES[event.call_id].push event
|
|
95
|
-
run_call
|
|
96
|
+
run_call client, event
|
|
96
97
|
when Event
|
|
97
98
|
CALL_QUEUES[event.call_id].push event
|
|
98
99
|
else
|
|
@@ -101,14 +102,14 @@ Thread.new do
|
|
|
101
102
|
end
|
|
102
103
|
end
|
|
103
104
|
|
|
104
|
-
def run_call(
|
|
105
|
+
def run_call(client, offer)
|
|
105
106
|
### CALL THREAD
|
|
106
107
|
# One thread is spun up to handle each call.
|
|
107
108
|
Thread.new do
|
|
108
109
|
raise "Unknown call #{offer.call_id}" unless CALL_QUEUES.has_key?(offer.call_id)
|
|
109
110
|
queue = CALL_QUEUES[offer.call_id]
|
|
110
111
|
call = queue.pop
|
|
111
|
-
dsl = DSL.new
|
|
112
|
+
dsl = DSL.new client, offer.call_id, queue
|
|
112
113
|
|
|
113
114
|
puts "Incoming offer to #{offer.to} from #{offer.headers_hash[:from]} #{offer}"
|
|
114
115
|
dsl.pry
|
|
@@ -118,4 +119,4 @@ def run_call(connection, offer)
|
|
|
118
119
|
end
|
|
119
120
|
end
|
|
120
121
|
|
|
121
|
-
|
|
122
|
+
client_thread.join
|
data/lib/punchblock.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
%w{
|
|
2
2
|
active_support/dependencies/autoload
|
|
3
3
|
active_support/core_ext/object/blank
|
|
4
|
+
active_support/core_ext/module/delegation
|
|
4
5
|
future-resource
|
|
5
6
|
has_guarded_handlers
|
|
6
7
|
}.each { |l| require l }
|
|
@@ -21,12 +22,12 @@ end
|
|
|
21
22
|
module Punchblock
|
|
22
23
|
extend ActiveSupport::Autoload
|
|
23
24
|
|
|
25
|
+
autoload :Client
|
|
24
26
|
autoload :Command
|
|
25
27
|
autoload :CommandNode
|
|
26
28
|
autoload :Component
|
|
27
29
|
autoload :Connection
|
|
28
30
|
autoload :DSL
|
|
29
|
-
autoload :GenericConnection
|
|
30
31
|
autoload :HasHeaders
|
|
31
32
|
autoload :Header
|
|
32
33
|
autoload :MediaContainer
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module Punchblock
|
|
2
|
+
class Client
|
|
3
|
+
extend ActiveSupport::Autoload
|
|
4
|
+
|
|
5
|
+
autoload :ComponentRegistry
|
|
6
|
+
|
|
7
|
+
include HasGuardedHandlers
|
|
8
|
+
|
|
9
|
+
attr_reader :connection, :event_queue, :component_registry
|
|
10
|
+
|
|
11
|
+
delegate :run, :stop, :to => :connection
|
|
12
|
+
|
|
13
|
+
# @param [Hash] options
|
|
14
|
+
# @option options [Connection::XMPP] :connection The Punchblock connection to use for this session
|
|
15
|
+
#
|
|
16
|
+
def initialize(options = {})
|
|
17
|
+
@event_queue = Queue.new
|
|
18
|
+
@connection = options[:connection]
|
|
19
|
+
@connection.event_handler = lambda { |event| self.handle_event event } if @connection
|
|
20
|
+
register_initial_handlers
|
|
21
|
+
@component_registry = ComponentRegistry.new
|
|
22
|
+
@write_timeout = options[:write_timeout] || 3
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def handle_event(event)
|
|
26
|
+
event.client = self
|
|
27
|
+
if event.source
|
|
28
|
+
event.source.add_event event
|
|
29
|
+
else
|
|
30
|
+
trigger_handler :event, event
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def register_event_handler(*guards, &block)
|
|
35
|
+
register_handler :event, *guards, &block
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def register_initial_handlers
|
|
39
|
+
register_handler_with_priority :event, -10 do |event|
|
|
40
|
+
event_queue.push event
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def register_component(component)
|
|
45
|
+
component_registry << component
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def find_component_by_id(component_id)
|
|
49
|
+
component_registry.find_by_id component_id
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def execute_command(command, options = {})
|
|
53
|
+
async = options.has_key?(:async) ? options.delete(:async) : true
|
|
54
|
+
command.client = self
|
|
55
|
+
if command.respond_to?(:register_event_handler)
|
|
56
|
+
command.register_event_handler do |event|
|
|
57
|
+
trigger_handler :event, event
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
connection.write command, options
|
|
61
|
+
command.response(@write_timeout).tap { |result| raise result if result.is_a? Exception } unless async
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Punchblock
|
|
2
|
+
class Client
|
|
3
|
+
class ComponentRegistry
|
|
4
|
+
def initialize
|
|
5
|
+
@mutex = Mutex.new
|
|
6
|
+
@components = Hash.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def <<(component)
|
|
10
|
+
@mutex.synchronize do
|
|
11
|
+
@components[component.component_id] = component
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def find_by_id(component_id)
|
|
16
|
+
@mutex.synchronize do
|
|
17
|
+
@components[component_id]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -73,11 +73,8 @@ module Punchblock
|
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
def response=(other)
|
|
76
|
+
@call_id = other.id if other.is_a?(Ref)
|
|
76
77
|
super
|
|
77
|
-
if other.is_a?(Blather::Stanza::Iq)
|
|
78
|
-
ref = other.rayo_node
|
|
79
|
-
@call_id = ref.id if ref.is_a?(Ref)
|
|
80
|
-
end
|
|
81
78
|
end
|
|
82
79
|
|
|
83
80
|
def inspect_attributes # :nodoc:
|
data/lib/punchblock/component.rb
CHANGED
|
@@ -38,18 +38,16 @@ module Punchblock
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def write_action(action)
|
|
41
|
-
|
|
41
|
+
client.execute_command action, :call_id => call_id, :component_id => component_id
|
|
42
|
+
action
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
def response=(other)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if ref.is_a?(Ref)
|
|
49
|
-
@component_id = ref.id
|
|
50
|
-
@connection.record_command_id_for_iq_id @component_id, other.id
|
|
51
|
-
end
|
|
46
|
+
if other.is_a?(Ref)
|
|
47
|
+
@component_id = other.id
|
|
48
|
+
client.register_component self
|
|
52
49
|
end
|
|
50
|
+
super
|
|
53
51
|
end
|
|
54
52
|
|
|
55
53
|
##
|
|
@@ -1,210 +1,8 @@
|
|
|
1
|
-
%w{
|
|
2
|
-
timeout
|
|
3
|
-
blather/client/dsl
|
|
4
|
-
punchblock/core_ext/blather/stanza
|
|
5
|
-
punchblock/core_ext/blather/stanza/presence
|
|
6
|
-
}.each { |f| require f }
|
|
7
|
-
|
|
8
1
|
module Punchblock
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
##
|
|
13
|
-
# Initialize the required connection attributes
|
|
14
|
-
#
|
|
15
|
-
# @param [Hash] options
|
|
16
|
-
# @option options [String] :username client JID
|
|
17
|
-
# @option options [String] :password XMPP password
|
|
18
|
-
# @option options [String] :rayo_domain the domain on which Rayo is running
|
|
19
|
-
# @option options [Logger] :wire_logger to which all XMPP transactions will be logged
|
|
20
|
-
# @option options [Boolean, Optional] :auto_reconnect whether or not to auto reconnect
|
|
21
|
-
# @option options [Numeric, Optional] :write_timeout for which to wait on a command response
|
|
22
|
-
# @option options [Numeric, nil, Optional] :ping_period interval in seconds on which to ping the server. Nil or false to disable
|
|
23
|
-
#
|
|
24
|
-
def initialize(options = {})
|
|
25
|
-
super
|
|
26
|
-
|
|
27
|
-
raise ArgumentError unless (@username = options[:username]) && options[:password]
|
|
28
|
-
|
|
29
|
-
setup *[:username, :password, :host, :port, :certs].map { |key| options.delete key }
|
|
30
|
-
|
|
31
|
-
@rayo_domain = options[:rayo_domain] || Blather::JID.new(@username).domain
|
|
32
|
-
|
|
33
|
-
@callmap = {} # This hash maps call IDs to their XMPP domain.
|
|
34
|
-
|
|
35
|
-
@component_id_to_iq_id = {}
|
|
36
|
-
@iq_id_to_command = {}
|
|
37
|
-
|
|
38
|
-
@auto_reconnect = !!options[:auto_reconnect]
|
|
39
|
-
@reconnect_attempts = 0
|
|
40
|
-
|
|
41
|
-
@write_timeout = options[:write_timeout] || 3
|
|
42
|
-
|
|
43
|
-
@ping_period = options.has_key?(:ping_period) ? options[:ping_period] : 60
|
|
44
|
-
|
|
45
|
-
Blather.logger = options.delete(:wire_logger) if options.has_key?(:wire_logger)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
##
|
|
49
|
-
# Write a command to the Rayo server for a particular call
|
|
50
|
-
#
|
|
51
|
-
# @param [String] call the call ID on which to act
|
|
52
|
-
# @param [CommandNode] cmd the command to execute on the call
|
|
53
|
-
# @param [String, Optional] component_id the component_id on which to execute
|
|
54
|
-
#
|
|
55
|
-
# @raise Exception if there is a server-side error
|
|
56
|
-
#
|
|
57
|
-
# @return true
|
|
58
|
-
#
|
|
59
|
-
def write(call_id, cmd, component_id = nil)
|
|
60
|
-
async_write call_id, cmd, component_id
|
|
61
|
-
cmd.response(@write_timeout).tap { |result| raise result if result.is_a? Exception }
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
##
|
|
65
|
-
# @return [Queue] Pop this queue to determine result of command execution. Will be true or an exception
|
|
66
|
-
def async_write(call_id, cmd, component_id = nil)
|
|
67
|
-
iq = prep_command_for_execution call_id, cmd, component_id
|
|
68
|
-
write_to_stream iq
|
|
69
|
-
cmd.request!
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def prep_command_for_execution(call_id, cmd, component_id = nil)
|
|
73
|
-
cmd.connection = self
|
|
74
|
-
cmd.call_id = call_id
|
|
75
|
-
jid = cmd.is_a?(Command::Dial) ? @rayo_domain : "#{call_id}@#{@callmap[call_id]}"
|
|
76
|
-
jid << "/#{component_id}" if component_id
|
|
77
|
-
create_iq(jid).tap do |iq|
|
|
78
|
-
@logger.debug "Sending IQ ID #{iq.id} #{cmd.inspect} to #{jid}" if @logger
|
|
79
|
-
iq << cmd
|
|
80
|
-
@iq_id_to_command[iq.id] = cmd
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
##
|
|
85
|
-
# Fire up the connection
|
|
86
|
-
#
|
|
87
|
-
def run
|
|
88
|
-
register_handlers
|
|
89
|
-
connect
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def connect
|
|
93
|
-
begin
|
|
94
|
-
EM.run { client.run }
|
|
95
|
-
rescue Blather::SASLError, Blather::StreamError => e
|
|
96
|
-
raise ProtocolError.new(e.class.to_s, e.message)
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def stop
|
|
101
|
-
@reconnect_attempts = nil
|
|
102
|
-
client.close
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def connected?
|
|
106
|
-
client.connected?
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
##
|
|
110
|
-
#
|
|
111
|
-
# Get the original command issued by command ID
|
|
112
|
-
#
|
|
113
|
-
# @param [String] component_id
|
|
114
|
-
#
|
|
115
|
-
# @return [RayoNode]
|
|
116
|
-
#
|
|
117
|
-
def original_component_from_id(component_id)
|
|
118
|
-
@iq_id_to_command[@component_id_to_iq_id[component_id]]
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def record_command_id_for_iq_id(command_id, iq_id)
|
|
122
|
-
@component_id_to_iq_id[command_id] = iq_id
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
private
|
|
126
|
-
|
|
127
|
-
def handle_presence(p)
|
|
128
|
-
throw :pass unless p.rayo_event? && p.from.domain == @rayo_domain
|
|
129
|
-
@logger.info "Receiving event for call ID #{p.call_id}" if @logger
|
|
130
|
-
@callmap[p.call_id] = p.from.domain
|
|
131
|
-
@logger.debug p.inspect if @logger
|
|
132
|
-
event = p.event
|
|
133
|
-
event.connection = self
|
|
134
|
-
if event.source
|
|
135
|
-
event.source.add_event event
|
|
136
|
-
else
|
|
137
|
-
@event_queue.push event
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def handle_iq_result(iq)
|
|
142
|
-
# FIXME: Do we need to raise a warning if the domain changes?
|
|
143
|
-
throw :pass unless command = @iq_id_to_command[iq.id]
|
|
144
|
-
@callmap[iq.from.node] = iq.from.domain
|
|
145
|
-
@logger.debug "Command #{iq.id} completed successfully" if @logger
|
|
146
|
-
command.response = iq
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def handle_error(iq)
|
|
150
|
-
e = Blather::StanzaError.import iq
|
|
151
|
-
|
|
152
|
-
protocol_error = ProtocolError.new e.name, e.text, iq.call_id, iq.component_id
|
|
153
|
-
|
|
154
|
-
throw :pass unless command = @iq_id_to_command[iq.id]
|
|
155
|
-
|
|
156
|
-
command.response = protocol_error
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def register_handlers
|
|
160
|
-
# Push a message to the queue and the log that we connected
|
|
161
|
-
when_ready do
|
|
162
|
-
@event_queue.push connected
|
|
163
|
-
@logger.info "Connected to XMPP as #{@username}" if @logger
|
|
164
|
-
@reconnect_attempts = 0
|
|
165
|
-
@rayo_ping = EM::PeriodicTimer.new(@ping_period) { ping_rayo } if @ping_period
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
disconnected do
|
|
169
|
-
@rayo_ping.cancel if @rayo_ping
|
|
170
|
-
if @auto_reconnect && @reconnect_attempts
|
|
171
|
-
timer = 30 * 2 ** @reconnect_attempts
|
|
172
|
-
@logger.warn "XMPP disconnected. Tried to reconnect #{@reconnect_attempts} times. Reconnecting in #{timer}s." if @logger
|
|
173
|
-
sleep timer
|
|
174
|
-
@logger.info "Trying to reconnect..." if @logger
|
|
175
|
-
@reconnect_attempts += 1
|
|
176
|
-
connect
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# Read/handle call control messages. These are mostly just acknowledgement of commands
|
|
181
|
-
iq :result? do |msg|
|
|
182
|
-
handle_iq_result msg
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
# Read/handle error IQs
|
|
186
|
-
iq :error? do |e|
|
|
187
|
-
handle_error e
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
# Read/handle presence requests. This is how we get events.
|
|
191
|
-
presence do |msg|
|
|
192
|
-
handle_presence msg
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
def ping_rayo
|
|
197
|
-
client.write_with_handler Blather::Stanza::Iq::Ping.new(:set, @rayo_domain) do |response|
|
|
198
|
-
begin
|
|
199
|
-
handle_error response if response.is_a? Blather::BlatherError
|
|
200
|
-
rescue ProtocolError => e
|
|
201
|
-
raise e unless e.name == :feature_not_implemented
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
end
|
|
2
|
+
module Connection
|
|
3
|
+
extend ActiveSupport::Autoload
|
|
205
4
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
end
|
|
5
|
+
autoload :Connected
|
|
6
|
+
autoload :XMPP
|
|
209
7
|
end
|
|
210
8
|
end
|