spectra-xmpp4r-observable 0.5.1

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.
@@ -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