spectra-xmpp4r-observable 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,58 @@
1
+ # Class ThreadStore
2
+ class ThreadStore
3
+ attr_reader :cycles, :max
4
+
5
+ # Create a new ThreadStore.
6
+ #
7
+ # max:: max number of threads to store (when exhausted, older threads will
8
+ # just be killed and discarded). Default is 100. If 0 or negative no
9
+ # threads will be discarded until #keep is called.
10
+ def initialize(max = 100)
11
+ @store = []
12
+ @max = max > 0 ? max : 0
13
+ @cycles = 0
14
+ @killer_thread = Thread.new do
15
+ loop do
16
+ sleep 2 while @store.empty?
17
+ sleep 1
18
+ @store.each_with_index do |thread, i|
19
+ th = @store.delete_at(i) if thread.nil? or ! thread.alive?
20
+ th = nil
21
+ end
22
+ @cycles += 1
23
+ end
24
+ end
25
+ end
26
+
27
+ def inspect # :nodoc:
28
+ sprintf("#<%s:0x%x @max=%d, @size=%d @cycles=%d>", self.class.name, __id__, @max, size, @cycles)
29
+ end
30
+
31
+ # Add a new thread to the ThreadStore
32
+ def add(thread)
33
+ if thread.instance_of?(Thread) and thread.respond_to?(:alive?)
34
+ @store << thread
35
+ keep(@max) if @max > 0
36
+ end
37
+ end
38
+
39
+ # Keep only the number passed of threads
40
+ #
41
+ # n:: number of threads to keep (default to @max if @max > 0)
42
+ def keep(n = nil)
43
+ if n.nil?
44
+ raise StandardError, "You need to pass the number of threads to keep!" if @max == 0
45
+ n = @max
46
+ end
47
+ @store.shift.kill while @store.length > n
48
+ end
49
+
50
+ # Kill all threads
51
+ def kill!
52
+ @store.shift.kill while @store.length > 0
53
+ end
54
+
55
+ # How long is our ThreadStore
56
+ def size; @store.length; end
57
+
58
+ end # of class ThreadStore
@@ -0,0 +1,648 @@
1
+ # XMPP4R-Observable - An easy-to-use Jabber Client library with PubSub support
2
+ # Copyright (c) 2009 by Pablo Lorenzoni <pablo@propus.com.br>
3
+ #
4
+ # This is based on XMPP4R-Simple (http://github.com/blaine/xmpp4r-simple) but
5
+ # we implement the notification of messages using a modified form of Ruby's
6
+ # Observable module instead of a queue.
7
+ #
8
+ # Jabber::Observable is free software; you can redistribute it and/or modify
9
+ # it under the terms of the GNU General Public License as published by the
10
+ # Free Software Foundation; either version 2 of the License, or (at your
11
+ # option) any later version.
12
+ #
13
+ # Jabber::Observable is distributed in the hope that it will be useful, but
14
+ # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15
+ # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
16
+ # more details.
17
+ #
18
+ # You should have received a copy of the GNU General Public License along with
19
+ # Jabber::Simple; if not, write to the Free Software Foundation, Inc., 51
20
+ # Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21
+
22
+ require 'time'
23
+ require 'rubygems'
24
+ require 'xmpp4r'
25
+ require 'xmpp4r/roster'
26
+ require 'xmpp4r/vcard'
27
+ require 'xmpp4r/pubsub'
28
+ require 'xmpp4r/pubsub/helper/servicehelper'
29
+ require 'xmpp4r/pubsub/helper/nodebrowser'
30
+ require 'xmpp4r/pubsub/helper/nodehelper'
31
+
32
+ # This will provide us our Notifications system
33
+ require 'observable_thing'
34
+
35
+ module Jabber
36
+
37
+ class ConnectionError < StandardError; end #:nodoc:
38
+
39
+ class NotConnected < StandardError; end #:nodoc:
40
+
41
+ class Contact #:nodoc:
42
+
43
+ def initialize(client, jid)
44
+ @jid = jid.respond_to?(:resource) ? jid : JID.new(jid)
45
+ @client = client
46
+ end
47
+
48
+ def inspect
49
+ sprintf("#<%s:0x%x @jid=%s>", self.class.name, __id__, @jid.to_s)
50
+ end
51
+
52
+ def subscribed?
53
+ [:to, :both].include?(subscription)
54
+ end
55
+
56
+ def subscription
57
+ roster_item && roster_item.subscription
58
+ end
59
+
60
+ def ask_for_authorization!
61
+ subscription_request = Jabber::Presence.new.set_type(:subscribe)
62
+ subscription_request.to = jid
63
+ client.send!(subscription_request)
64
+ end
65
+
66
+ def unsubscribe!
67
+ unsubscription_request = Jabber::Presence.new.set_type(:unsubscribe)
68
+ unsubscription_request.to = jid
69
+ client.send!(unsubscription_request)
70
+ client.send!(unsubscription_request.set_type(:unsubscribed))
71
+ end
72
+
73
+ def jid(bare=true)
74
+ bare ? @jid.strip : @jid
75
+ end
76
+
77
+ private
78
+
79
+ def roster_item
80
+ client.roster.items[jid]
81
+ end
82
+
83
+ def client
84
+ @client
85
+ end
86
+ end
87
+
88
+ # Jabber::Observable - Creates observable Jabber Clients
89
+ class Observable
90
+
91
+ # Jabber::Observable::PubSub - Convenience subclass to deal with PubSub
92
+ class PubSub
93
+ class NoService < StandardError; end #:nodoc:
94
+
95
+ class AlreadySet < StandardError; end #:nodoc:
96
+
97
+ # Creates a new PubSub object
98
+ #
99
+ # observable:: points a Jabber::Observable object
100
+ def initialize(observable)
101
+ @observable = observable
102
+
103
+ @helper = @service_jid = nil
104
+ begin
105
+ domain = Jabber::JID.new(@observable.jid).domain
106
+ @service_jid = "pubsub." + domain
107
+ set_service(@service_jid)
108
+ rescue
109
+ @helper = @service_jid = nil
110
+ end
111
+ end
112
+
113
+ def inspect #:nodoc:
114
+ if has_service?
115
+ sprintf("#<%s:0x%x @service_jid=%s>", self.class.name, __id__, @service_jid)
116
+ else
117
+ sprintf("#<%s:0x%x @has_service?=false>", self.class.name, __id__)
118
+ end
119
+ end
120
+
121
+ # Checks if the PubSub service is set
122
+ def has_service?
123
+ ! @helper.nil?
124
+ end
125
+
126
+ # Sets the PubSub service. Just one service is allowed.
127
+ def set_service(service)
128
+ raise NotConnected, "You are not connected" if ! @observable.connected?
129
+ raise AlreadySet, "You already have a PubSub service (#{@service_jid})." if has_service?
130
+ @helper = Jabber::PubSub::ServiceHelper.new(@observable.client, service)
131
+ @service_jid = service
132
+
133
+ @helper.add_event_callback do |event|
134
+ @observable.changed(:event)
135
+ @observable.notify_observers(:event, event)
136
+ end
137
+ end
138
+
139
+ # Subscribe to a node.
140
+ def subscribe_to(node)
141
+ raise_noservice if ! has_service?
142
+ @helper.subscribe_to(node)
143
+ end
144
+
145
+ # Unsubscribe from a node.
146
+ def unsubscribe_from(node)
147
+ raise_noservice if ! has_service?
148
+
149
+ # FIXME
150
+ # @helper.unsubscribe_from(node)
151
+ # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
152
+ # The above should just work, but I had to reimplement it since XMPP4R doesn't support subids
153
+ # and OpenFire (the Jabber Server I am testing against) seems to require it.
154
+
155
+ subids = find_subids_for(node)
156
+ return if subids.empty?
157
+
158
+ subids.each do |subid|
159
+ iq = Jabber::Iq.new(:set, @service_jid)
160
+ iq.add(Jabber::PubSub::IqPubSub.new)
161
+ iq.from = @jid
162
+ unsub = REXML::Element.new('unsubscribe')
163
+ unsub.attributes['node'] = node
164
+ unsub.attributes['jid'] = @jid
165
+ unsub.attributes['subid'] = subid
166
+ iq.pubsub.add(unsub)
167
+ res = nil
168
+ @observable.client.send_with_id(iq) do |reply|
169
+ res = reply.kind_of?(Jabber::Iq) and reply.type == :result
170
+ end # @stream.send_with_id(iq)
171
+ end
172
+ end
173
+
174
+ # Return the subscriptions we have in the configured PubSub service.
175
+ def subscriptions
176
+ raise_noservice if ! has_service?
177
+ @helper.get_subscriptions_from_all_nodes()
178
+ end
179
+
180
+ # Create a PubSub node (Lots of options still have to be encoded!)
181
+ def create_node(node)
182
+ raise_noservice if ! has_service?
183
+ @helper.create_node(node)
184
+ end
185
+
186
+ # Return an array of noes I own
187
+ def my_nodes
188
+ ret = []
189
+ subscriptions.each do |sub|
190
+ ret << sub.node if sub.attributes['affiliation'] == 'owner'
191
+ end
192
+ return ret
193
+ end
194
+
195
+ # Delete a PubSub node (Lots of options still have to be encoded!)
196
+ def delete_node(node)
197
+ raise_noservice if ! has_service?
198
+ @helper.delete_node(node)
199
+ end
200
+
201
+ # Publish an Item. This infers an item of Jabber::PubSub::Item kind is passed
202
+ def publish_item(node, item)
203
+ raise_noservice if ! has_service?
204
+ @helper.publish_item_to(node, item)
205
+ end
206
+
207
+ # Publish Simple Item. This is an item with one element and some text to it.
208
+ def publish_simple_item(node, text)
209
+ raise_noservice if ! has_service?
210
+
211
+ item = Jabber::PubSub::Item.new
212
+ xml = REXML::Element.new('value')
213
+ xml.text = text
214
+ item.add(xml)
215
+ publish_item(node, item)
216
+ end
217
+
218
+ # Publish atom Item. This is an item with one atom entry with title, body and time.
219
+ def publish_atom_item(node, title, body, time = Time.now)
220
+ raise_noservice if ! has_service?
221
+
222
+ item = Jabber::PubSub::Item.new
223
+ entry = REXML::Element.new('entry')
224
+ entry.add_namespace("http://www.w3.org/2005/Atom")
225
+ mytitle = REXML::Element.new('title')
226
+ mytitle.text = title
227
+ entry.add(mytitle)
228
+ mybody = REXML::Element.new('body')
229
+ mybody.text = body
230
+ entry.add(mybody)
231
+ published = REXML::Element.new("published")
232
+ published.text = time.utc.iso8601
233
+ entry.add(published)
234
+ item.add(entry)
235
+ publish_item(node, item)
236
+ end
237
+
238
+ private
239
+
240
+ def find_subids_for(node) #:nodoc:
241
+ ret = []
242
+ subscriptions.each do |subscription|
243
+ if subscription.node == node
244
+ ret << subscription.subid
245
+ end
246
+ end
247
+ return ret
248
+ end
249
+
250
+ def raise_noservice #:nodoc:
251
+ raise NoService, "Have you forgot to call #set_service ?"
252
+ end
253
+ end
254
+
255
+ # Jabber::Observable::Subscriptions - convenience class to deal with
256
+ # Presence subscriptions
257
+ #
258
+ # observable:: points to a Jabber::Observable object
259
+ class Subscriptions
260
+ def initialize(observable)
261
+ @observable = observable
262
+ end
263
+
264
+ # Ask the users specified by jids for authorization (i.e., ask them to add
265
+ # you to their contact list). If you are already in the user's contact list,
266
+ # add() will not attempt to re-request authorization. In order to force
267
+ # re-authorization, first remove() the user, then re-add them.
268
+ #
269
+ # Example usage:
270
+ #
271
+ # jabber_observable.subs.add("friend@friendosaurus.com")
272
+ #
273
+ # Because the authorization process might take a few seconds, or might
274
+ # never happen depending on when (and if) the user accepts your
275
+ # request, results are notified to observers of :new_subscription
276
+ def add(*jids)
277
+ @observable.contacts(*jids) do |friend|
278
+ next if subscribed_to? friend
279
+ friend.ask_for_authorization!
280
+ end
281
+ end
282
+
283
+ # Remove the jabber users specified by jids from the contact list.
284
+ def remove(*jids)
285
+ @observable.contacts(*jids) do |unfriend|
286
+ unfriend.unsubscribe!
287
+ end
288
+ end
289
+
290
+ # Returns true if this Jabber account is subscribed to status updates for
291
+ # the jabber user jid, false otherwise.
292
+ def subscribed_to?(jid)
293
+ @observable.contacts(jid) do |contact|
294
+ return contact.subscribed?
295
+ end
296
+ end
297
+
298
+ # Returns true if auto-accept subscriptions (friend requests) is enabled
299
+ # (default), false otherwise.
300
+ def accept?
301
+ @accept = true if @accept.nil?
302
+ @accept
303
+ end
304
+
305
+ # Change whether or not subscriptions (friend requests) are automatically accepted.
306
+ def accept=(accept_status)
307
+ @accept=accept_status
308
+ end
309
+ end
310
+
311
+ include ObservableThing
312
+
313
+ attr_reader :subs, :pubsub, :jid, :auto
314
+
315
+ # Create a new Jabber::Observable client. You will be automatically connected
316
+ # to the Jabber server and your status message will be set to the string
317
+ # passed in as the status_message argument.
318
+ #
319
+ # jabber = Jabber::Observable.new("me@example.com", "password", "Chat with me - Please!")
320
+ def initialize(jid, password, status = nil, status_message = "Available", host = nil, port=5222)
321
+ # Basic stuff
322
+ @jid = jid
323
+ @password = password
324
+ @host = host
325
+ @port = port
326
+ @disconnected = false
327
+
328
+ # Message dealing
329
+ @delivered_messages = 0
330
+ @deferred_messages = Queue.new
331
+ start_deferred_delivery_thread
332
+
333
+ # Tell everybody I am here
334
+ status(status, status_message)
335
+
336
+ # Subscription Accessor
337
+ @subs = Subscriptions.new(self)
338
+
339
+ # PubSub Accessor
340
+ @pubsub = PubSub.new(self)
341
+
342
+ # Auto Observer placeholder
343
+ @auto = nil
344
+ end
345
+
346
+ def inspect # :nodoc:
347
+ sprintf("#<%s:0x%x @jid=%s, @delivered_messages=%d, @deferred_messages=%d, @observer_count=%s, @notification_count=%s, @pubsub=%s>", self.class.name, __id__, @jid, @delivered_messages, @deferred_messages.length, observer_count.inspect, notification_count.inspect, @pubsub.inspect)
348
+ end
349
+
350
+ # Count the registered observers in each thing
351
+ def observer_count
352
+ h = {}
353
+ [ :message, :presence, :iq, :new_subscription, :subscription_request, :event ].each do |thing|
354
+ h[thing] = count_observers(thing)
355
+ end
356
+ h
357
+ end
358
+
359
+ # Count the notifications really send for each thing
360
+ def notification_count
361
+ h = {}
362
+ [ :message, :presence, :iq, :new_subscription, :subscription_request, :event ].each do |thing|
363
+ h[thing] = count_notifications(thing)
364
+ end
365
+ h
366
+ end
367
+
368
+ # Attach an auto-observer based on QObserver
369
+ def attach_auto_observer
370
+ raise StandardError, "Already attached." if ! @auto.nil?
371
+
372
+ @auto = QObserver.new
373
+ [ :message, :presence, :iq, :new_subscription, :subscription_request, :event ].each do |thing|
374
+ self.add_observer(thing, @auto)
375
+ end
376
+ end
377
+
378
+ # Dettach the auto-observer
379
+ def dettach_auto_observer
380
+ raise StandardError, "Not attached." if @auto.nil?
381
+
382
+ [ :message, :presence, :iq, :new_subscription, :subscription_request, :event ].each do |thing|
383
+ self.delete_observer(thing, @auto)
384
+ end
385
+ @auto = nil
386
+ end
387
+
388
+ # Send a message to jabber user jid.
389
+ #
390
+ # Valid message types are:
391
+ #
392
+ # * :normal (default): a normal message.
393
+ # * :chat: a one-to-one chat message.
394
+ # * :groupchat: a group-chat message.
395
+ # * :headline: a "headline" message.
396
+ # * :error: an error message.
397
+ #
398
+ # If the recipient is not in your contacts list, the message will be queued
399
+ # for later delivery, and the Contact will be automatically asked for
400
+ # authorization (see Jabber::Observable#add).
401
+ #
402
+ # message should be a string or a valid Jabber::Message object. In either case,
403
+ # the message recipient will be set to jid.
404
+ def deliver(jid, message, type=:chat)
405
+ contacts(jid) do |friend|
406
+ unless @subs.subscribed_to? friend
407
+ @subs.add(friend.jid)
408
+ return deliver_deferred(friend.jid, message, type)
409
+ end
410
+ if message.kind_of?(Jabber::Message)
411
+ msg = message
412
+ msg.to = friend.jid
413
+ else
414
+ msg = Message.new(friend.jid)
415
+ msg.type = type
416
+ msg.body = message
417
+ end
418
+ @delivered_messages += 1
419
+ send!(msg)
420
+ end
421
+ end
422
+
423
+ # Set your presence, with a message.
424
+ #
425
+ # Available values for presence are:
426
+ #
427
+ # * nil: online.
428
+ # * :chat: free for chat.
429
+ # * :away: away from the computer.
430
+ # * :dnd: do not disturb.
431
+ # * :xa: extended away.
432
+ #
433
+ # It's not possible to set an offline status - to do that, disconnect! :-)
434
+ def status(presence, message)
435
+ @presence = presence
436
+ @status_message = message
437
+ stat_msg = Jabber::Presence.new(@presence, @status_message)
438
+ send!(stat_msg)
439
+ end
440
+
441
+ # If contacts is a single contact, returns a Jabber::Contact object
442
+ # representing that user; if contacts is an array, returns an array of
443
+ # Jabber::Contact objects.
444
+ #
445
+ # When called with a block, contacts will yield each Jabber::Contact object
446
+ # in turn. This is mainly used internally, but exposed as an utility
447
+ # function.
448
+ def contacts(*contacts, &block)
449
+ @contacts ||= {}
450
+ contakts = []
451
+ contacts.each do |contact|
452
+ jid = contact.to_s
453
+ unless @contacts[jid]
454
+ @contacts[jid] = contact.respond_to?(:ask_for_authorization!) ? contact : Contact.new(self, contact)
455
+ end
456
+ yield @contacts[jid] if block_given?
457
+ contakts << @contacts[jid]
458
+ end
459
+ contakts.size > 1 ? contakts : contakts.first
460
+ end
461
+
462
+ # Returns true if the Jabber client is connected to the Jabber server,
463
+ # false otherwise.
464
+ def connected?
465
+ @client ||= nil
466
+ connected = @client.respond_to?(:is_connected?) && @client.is_connected?
467
+ return connected
468
+ end
469
+
470
+ # Direct access to the underlying Roster helper.
471
+ def roster
472
+ return @roster if @roster
473
+ self.roster = Roster::Helper.new(client)
474
+ end
475
+
476
+ # Direct access to the underlying Jabber client.
477
+ def client
478
+ connect!() unless connected?
479
+ @client
480
+ end
481
+
482
+ # Send a Jabber stanza over-the-wire.
483
+ def send!(msg)
484
+ attempts = 0
485
+ begin
486
+ attempts += 1
487
+ client.send(msg)
488
+ rescue Errno::EPIPE, IOError => e
489
+ sleep 1
490
+ disconnect
491
+ reconnect
492
+ retry unless attempts > 3
493
+ raise e
494
+ rescue Errno::ECONNRESET => e
495
+ sleep (attempts^2) * 60 + 60
496
+ disconnect
497
+ reconnect
498
+ retry unless attempts > 3
499
+ raise e
500
+ end
501
+ end
502
+
503
+ # Use this to force the client to reconnect after a force_disconnect.
504
+ def reconnect
505
+ @disconnected = false
506
+ connect!
507
+ end
508
+
509
+ # Use this to force the client to disconnect and not automatically
510
+ # reconnect.
511
+ def disconnect
512
+ disconnect!
513
+ end
514
+
515
+ # Queue messages for delivery once a user has accepted our authorization
516
+ # request. Works in conjunction with the deferred delivery thread.
517
+ #
518
+ # You can use this method if you want to manually add friends and still
519
+ # have the message queued for later delivery.
520
+ def deliver_deferred(jid, message, type)
521
+ msg = {:to => jid, :message => message, :type => type, :time => Time.now}
522
+ @deferred_messages.enq msg
523
+ end
524
+
525
+ # Sets the maximum time to wait for a message to be delivered (in
526
+ # seconds). It will be removed of the queue afterwards.
527
+
528
+ def deferred_max_wait=(seconds)
529
+ @deferred_max_wait = seconds
530
+ end
531
+
532
+ # Get the maximum time to wait for a message to be delivered. Default: 600
533
+ # seconds (10 minutes).
534
+ def deferred_max_wait
535
+ @deferred_max_wait || 600
536
+ end
537
+
538
+ private
539
+
540
+ def client=(client)
541
+ self.roster = nil # ensure we clear the roster, since that's now associated with a different client.
542
+ @client = client
543
+ end
544
+
545
+ def roster=(new_roster)
546
+ @roster = new_roster
547
+ end
548
+
549
+ def connect!
550
+ raise ConnectionError, "Connections are disabled - use Jabber::Observable::force_connect() to reconnect." if @disconnected
551
+ # Pre-connect
552
+ @connect_mutex ||= Mutex.new
553
+
554
+ # don't try to connect if another thread is already connecting.
555
+ return if @connect_mutex.locked?
556
+
557
+ @connect_mutex.lock
558
+ disconnect!(false) if connected?
559
+
560
+ # Connect
561
+ jid = JID.new(@jid)
562
+ my_client = Client.new(@jid)
563
+ my_client.connect(@host, @port)
564
+ my_client.auth(@password)
565
+ self.client = my_client
566
+
567
+ # Post-connect
568
+ register_default_callbacks
569
+ status(@presence, @status_message)
570
+ @connect_mutex.unlock
571
+ end
572
+
573
+ def disconnect!(auto_reconnect = true)
574
+ if client.respond_to?(:is_connected?) && client.is_connected?
575
+ begin
576
+ client.close
577
+ rescue Errno::EPIPE, IOError => e
578
+ # probably should log this.
579
+ nil
580
+ end
581
+ end
582
+ client = nil
583
+ @disconnected = auto_reconnect
584
+ end
585
+
586
+ def register_default_callbacks
587
+ client.add_message_callback do |message|
588
+ unless message.body.nil?
589
+ changed(:message)
590
+ notify_observers(:message, message)
591
+ end
592
+ end
593
+
594
+ roster.add_subscription_callback do |roster_item, presence|
595
+ if presence.type == :subscribed
596
+ changed(:new_subscription)
597
+ notify_observers(:new_subscription, [roster_item, presence])
598
+ end
599
+ end
600
+
601
+ roster.add_subscription_request_callback do |roster_item, presence|
602
+ roster.accept_subscription(presence.from) if @subs.accept?
603
+ changed(:subscription_request)
604
+ notify_observers(:subscription_request, [roster_item, presence])
605
+ end
606
+
607
+ client.add_iq_callback do |iq|
608
+ changed(:iq)
609
+ notify_observers(:iq, iq)
610
+ end
611
+
612
+ roster.add_presence_callback do |roster_item, old_presence, new_presence|
613
+ simple_jid = roster_item.jid.strip.to_s
614
+ presence = case new_presence.type
615
+ when nil then new_presence.show || :online
616
+ when :unavailable then :unavailable
617
+ else
618
+ nil
619
+ end
620
+
621
+ changed(:presence)
622
+ notify_observers(:presence, simple_jid, presence, new_presence)
623
+ end
624
+ end
625
+
626
+ # This thread facilitates the delivery of messages to users who haven't yet
627
+ # accepted an invitation from us. When we attempt to deliver a message, if
628
+ # the user hasn't subscribed, we place the message in a queue for later
629
+ # delivery. Once a user has accepted our authorization request, we deliver
630
+ # any messages that have been queued up in the meantime.
631
+ def start_deferred_delivery_thread
632
+ @deferred_delivery_thread = Thread.new {
633
+ loop {
634
+ sleep 3 while @deferred_messages.empty?
635
+ message = @deferred_messages.deq
636
+ if @subs.subscribed_to?(message[:to])
637
+ deliver(message[:to], message[:message], message[:type])
638
+ else
639
+ @deferred_messages.enq message unless Time.now > (deferred_max_wait + message[:time])
640
+ end
641
+ }
642
+ }
643
+ end
644
+
645
+ end
646
+ end
647
+
648
+ true