xmpp4r 0.4 → 0.5

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.
Files changed (68) hide show
  1. data/CHANGELOG +8 -0
  2. data/README.rdoc +4 -1
  3. data/Rakefile +10 -20
  4. data/data/doc/xmpp4r/examples/advanced/versionpoll.rb +20 -1
  5. data/lib/xmpp4r/bytestreams/helper/ibb/target.rb +7 -0
  6. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb +7 -1
  7. data/lib/xmpp4r/callbacks.rb +9 -0
  8. data/lib/xmpp4r/caps/c.rb +14 -0
  9. data/lib/xmpp4r/caps/helper/helper.rb +1 -4
  10. data/lib/xmpp4r/client.rb +42 -15
  11. data/lib/xmpp4r/connection.rb +7 -3
  12. data/lib/xmpp4r/debuglog.rb +22 -1
  13. data/lib/xmpp4r/discovery.rb +1 -0
  14. data/lib/xmpp4r/discovery/helper/helper.rb +58 -0
  15. data/lib/xmpp4r/discovery/iq/discoinfo.rb +2 -2
  16. data/lib/xmpp4r/discovery/iq/discoitems.rb +2 -2
  17. data/lib/xmpp4r/errors.rb +5 -2
  18. data/lib/xmpp4r/httpbinding/client.rb +9 -19
  19. data/lib/xmpp4r/last.rb +2 -0
  20. data/lib/xmpp4r/last/helper/helper.rb +37 -0
  21. data/lib/xmpp4r/last/iq/last.rb +67 -0
  22. data/lib/xmpp4r/location.rb +2 -0
  23. data/lib/xmpp4r/location/helper/helper.rb +56 -0
  24. data/lib/xmpp4r/location/location.rb +179 -0
  25. data/lib/xmpp4r/message.rb +32 -0
  26. data/lib/xmpp4r/presence.rb +1 -1
  27. data/lib/xmpp4r/pubsub/children/configuration.rb +1 -1
  28. data/lib/xmpp4r/pubsub/children/items.rb +11 -2
  29. data/lib/xmpp4r/pubsub/children/publish.rb +14 -0
  30. data/lib/xmpp4r/pubsub/children/retract.rb +41 -0
  31. data/lib/xmpp4r/pubsub/helper/nodebrowser.rb +2 -3
  32. data/lib/xmpp4r/pubsub/helper/nodehelper.rb +4 -4
  33. data/lib/xmpp4r/pubsub/helper/oauth_service_helper.rb +90 -0
  34. data/lib/xmpp4r/pubsub/helper/servicehelper.rb +58 -19
  35. data/lib/xmpp4r/reliable.rb +168 -0
  36. data/lib/xmpp4r/rexmladdons.rb +6 -0
  37. data/lib/xmpp4r/roster/helper/roster.rb +5 -2
  38. data/lib/xmpp4r/sasl.rb +19 -8
  39. data/lib/xmpp4r/stream.rb +133 -31
  40. data/lib/xmpp4r/streamparser.rb +9 -1
  41. data/lib/xmpp4r/test/listener_mocker.rb +118 -0
  42. data/lib/xmpp4r/xmpp4r.rb +3 -1
  43. data/test/bytestreams/tc_ibb.rb +6 -4
  44. data/test/bytestreams/tc_socks5bytestreams.rb +3 -2
  45. data/test/caps/tc_helper.rb +4 -2
  46. data/test/dataforms/tc_data.rb +1 -1
  47. data/test/last/tc_helper.rb +75 -0
  48. data/test/lib/clienttester.rb +43 -14
  49. data/test/muc/tc_muc_mucclient.rb +6 -2
  50. data/test/pubsub/tc_helper.rb +131 -8
  51. data/test/pubsub/tc_nodeconfig.rb +7 -0
  52. data/test/reliable/tc_disconnect_cleanup.rb +334 -0
  53. data/test/reliable/tc_disconnect_exception.rb +37 -0
  54. data/test/reliable/tc_listener_mocked_test.rb +68 -0
  55. data/test/reliable/tc_reliable_connection.rb +31 -0
  56. data/test/roster/tc_helper.rb +21 -11
  57. data/test/rpc/tc_helper.rb +2 -2
  58. data/test/tc_callbacks.rb +3 -3
  59. data/test/tc_message.rb +15 -0
  60. data/test/tc_stream.rb +59 -121
  61. data/test/tc_streamError.rb +2 -4
  62. data/test/tc_streamparser.rb +26 -13
  63. data/test/ts_xmpp4r.rb +0 -9
  64. data/test/tune/tc_helper_recv.rb +0 -2
  65. data/test/vcard/tc_helper.rb +1 -1
  66. data/xmpp4r.gemspec +31 -84
  67. metadata +116 -167
  68. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb.orig +0 -62
