punchblock 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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