xmpp4r 0.4 → 0.5

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