@@ -0,0 +1,168 @@
1
+ require 'xmpp4r/stream'
2
+
3
+ module Jabber
4
+ module Reliable
5
+
6
+ class Connection < Jabber::Client
7
+ def initialize(full_jid, config)
8
+ super(full_jid)
9
+ @servers = config[:servers]
10
+ @port = config[:port] || 5222
11
+ @max_retry = config[:max_retry] || 30
12
+ @retry_sleep = config[:retry_sleep] || 2
13
+ if(@servers.nil? or @servers.empty?)
14
+ @servers = [@jid.domain]
15
+ end
16
+ end
17
+
18
+ def connect
19
+ retry_count = 0
20
+ server_to_use = nil
21
+ server_pool = @servers.dup.sort{ rand <=> rand }
22
+ begin
23
+ server_to_use = server_pool.shift
24
+ server_pool.push(server_to_use)
25
+
26
+ Jabber::debuglog "timeout will be: #{@retry_sleep.to_f}"
27
+ Timeout.timeout(@retry_sleep.to_f){
28
+ Jabber::debuglog "trying to connect to #{server_to_use}"
29
+ super(server_to_use, @port)
30
+ }
31
+
32
+ Jabber::debuglog self.jid.to_s + " connected to " + server_to_use.to_s
33
+ Jabber::debuglog "out of possible servers " + @servers.inspect
34
+ rescue Exception, Timeout::Error => e
35
+ Jabber::warnlog "#{server_to_use} error: #{e.inspect}. Will attempt to reconnect in #{@retry_sleep}"
36
+ sleep(@retry_sleep.to_f)
37
+ if(retry_count >= @max_retry.to_i)
38
+ Jabber::warnlog "reached max retry count on exception, failing"
39
+ raise e
40
+ end
41
+ retry_count += 1
42
+ retry
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ class Listener
49
+ def initialize(full_jid, password, config, &block)
50
+ @on_message_block = block
51
+ @full_jid = full_jid
52
+ @config = config
53
+ @password = password
54
+ @max_retry = config[:max_retry] || 30
55
+ end
56
+
57
+ def setup_connection
58
+ @connection = Connection.new(@full_jid, @config)
59
+ if @on_message_block
60
+ @connection.add_message_callback(&@on_message_block)
61
+ else
62
+ @connection.add_message_callback do |msg|
63
+ self.on_message(msg)
64
+ end
65
+ end
66
+
67
+ #We could just reconnect in @connection.on_exception,
68
+ #but by raising into this seperate thread, we avoid growing our stack trace
69
+ @reconnection_thread = Thread.new do
70
+ first_run = true
71
+ begin
72
+ self.start unless first_run
73
+ loop do
74
+ sleep(1)
75
+ Thread.pass
76
+ end
77
+ rescue => e
78
+ first_run = false
79
+ retry
80
+ end
81
+ end
82
+ @exception_handlers = []
83
+ @connection.on_exception do |e, connection, where_failed|
84
+ self.run_exception_handlers(e, connection, where_failed)
85
+ end
86
+ end
87
+
88
+ def run_exception_handlers(e, connection, where_failed)
89
+ @exception_handlers.each do |ex_handler|
90
+ ex_handler.call(e, connection, where_failed)
91
+ end
92
+ if where_failed == :sending
93
+ @message_to_send_on_reconnect = @message_now_sending
94
+ end
95
+ if where_failed != :close && !@connection.is_connected?
96
+ @reconnection_thread.raise(e)
97
+ end
98
+ end
99
+
100
+ def add_exception_handler(&block)
101
+ @exception_handlers << block
102
+ end
103
+
104
+ def start
105
+ setup_connection unless @connection
106
+ connect
107
+ auth
108
+ send_presence
109
+ if @message_to_send_on_reconnect
110
+ send_message(@message_to_send_on_reconnect)
111
+ end
112
+ @message_to_send_on_reconnect = nil
113
+ end
114
+
115
+ #Stop the listener. (close the connection)
116
+ def stop
117
+ @connection.close if @connection and @connection.is_connected?
118
+ @connection = nil
119
+ end
120
+
121
+ def connect
122
+ @connection.connect
123
+ end
124
+
125
+ def auth
126
+ @connection.auth(@password)
127
+ end
128
+
129
+ def send_presence
130
+ presence_message = @config[:presence_message]
131
+ if presence_message && !presence_message.empty?
132
+ @connection.send(Jabber::Presence.new.set_show(:chat).set_status(presence_message))
133
+ end
134
+ end
135
+
136
+ #TODO: test and fix situation where we get disconnected while sending but then successfully reconnect
137
+ # (and make sure in such cases we resent)
138
+ def send_message(message)
139
+ unless @connection
140
+ raise ::ArgumentError, "Can't send messages while listener is stopped. Plase 'start' the listener first."
141
+ end
142
+ retry_count = 0
143
+ begin
144
+ while(not @connection.is_connected?)
145
+ #wait
146
+ Thread.pass
147
+ end
148
+ @message_now_sending = message
149
+ @connection.send(message)
150
+ return true #true, message was sent
151
+ rescue => e
152
+ if e.is_a?(Interrupt)
153
+ raise e
154
+ end
155
+ if(retry_count > @max_retry.to_i)
156
+ Jabber::debuglog "reached max retry count on message re-send, failing"
157
+ raise e
158
+ end
159
+ retry_count += 1
160
+ Jabber::debuglog "retrying message send.." + e.inspect
161
+ retry
162
+ end
163
+ end
164
+
165
+ end
166
+
167
+ end
168
+ end
@@ -17,6 +17,12 @@ module REXML
17
17
  # this class adds a few helper methods to REXML::Element
