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 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)
@@ -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
- connection.stop
63
+ client.stop
63
64
  end
64
65
  end
65
66
 
66
- connection_thread = Thread.new do
67
+ client_thread = Thread.new do
67
68
  begin
68
- connection.run
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 = connection.event_queue.pop
84
- if event == connection.connected
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 connection, event
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(connection, offer)
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 connection, offer.call_id, queue
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
- connection_thread.join
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:
@@ -38,18 +38,16 @@ module Punchblock
38
38
  end
39
39
 
40
40
  def write_action(action)
41
- connection.write call_id, action, component_id
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
- super
46
- if other.is_a?(Blather::Stanza::Iq)
47
- ref = other.rayo_node
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
- class Connection < GenericConnection
10
- include Blather::DSL
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
- def create_iq(jid = nil)
207
- Blather::Stanza::Iq.new :set, jid || @call_id
208
- end
5
+ autoload :Connected
6
+ autoload :XMPP
209
7
  end
210
8
  end
@@ -0,0 +1,15 @@
1
+ module Punchblock
2
+ module Connection
3
+ Connected = Class.new do
4
+ class << self
5
+ def source
6
+ nil
7
+ end
8
+
9
+ def client=(other)
10
+ nil
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end