18
18
  class Element
19
19
 
20
+ def each_elements(*els, &block)
21
+ els.inject([ ]) do |res, e|
22
+ res + each_element(e, &block)
23
+ end
24
+ end
25
+
20
26
  ##
21
27
  # Replaces or adds a child element of name <tt>e</tt> with text <tt>t</tt>.
22
28
  def replace_element_text(e, t)
@@ -36,7 +36,7 @@ module Jabber
36
36
  # <b>Attention:</b> If you send presence and receive presences
37
37
  # before the roster has arrived, the Roster helper will let them
38
38
  # pass through and does *not* keep them!
39
- def initialize(stream)
39
+ def initialize(stream, startnow = true)
40
40
  @stream = stream
41
41
  @items = {}
42
42
  @items_lock = Mutex.new
@@ -66,10 +66,13 @@ module Jabber
66
66
  handle_presence(pres)
67
67
  end
68
68
  }
69
+ get_roster if startnow
70
+ end
69
71
 
72
+ def get_roster
70
73
  # Request the roster
71
74
  rosterget = Iq.new_rosterget
72
- stream.send(rosterget)
75
+ @stream.send(rosterget)
73
76
  end
74
77
 
75
78
  ##
@@ -122,7 +122,6 @@ module Jabber
122
122
  state = :key
123
123
  key = ''
124
124
  value = ''
125
-
126
125
  text.scan(/./) do |ch|
127
126
  if state == :key
128
127
  if ch == '='
@@ -133,6 +132,9 @@ module Jabber
133
132
 
134
133
  elsif state == :value
135
134
  if ch == ','
135
+ # due to our home-made parsing of the challenge, the key could have
136
+ # leading whitespace. strip it, or that would break jabberd2 support.
137
+ key = key.strip
136
138
  res[key] = value
137
139
  key = ''
138
140
  value = ''
@@ -151,9 +153,12 @@ module Jabber
151
153
  end
152
154
  end
153
155
  end
156
+ # due to our home-made parsing of the challenge, the key could have
157
+ # leading whitespace. strip it, or that would break jabberd2 support.
158
+ key = key.strip
154
159
  res[key] = value unless key == ''
155
160
 
156
- Jabber::debuglog("SASL DIGEST-MD5 challenge:\n#{text.inspect}\n#{res.inspect}")
161
+ Jabber::debuglog("SASL DIGEST-MD5 challenge:\n#{text}\n#{res.inspect}")
157
162
 
158
163
  res
159
164
  end
@@ -172,7 +177,7 @@ module Jabber
172
177
  response['nc'] = '00000001'
173
178
  response['qop'] = 'auth'
174
179
  response['digest-uri'] = "xmpp/#{@stream.jid.domain}"
175
- response['response'] = response_value(@stream.jid.node, @stream.jid.domain, response['digest-uri'], password, @nonce, response['cnonce'], response['qop'])
180
+ response['response'] = response_value(@stream.jid.node, @stream.jid.domain, response['digest-uri'], password, @nonce, response['cnonce'], response['qop'], response['authzid'])
176
181
  response.each { |key,value|
177
182
  unless %w(nc qop response charset).include? key
178
183
  response[key] = "\"#{value}\""
@@ -180,7 +185,7 @@ module Jabber
180
185
  }
181
186
 
182
187
  response_text = response.collect { |k,v| "#{k}=#{v}" }.join(',')
183
- Jabber::debuglog("SASL DIGEST-MD5 response:\n#{response_text}")
188
+ Jabber::debuglog("SASL DIGEST-MD5 response:\n#{response_text}\n#{response.inspect}")
184
189
 
185
190
  r = REXML::Element.new('response')
186
191
  r.add_namespace NS_SASL
@@ -224,14 +229,20 @@ module Jabber
224
229
 
225
230
  ##
226
231
  # Calculate the value for the response field
227
- def response_value(username, realm, digest_uri, passwd, nonce, cnonce, qop)
232
+ def response_value(username, realm, digest_uri, passwd, nonce, cnonce, qop, authzid)
228
233
  a1_h = h("#{username}:#{realm}:#{passwd}")
229
234
  a1 = "#{a1_h}:#{nonce}:#{cnonce}"
230
- #a2 = "AUTHENTICATE:#{digest_uri}#{(qop == 'auth') ? '' : ':00000000000000000000000000000000'}"
231
- a2 = "AUTHENTICATE:#{digest_uri}"
232
-
235
+ if authzid
236
+ a1 += ":#{authzid}"
237
+ end
238
+ if qop == 'auth-int' || qop == 'auth-conf'
239
+ a2 = "AUTHENTICATE:#{digest_uri}:00000000000000000000000000000000"
240
+ else
241
+ a2 = "AUTHENTICATE:#{digest_uri}"
242
+ end
233
243
  hh("#{hh(a1)}:#{nonce}:00000001:#{cnonce}:#{qop}:#{hh(a2)}")
234
244
  end
235
245
  end
236
246
  end
237
247
  end
248
+
@@ -35,6 +35,9 @@ module Jabber
35
35
  # connection status
36
36
  attr_reader :status
37
37
 
38
+ # number of stanzas currently being processed
39
+ attr_reader :processing
40
+
38
41
  ##
39
42
  # Initialize a new stream
40
43
  def initialize
@@ -48,12 +51,14 @@ module Jabber
48
51
  @send_lock = Mutex.new
49
52
  @last_send = Time.now
50
53
  @exception_block = nil
54
+ @tbcbmutex = Mutex.new
51
55
  @threadblocks = []
52
56
  @wakeup_thread = nil
53
57
  @streamid = nil
54
58
  @streamns = 'jabber:client'
55
59
  @features_sem = Semaphore.new
56
60
  @parser_thread = nil
61
+ @processing = 0
57
62
  end
58
63
 
59
64
  ##
@@ -76,7 +81,7 @@ module Jabber
76
81
  close!
77
82
  end
78
83
  rescue Exception => e
79
- Jabber::debuglog("EXCEPTION:\n#{e.class}\n#{e.message}\n#{e.backtrace.join("\n")}")
84
+ Jabber::warnlog("EXCEPTION:\n#{e.class}\n#{e.message}\n#{e.backtrace.join("\n")}")
80
85
 
81
86
  if @exception_block
82
87
  Thread.new do
@@ -85,7 +90,7 @@ module Jabber
85
90
  @exception_block.call(e, self, :start)
86
91
  end
87
92
  else
88
- Jabber::debuglog "Exception caught in Parser thread! (#{e.class})\n#{e.backtrace.join("\n")}"
93
+ Jabber::warnlog "Exception caught in Parser thread! (#{e.class})\n#{e.backtrace.join("\n")}"
89
94
  close!
90
95
  raise
91
96
  end
@@ -116,7 +121,7 @@ module Jabber
116
121
  ##
117
122
  # This method is called by the parser when a failure occurs
118
123
  def parse_failure(e)
119
- Jabber::debuglog("EXCEPTION:\n#{e.class}\n#{e.message}\n#{e.backtrace.join("\n")}")
124
+ Jabber::warnlog("EXCEPTION:\n#{e.class}\n#{e.message}\n#{e.backtrace.join("\n")}")
120
125
 
121
126
  # A new thread has to be created because close will cause the thread
122
127
  # to commit suicide(???)
@@ -128,7 +133,7 @@ module Jabber
128
133
  @exception_block.call(e, self, :parser)
129
134
  end
130
135
  else
131
- Jabber::debuglog "Stream#parse_failure was called by XML parser. Dumping " +
136
+ Jabber::warnlog "Stream#parse_failure was called by XML parser. Dumping " +
132
137
  "backtrace...\n" + e.exception + "\n#{e.backtrace.join("\n")}"
133
138
  close
134
139
  raise
@@ -170,6 +175,7 @@ module Jabber
170
175
  #
171
176
  # element:: [REXML::Element] The received element
172
177
  def receive(element)
178
+ @tbcbmutex.synchronize { @processing += 1 }
173
179
  Jabber::debuglog("RECEIVED:\n#{element.to_s}")
174
180
 
175
181
  if element.namespace('').to_s == '' # REXML namespaces are always strings
@@ -219,13 +225,21 @@ module Jabber
219
225
  end
220
226
  end
221
227
 
228
+ if @xmlcbs.process(stanza)
229
+ @tbcbmutex.synchronize { @processing -= 1 }
230
+ return true
231
+ end
232
+
222
233
  # Iterate through blocked threads (= waiting for an answer)
223
234
  #
224
235
  # We're dup'ping the @threadblocks here, so that we won't end up in an
225
236
  # endless loop if Stream#send is being nested. That means, the nested
226
237
  # threadblock won't receive the stanza currently processed, but the next
227
238
  # one.
228
- threadblocks = @threadblocks.dup
239
+ threadblocks = nil
240
+ @tbcbmutex.synchronize do
241
+ threadblocks = @threadblocks.dup
242
+ end
229
243
  threadblocks.each { |threadblock|
230
244
  exception = nil
231
245
  r = false
@@ -236,26 +250,68 @@ module Jabber
236
250
  end
237
251
 
238
252
  if r == true
239
- @threadblocks.delete(threadblock)
253
+ @tbcbmutex.synchronize do
254
+ @threadblocks.delete(threadblock)
255
+ end
240
256
  threadblock.wakeup
241
- return
257
+ @tbcbmutex.synchronize { @processing -= 1 }
258
+ return true
242
259
  elsif exception
243
- @threadblocks.delete(threadblock)
260
+ @tbcbmutex.synchronize do
261
+ @threadblocks.delete(threadblock)
262
+ end
244
263
  threadblock.raise(exception)
245
264
  end
246
265
  }
247
266
 
248
267
  Jabber::debuglog("PROCESSING:\n#{stanza.to_s} (#{stanza.class})")
249
- return true if @xmlcbs.process(stanza)
250
- return true if @stanzacbs.process(stanza)
268
+ Jabber::debuglog("TRYING stanzacbs...")
269
+ if @stanzacbs.process(stanza)
270
+ @tbcbmutex.synchronize { @processing -= 1 }
271
+ return true
272
+ end
273
+ r = false
274
+ Jabber::debuglog("TRYING message/iq/presence/cbs...")
251
275
  case stanza
252
276
  when Message
253
- return true if @messagecbs.process(stanza)
277
+ r = @messagecbs.process(stanza)
254
278
  when Iq
255
- return true if @iqcbs.process(stanza)
279
+ r = @iqcbs.process(stanza)
256
280
  when Presence
257
- return true if @presencecbs.process(stanza)
281
+ r = @presencecbs.process(stanza)
258
282
  end
283
+ @tbcbmutex.synchronize { @processing -= 1 }
284
+ return r
285
+ end
286
+
287
+ ##
288
+ # Get the list of iq callbacks.
289
+ def iq_callbacks
290
+ @iqcbs
291
+ end
292
+
293
+ ##
294
+ # Get the list of message callbacks.
295
+ def message_callbacks
296
+ @messagecbs
297
+ end
298
+
299
+ ##
300
+ # Get the list of presence callbacks.
301
+ def presence_callbacks
302
+ @presencecbs
303
+ end
304
+
305
+ ##
306
+ # Get the list of stanza callbacks.
307
+ def stanza_callbacks
308
+ @stanzacbs
309
+ end
310
+
311
+ ##
312
+ # Get the list of xml callbacks.
313
+ def xml_callbacks
314
+ @xmlcbs
259
315
  end
260
316
 
261
317
  ##
@@ -276,7 +332,6 @@ module Jabber
276
332
  raise @exception if @exception
277
333
  end
278
334
  def wakeup
279
- # TODO: Handle threadblock removal if !alive?
280
335
  @waiter.run
281
336
  end
282
337
  def raise(exception)
@@ -305,11 +360,17 @@ module Jabber
305
360
  # &block:: [Block] The optional block
306
361
  def send(xml, &block)
307
362
  Jabber::debuglog("SENDING:\n#{xml}")
308
- @threadblocks.unshift(threadblock = ThreadBlock.new(block)) if block
363
+ if block
364
+ threadblock = ThreadBlock.new(block)
365
+ @tbcbmutex.synchronize do
366
+ @threadblocks.unshift(threadblock)
367
+ end
368
+ end
309
369
  begin
310
370
  # Temporarily remove stanza's namespace to
311
371
  # reduce bandwidth consumption
312
- if xml.kind_of? XMPPStanza and xml.namespace == 'jabber:client'
372
+ if xml.kind_of? XMPPStanza and xml.namespace == 'jabber:client' and
373
+ xml.prefix != 'stream' and xml.name != 'stream'
313
374
  xml.delete_namespace
314
375
  send_data(xml.to_s)
315
376
  xml.add_namespace(@streamns)
@@ -317,7 +378,7 @@ module Jabber
317
378
  send_data(xml.to_s)
318
379
  end
319
380
  rescue Exception => e
320
- Jabber::debuglog("EXCEPTION:\n#{e.class}\n#{e.message}\n#{e.backtrace.join("\n")}")
381
+ Jabber::warnlog("EXCEPTION:\n#{e.class}\n#{e.message}\n#{e.backtrace.join("\n")}")
321
382
 
322
383
  if @exception_block
323
384
  Thread.new do
@@ -326,7 +387,7 @@ module Jabber
326
387
  @exception_block.call(e, self, :sending)
327
388
  end
328
389
  else
329
- Jabber::debuglog "Exception caught while sending! (#{e.class})\n#{e.backtrace.join("\n")}"
390
+ Jabber::warnlog "Exception caught while sending! (#{e.class})\n#{e.backtrace.join("\n")}"
330
391
  close!
331
392
  raise
332
393
  end
@@ -336,7 +397,7 @@ module Jabber
336
397
  if block and Thread.current != @parser_thread
337
398
  threadblock.wait
338
399
  elsif block
339
- Jabber::debuglog("WARNING:\nCannot stop current thread in Jabber::Stream#send because it is the parser thread!")
400
+ Jabber::warnlog("WARNING:\nCannot stop current thread in Jabber::Stream#send because it is the parser thread!")
340
401
  end
341
402
  end
342
403
 
@@ -393,13 +454,17 @@ module Jabber
393
454
  end
394
455
 
395
456
  ##
396
- # Adds a callback block to process received XML messages
457
+ # Adds a callback block to process received XML messages, these
458
+ # will be handled before any blocks given to Stream#send or other
459
+ # callbacks.
397
460
  #
398
461
  # priority:: [Integer] The callback's priority, the higher, the sooner
399
462
  # ref:: [String] The callback's reference
400
463
  # &block:: [Block] The optional block
401
464
  def add_xml_callback(priority = 0, ref = nil, &block)
402
- @xmlcbs.add(priority, ref, block)
465
+ @tbcbmutex.synchronize do
466
+ @xmlcbs.add(priority, ref, block)
467
+ end
403
468
  end
404
469
 
405
470
  ##
@@ -407,7 +472,9 @@ module Jabber
407
472
  #
408
473
  # ref:: [String] The reference of the callback to delete
409
474
  def delete_xml_callback(ref)
410
- @xmlcbs.delete(ref)
475
+ @tbcbmutex.synchronize do
476
+ @xmlcbs.delete(ref)
477
+ end
411
478
  end
412
479
 
413
480
  ##
@@ -417,7 +484,9 @@ module Jabber
417
484
  # ref:: [String] The callback's reference
418
485
  # &block:: [Block] The optional block
419
486
  def add_message_callback(priority = 0, ref = nil, &block)
420
- @messagecbs.add(priority, ref, block)
487
+ @tbcbmutex.synchronize do
488
+ @messagecbs.add(priority, ref, block)
489
+ end
421
490
  end
422
491
 
423
492
  ##
@@ -425,7 +494,9 @@ module Jabber
425
494
  #
426
495
  # ref:: [String] The reference of the callback to delete
427
496
  def delete_message_callback(ref)
428
- @messagecbs.delete(ref)
497
+ @tbcbmutex.synchronize do
498
+ @messagecbs.delete(ref)
499
+ end
429
500
  end
430
501
 
431
502
  ##
@@ -435,7 +506,9 @@ module Jabber
435
506
  # ref:: [String] The callback's reference
436
507
  # &block:: [Block] The optional block
437
508
  def add_stanza_callback(priority = 0, ref = nil, &block)
438
- @stanzacbs.add(priority, ref, block)
509
+ @tbcbmutex.synchronize do
510
+ @stanzacbs.add(priority, ref, block)
511
+ end
439
512
  end
440
513
 
441
514
  ##
@@ -443,7 +516,9 @@ module Jabber
443
516
  #
444
517
  # ref:: [String] The reference of the callback to delete
445
518
  def delete_stanza_callback(ref)
446
- @stanzacbs.delete(ref)
519
+ @tbcbmutex.synchronize do
520
+ @stanzacbs.delete(ref)
521
+ end
447
522
  end
448
523
 
449
524
  ##
@@ -453,7 +528,9 @@ module Jabber
453
528
  # ref:: [String] The callback's reference
454
529
  # &block:: [Block] The optional block
455
530
  def add_presence_callback(priority = 0, ref = nil, &block)
456
- @presencecbs.add(priority, ref, block)
531
+ @tbcbmutex.synchronize do
532
+ @presencecbs.add(priority, ref, block)
533
+ end
457
534
  end
458
535
 
459
536
  ##
@@ -461,7 +538,9 @@ module Jabber
461
538
  #
462
539
  # ref:: [String] The reference of the callback to delete
463
540
  def delete_presence_callback(ref)
464
- @presencecbs.delete(ref)
541
+ @tbcbmutex.synchronize do
542
+ @presencecbs.delete(ref)
543
+ end
465
544
  end
466
545
 
467
546
  ##
@@ -471,7 +550,9 @@ module Jabber
471
550
  # ref:: [String] The callback's reference
472
551
  # &block:: [Block] The optional block
473
552
  def add_iq_callback(priority = 0, ref = nil, &block)
474
- @iqcbs.add(priority, ref, block)
553
+ @tbcbmutex.synchronize do
554
+ @iqcbs.add(priority, ref, block)
555
+ end
475
556
  end
476
557
 
477
558
  ##
@@ -480,7 +561,9 @@ module Jabber
480
561
  # ref:: [String] The reference of the callback to delete
481
562
  #
482
563
  def delete_iq_callback(ref)
483
- @iqcbs.delete(ref)
564
+ @tbcbmutex.synchronize do
565
+ @iqcbs.delete(ref)
566
+ end
484
567
  end
485
568
  ##
486
569
  # Closes the connection to the Jabber service
@@ -489,9 +572,28 @@ module Jabber
489
572
  end
490
573
 
491
574
  def close!
492
- @parser_thread.kill if @parser_thread
575
+ pr = 1
576
+ n = 0
577
+ # In some cases, we might lost count of some stanzas
578
+ # (for example, if the handler raises an exception)
579
+ # so we can't block forever.
580
+ while pr > 0 and n <= 1000
581
+ @tbcbmutex.synchronize { pr = @processing }
582
+ if pr > 0
583
+ n += 1
584
+ Jabber::debuglog("TRYING TO CLOSE, STILL PROCESSING #{pr} STANZAS")
585
+ #puts("TRYING TO CLOSE, STILL PROCESSING #{pr} STANZAS")
586
+ Thread::pass
587
+ end
588
+ end
589
+
590
+ # Order Matters here! If this method is called from within
591
+ # @parser_thread then killing @parser_thread first would
592
+ # mean the other parts of the method fail to execute.
593
+ # That would be bad. So kill parser_thread last
493
594
  @fd.close if @fd and !@fd.closed?
494
595
  @status = DISCONNECTED
596
+ @parser_thread.kill if @parser_thread
495
597
  end
496
598
  end
497
599
  end