pyu-xmpp4r-simple 0.8.8

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,819 @@
1
+ # Jabber::Simple - An extremely easy-to-use Jabber client library.
2
+ # Copyright 2006 Blaine Cook <blaine@obvious.com>, Obvious Corp.
3
+ #
4
+ # Jabber::Simple is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # Jabber::Simple is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with Jabber::Simple; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17
+
18
+ require 'time'
19
+ require 'rubygems'
20
+ require 'xmpp4r'
21
+ require 'xmpp4r/roster'
22
+ require 'xmpp4r/vcard'
23
+ require 'xmpp4r/pubsub'
24
+ require 'xmpp4r/pubsub/helper/servicehelper'
25
+ require 'xmpp4r/pubsub/helper/nodebrowser'
26
+ require 'xmpp4r/pubsub/helper/nodehelper'
27
+
28
+ module Jabber
29
+
30
+ class ConnectionError < StandardError #:nodoc:
31
+ end
32
+
33
+ class NotConnected < StandardError #:nodoc:
34
+ end
35
+
36
+ class NoPubSubService < StandardError #:nodoc:
37
+ end
38
+
39
+ class AlreadySet < StandardError #:nodoc:
40
+ end
41
+
42
+ class RegistrationError < StandardError #:nodoc:
43
+ end
44
+
45
+ module Roster
46
+ class RosterItem
47
+ def chat_state
48
+ @chat_state
49
+ end
50
+
51
+ def chat_state=(state)
52
+ @chat_state = state
53
+ end
54
+ end
55
+ end
56
+
57
+
58
+
59
+ class Contact #:nodoc:
60
+
61
+ include DRb::DRbUndumped if defined?(DRb::DRbUndumped)
62
+
63
+ def initialize(client, jid)
64
+ @jid = jid.respond_to?(:resource) ? jid : JID.new(jid)
65
+ @client = client
66
+ end
67
+
68
+ def inspect
69
+ "Jabber::Contact #{jid.to_s}"
70
+ end
71
+
72
+ def subscribed?
73
+ [:to, :both].include?(subscription)
74
+ end
75
+
76
+ def subscription
77
+ roster_item && roster_item.subscription
78
+ end
79
+
80
+ def ask_for_authorization!
81
+ subscription_request = Presence.new.set_type(:subscribe)
82
+ subscription_request.to = jid
83
+ client.send!(subscription_request)
84
+ end
85
+
86
+ def unsubscribe!
87
+ unsubscription_request = Presence.new.set_type(:unsubscribe)
88
+ unsubscription_request.to = jid
89
+ client.send!(unsubscription_request)
90
+ client.send!(unsubscription_request.set_type(:unsubscribed))
91
+ end
92
+
93
+ def jid(bare=true)
94
+ bare ? @jid.strip : @jid
95
+ end
96
+
97
+ private
98
+
99
+ def roster_item
100
+ client.roster.items[jid]
101
+ end
102
+
103
+ def client
104
+ @client
105
+ end
106
+ end
107
+
108
+ class Simple
109
+
110
+ include DRb::DRbUndumped if defined?(DRb::DRbUndumped)
111
+
112
+ def self.register(jid, password, status = nil, status_message = "Available")
113
+ new(jid, password, status, status_message, nil, 5222, nil, true)
114
+ end
115
+
116
+ # Create a new Jabber::Simple client. You will be automatically connected
117
+ # to the Jabber server and your status message will be set to the string
118
+ # passed in as the status_message argument.
119
+ # If you'd like to connect to a different talk server than the one which would
120
+ # be guessed from your jid, you may provide a server. For example, to connect
121
+ # to the gmail talk servers with a jid that doesn't end in @gmail.com, just provide
122
+ # 'talk.l.google.com' as the server. You may leave server as nil to use the default.
123
+ #
124
+ # jabber = Jabber::Simple.new("me@example.com", "password", "Chat with me - Please!")
125
+ def initialize(jid, password, status = nil, status_message = "Available", host = nil, port = 5222, server=nil, register = false)
126
+ @jid = jid
127
+ @password = password
128
+ @host = host
129
+ @port = port
130
+ @disconnected = false
131
+ @server = server
132
+ register!(password) if @register = register
133
+ status(status, status_message)
134
+ start_deferred_delivery_thread
135
+
136
+ @pubsub = @pubsub_jid = nil
137
+ begin
138
+ domain = Jabber::JID.new(@jid).domain
139
+ @pubsub_jid = "pubsub." + domain
140
+ set_pubsub_service(@pubsub_jid)
141
+ rescue
142
+ @pubsub = @pubsub_jid = nil
143
+ end
144
+ end
145
+
146
+ def inspect #:nodoc:
147
+ "Jabber::Simple #{@jid}"
148
+ end
149
+
150
+ # Send a message to jabber user jid.
151
+ # It is possible to send message to multiple users at once,
152
+ # just supply array of jids.
153
+ #
154
+ # Example usage:
155
+ #
156
+ # jabber_simple.deliver("foo@example.com", "blabla")
157
+ # jabber_simple.deliver(["foo@example.com", "bar@example.com"], "blabla")
158
+ #
159
+ # Valid message types are:
160
+ #
161
+ # * :normal (default): a normal message.
162
+ # * :chat: a one-to-one chat message.
163
+ # * :groupchat: a group-chat message.
164
+ # * :headline: a "headline" message.
165
+ # * :error: an error message.
166
+ #
167
+ # If the recipient is not in your contacts list, the message will be queued
168
+ # for later delivery, and the Contact will be automatically asked for
169
+ # authorization (see Jabber::Simple#add).
170
+ #
171
+ # message should be a string or a valid Jabber::Message object. In either case,
172
+ # the message recipient will be set to jid.
173
+ def deliver(jids, message, type = :chat, chat_state = :active)
174
+ contacts(jids) do |friend|
175
+ unless subscribed_to? friend
176
+ add(friend.jid)
177
+ deliver_deferred(friend.jid, message, type)
178
+ next
179
+ end
180
+ if message.kind_of?(Jabber::Message)
181
+ msg = message
182
+ msg.to = friend.jid
183
+ else
184
+ msg = Message.new(friend.jid)
185
+ msg.type = type
186
+ msg.chat_state = chat_state
187
+ msg.body = message
188
+ end
189
+ send!(msg)
190
+ end
191
+ end
192
+
193
+ # Set your presence, with a message.
194
+ #
195
+ # Available values for presence are:
196
+ #
197
+ # * nil: online.
198
+ # * :chat: free for chat.
199
+ # * :away: away from the computer.
200
+ # * :dnd: do not disturb.
201
+ # * :xa: extended away.
202
+ #
203
+ # It's not possible to set an offline status - to do that, disconnect! :-)
204
+ def status(presence, message)
205
+ @presence = presence
206
+ @status_message = message
207
+ stat_msg = Presence.new(@presence, @status_message)
208
+
209
+ if !avatar_hash.nil?
210
+ x = Jabber::X.new
211
+ x.add_namespace('vcard-temp:x:update')
212
+ photo = REXML::Element::new("photo")
213
+ photo.add(REXML::Text.new(avatar_hash))
214
+ x.add(photo)
215
+ stat_msg.add_element(x)
216
+ end
217
+
218
+ send!(stat_msg)
219
+ end
220
+
221
+ # Ask the users specified by jids for authorization (i.e., ask them to add
222
+ # you to their contact list). If you are already in the user's contact list,
223
+ # add() will not attempt to re-request authorization. In order to force
224
+ # re-authorization, first remove() the user, then re-add them.
225
+ #
226
+ # Example usage:
227
+ #
228
+ # jabber_simple.add("friend@friendosaurus.com")
229
+ #
230
+ # Because the authorization process might take a few seconds, or might
231
+ # never happen depending on when (and if) the user accepts your
232
+ # request, results are placed in the Jabber::Simple#new_subscriptions queue.
233
+ def add(*jids)
234
+ contacts(*jids) do |friend|
235
+ next if subscribed_to? friend
236
+ friend.ask_for_authorization!
237
+ end
238
+ end
239
+
240
+ # Remove the jabber users specified by jids from the contact list.
241
+ def remove(*jids)
242
+ contacts(*jids) do |unfriend|
243
+ unfriend.unsubscribe!
244
+ end
245
+ end
246
+
247
+ # Returns true if this Jabber account is subscribed to status updates for
248
+ # the jabber user jid, false otherwise.
249
+ def subscribed_to?(jid)
250
+ contacts(jid) do |contact|
251
+ return contact.subscribed?
252
+ end
253
+ end
254
+
255
+ # Returns array of roster items.
256
+ def contact_list
257
+ roster.items.values
258
+ end
259
+
260
+ # Retrieve vCard of an entity
261
+ #
262
+ # Raises exception upon retrieval error, please catch that!
263
+ #
264
+ # Usage of Threads is suggested here as vCards can be very
265
+ # big.
266
+ #
267
+ # jid:: [Jabber::JID] or nil (should be stripped, nil for the client's own vCard)
268
+ # result:: [Jabber::IqVcard] or nil (nil results may be handled as empty vCards)
269
+ def get_info(jid = nil)
270
+ Jabber::Vcard::Helper.new(client).get(jid)
271
+ end
272
+
273
+ # Update your own vCard
274
+ #
275
+ # Raises exception when setting fails
276
+ #
277
+ # Usage of Threads suggested here, too. The function
278
+ # waits for approval from the server.
279
+ # e.g.:
280
+ #
281
+ # info = Jabber::Vcard::IqVcard.new
282
+ # info["NICKNAME"] = "amay"
283
+ # jabber.update_info(info)
284
+ def update_info(vcard)
285
+ Jabber::Vcard::Helper.new(client).set(vcard)
286
+ end
287
+
288
+ # Returns SHA1 hash of the avatar image data.
289
+ #
290
+ # This hash is then included in the user's presence information.
291
+ def avatar_hash
292
+ vcard = get_info
293
+ if !vcard.nil? && !vcard["PHOTO/BINVAL"].nil?
294
+ Digest::SHA1.hexdigest(Base64.decode64(vcard["PHOTO/BINVAL"]))
295
+ else
296
+ nil
297
+ end
298
+ end
299
+
300
+ # Returns true if the Jabber client is connected to the Jabber server,
301
+ # false otherwise.
302
+ def connected?
303
+ @client ||= nil
304
+ connected = @client.respond_to?(:is_connected?) && @client.is_connected?
305
+ return connected
306
+ end
307
+
308
+ # Returns an array of messages received since the last time
309
+ # received_messages was called. Passing a block will yield each message in
310
+ # turn, allowing you to break part-way through processing (especially
311
+ # useful when your message handling code is not thread-safe (e.g.,
312
+ # ActiveRecord).
313
+ #
314
+ # e.g.:
315
+ #
316
+ # jabber.received_messages do |message|
317
+ # puts "Received message from #{message.from}: #{message.body}"
318
+ # end
319
+ def received_messages(&block)
320
+ dequeue(:received_messages, &block)
321
+ end
322
+
323
+ # Returns true if there are unprocessed received messages waiting in the
324
+ # queue, false otherwise.
325
+ def received_messages?
326
+ !queue(:received_messages).empty?
327
+ end
328
+
329
+ # Returns an array of iq stanzas received since the last time
330
+ # iq_stanzas was called. There will be no stanzas in this queue
331
+ # unless you have enabled capture of <iq/> stanzas using the
332
+ # capture_iq_stanzas! method. Passing a block will yield each stanza in
333
+ # turn, allowing you to break part-way through processing (especially
334
+ # useful when your message handling code is not thread-safe (e.g.,
335
+ # ActiveRecord).
336
+ #
337
+ # e.g.:
338
+ #
339
+ # jabber.capture_iq_stanzas!
340
+ # jabber.iq_stanzas do |iq|
341
+ # puts "Received iq stanza from #{iq.from}"
342
+ # end
343
+ def iq_stanza(&block)
344
+ dequeue(:iq_stanzas, &block)
345
+ end
346
+
347
+ # Returns true if there are unprocessed iq stanzas waiting in the
348
+ # queue, false otherwise.
349
+ def iq_stanzas?
350
+ !queue(:iq_stanzas).empty?
351
+ end
352
+
353
+ # Tell the client to capture (or stop capturing) <iq/> stanzas
354
+ # addressed to it. When the client is initially started, this
355
+ # is false.
356
+ def capture_iq_stanzas!(capture=true)
357
+ @iq_mutex.synchronize { @capture_iq_stanzas = capture }
358
+ end
359
+
360
+ # Returns true if iq stanzas will be captured in the queue, as
361
+ # they arrive, false otherwise.
362
+ def capture_iq_stanzas?
363
+ @capture_iq_stanzas
364
+ end
365
+
366
+ # Returns an array of presence updates received since the last time
367
+ # presence_updates was called. Passing a block will yield each update in
368
+ # turn, allowing you to break part-way through processing (especially
369
+ # useful when your presence handling code is not thread-safe (e.g.,
370
+ # ActiveRecord).
371
+ #
372
+ # e.g.:
373
+ #
374
+ # jabber.presence_updates do |friend, new_presence|
375
+ # puts "Received presence update from #{friend}: #{new_presence}"
376
+ # end
377
+ def presence_updates(&block)
378
+ updates = []
379
+ @presence_mutex.synchronize do
380
+ dequeue(:presence_updates) do |friend|
381
+ presence = @presence_updates[friend]
382
+ next unless presence
383
+ new_update = [friend, presence[0], presence[1]]
384
+ yield new_update if block_given?
385
+ updates << new_update
386
+ @presence_updates.delete(friend)
387
+ end
388
+ end
389
+ return updates
390
+ end
391
+
392
+ # Returns true if there are unprocessed presence updates waiting in the
393
+ # queue, false otherwise.
394
+ def presence_updates?
395
+ !queue(:presence_updates).empty?
396
+ end
397
+
398
+ # Returns an array of subscription notifications received since the last
399
+ # time new_subscriptions was called. Passing a block will yield each update
400
+ # in turn, allowing you to break part-way through processing (especially
401
+ # useful when your subscription handling code is not thread-safe (e.g.,
402
+ # ActiveRecord).
403
+ #
404
+ # e.g.:
405
+ #
406
+ # jabber.new_subscriptions do |friend, presence|
407
+ # puts "Received presence update from #{friend.to_s}: #{presence}"
408
+ # end
409
+ def new_subscriptions(&block)
410
+ dequeue(:new_subscriptions, &block)
411
+ end
412
+
413
+ # Returns true if there are unprocessed presence updates waiting in the
414
+ # queue, false otherwise.
415
+ def new_subscriptions?
416
+ !queue(:new_subscriptions).empty?
417
+ end
418
+
419
+ # Returns an array of subscription notifications received since the last
420
+ # time subscription_requests was called. Passing a block will yield each update
421
+ # in turn, allowing you to break part-way through processing (especially
422
+ # useful when your subscription handling code is not thread-safe (e.g.,
423
+ # ActiveRecord).
424
+ #
425
+ # e.g.:
426
+ #
427
+ # jabber.subscription_requests do |friend, presence|
428
+ # puts "Received presence update from #{friend.to_s}: #{presence}"
429
+ # end
430
+ def subscription_requests(&block)
431
+ dequeue(:subscription_requests, &block)
432
+ end
433
+
434
+ # Returns true if auto-accept subscriptions (friend requests) is enabled
435
+ # (default), false otherwise.
436
+ def accept_subscriptions?
437
+ @accept_subscriptions = true if @accept_subscriptions.nil?
438
+ @accept_subscriptions
439
+ end
440
+
441
+ # Change whether or not subscriptions (friend requests) are automatically accepted.
442
+ def accept_subscriptions=(accept_status)
443
+ @accept_subscriptions = accept_status
444
+ end
445
+
446
+ # Direct access to the underlying Roster helper.
447
+ def roster
448
+ return @roster if @roster
449
+ self.roster = Roster::Helper.new(client)
450
+ end
451
+
452
+ # Direct access to the underlying Jabber client.
453
+ def client
454
+ connect!() unless connected?
455
+ @client
456
+ end
457
+ def register!(password)
458
+ attempt! {
459
+ begin
460
+ client.register(password)
461
+ @register = false
462
+ disconnect
463
+ reconnect
464
+ rescue Exception => e
465
+ error_msg = "Error registering: #{e.message}\n\n"
466
+ if e.respond_to?('error') && e.error.type == :modify
467
+ error_msg += "Accepted registration information:\n"
468
+ instructions, fields = client.register_info
469
+ fields.each { |info|
470
+ error_msg += "* #{info}\n"
471
+ }
472
+ error_msg += "(#{instructions})"
473
+ end
474
+ raise RegistrationError, error_msg
475
+ end
476
+ }
477
+ end
478
+ # Send a Jabber stanza over-the-wire.
479
+ def send!(msg)
480
+ attempt! {
481
+ client.send(msg)
482
+ }
483
+ end
484
+ def attempt!
485
+ attempts = 0
486
+ begin
487
+ attempts += 1
488
+ yield
489
+ rescue Errno::EPIPE, IOError => e
490
+ sleep 1
491
+ disconnect
492
+ reconnect
493
+ retry unless attempts > 3
494
+ raise e
495
+ rescue Errno::ECONNRESET => e
496
+ sleep (attempts^2) * 60 + 60
497
+ disconnect
498
+ reconnect
499
+ retry unless attempts > 3
500
+ raise e
501
+ end
502
+ end
503
+
504
+ # Use this to force the client to reconnect after a force_disconnect.
505
+ def reconnect
506
+ @disconnected = false
507
+ connect!
508
+ end
509
+
510
+ # Use this to force the client to disconnect and not automatically
511
+ # reconnect.
512
+ def disconnect
513
+ disconnect!
514
+ end
515
+
516
+ # Queue messages for delivery once a user has accepted our authorization
517
+ # request. Works in conjunction with the deferred delivery thread.
518
+ #
519
+ # You can use this method if you want to manually add friends and still
520
+ # have the message queued for later delivery.
521
+ def deliver_deferred(jid, message, type)
522
+ msg = {:to => jid, :message => message, :type => type}
523
+ queue(:pending_messages) << [msg]
524
+ end
525
+
526
+ # Checks if the PubSub service is set
527
+ def has_pubsub?
528
+ ! @pubsub.nil?
529
+ end
530
+
531
+ # Sets the PubSub service. Just one service is allowed.
532
+ def set_pubsub_service(service)
533
+ raise NotConnected, "You are not connected" if @disconnected
534
+ raise AlreadySet, "You already have a PubSub service. Currently it's not allowed to have more." if has_pubsub?
535
+ @pubsub = PubSub::ServiceHelper.new(@client, service)
536
+ @pubsub_jid = service
537
+
538
+ @pubsub.add_event_callback do |event|
539
+ queue(:received_events) << event
540
+ end
541
+ end
542
+
543
+ # Subscribe to a node.
544
+ def pubsubscribe_to(node)
545
+ raise NoPubSubService, "Have you forgot to call #set_pubsub_service ?" if ! has_pubsub?
546
+ @pubsub.subscribe_to(node)
547
+ end
548
+
549
+ # Unsubscribe from a node.
550
+ def pubunsubscribe_from(node)
551
+ raise NoPubSubService, "Have you forgot to call #set_pubsub_service ?" if ! has_pubsub?
552
+
553
+ # FIXME
554
+ # @pubsub.unsubscribe_from(node)
555
+ # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
556
+ # The above should just work, but I had to reimplement it since XMPP4R doesn't support subids
557
+ # and OpenFire (the Jabber Server I am testing against) seems to require it.
558
+
559
+ subids = find_subids_for(node)
560
+ return if subids.empty?
561
+
562
+ subids.each do |subid|
563
+ iq = Jabber::Iq.new(:set, @pubsub_jid)
564
+ iq.add(Jabber::PubSub::IqPubSub.new)
565
+ iq.from = @jid
566
+ unsub = REXML::Element.new('unsubscribe')
567
+ unsub.attributes['node'] = node
568
+ unsub.attributes['jid'] = @jid
569
+ unsub.attributes['subid'] = subid
570
+ iq.pubsub.add(unsub)
571
+ res = nil
572
+ @client.send_with_id(iq) do |reply|
573
+ res = reply.kind_of?(Jabber::Iq) and reply.type == :result
574
+ end # @stream.send_with_id(iq)
575
+ end
576
+ end
577
+
578
+ # Return the subscriptions we have in the configured PubSub service.
579
+ def pubsubscriptions
580
+ raise NoPubSubService, "Have you forgot to call #set_pubsub_service ?" if ! has_pubsub?
581
+ @pubsub.get_subscriptions_from_all_nodes()
582
+ end
583
+
584
+ # Just like #received_messages, but for PubSub events
585
+ def received_events(&block)
586
+ dequeue(:received_events, &block)
587
+ end
588
+
589
+ # Returns true if there are unprocessed received events waiting in the
590
+ # queue, false otherwise.
591
+ def received_events?
592
+ !queue(:received_events).empty?
593
+ end
594
+
595
+ # Create a PubSub node (Lots of options still have to be encoded!)
596
+ def create_node(node)
597
+ raise NoPubSubService, "Have you forgot to call #set_pubsub_service ?" if ! has_pubsub?
598
+ @pubsub.create_node(node)
599
+ end
600
+
601
+ # Return an array of nodes I own
602
+ def my_nodes
603
+ ret = []
604
+ pubsubscriptions.each do |sub|
605
+ ret << sub.node if sub.attributes['affiliation'] == 'owner'
606
+ end
607
+ return ret
608
+ end
609
+
610
+ # Delete a PubSub node (Lots of options still have to be encoded!)
611
+ def delete_node(node)
612
+ raise NoPubSubService, "Have you forgot to call #set_pubsub_service ?" if ! has_pubsub?
613
+ @pubsub.delete_node(node)
614
+ end
615
+
616
+ # Publish an Item. This infers an item of Jabber::PubSub::Item kind is passed
617
+ def publish_item(node, item)
618
+ raise NoPubSubService, "Have you forgot to call #set_pubsub_service ?" if ! has_pubsub?
619
+ @pubsub.publish_item_to(node, item)
620
+ end
621
+
622
+ # Publish Simple Item. This is an item with one element and some text to it.
623
+ def publish_simple_item(node, text)
624
+ raise NoPubSubService, "Have you forgot to call #set_pubsub_service ?" if ! has_pubsub?
625
+
626
+ item = Jabber::PubSub::Item.new
627
+ xml = REXML::Element.new('value')
628
+ xml.text = text
629
+ item.add(xml)
630
+ publish_item(node, item)
631
+ end
632
+
633
+ # Publish atom Item. This is an item with one atom entry with title, body and time.
634
+ def publish_atom_item(node, title, body, time = Time.now)
635
+ raise NoPubSubService, "Have you forgot to call #set_pubsub_service ?" if ! has_pubsub?
636
+
637
+ item = Jabber::PubSub::Item.new
638
+ entry = REXML::Element.new('entry')
639
+ entry.add_namespace("http://www.w3.org/2005/Atom")
640
+ mytitle = REXML::Element.new('title')
641
+ mytitle.text = title
642
+ entry.add(mytitle)
643
+ mybody = REXML::Element.new('body')
644
+ mybody.text = body
645
+ entry.add(mybody)
646
+ published = REXML::Element.new("published")
647
+ published.text = time.utc.iso8601
648
+ entry.add(published)
649
+ item.add(entry)
650
+ publish_item(node, item)
651
+ end
652
+
653
+ private
654
+
655
+ # If contacts is a single contact, returns a Jabber::Contact object
656
+ # representing that user; if contacts is an array, returns an array of
657
+ # Jabber::Contact objects.
658
+ #
659
+ # When called with a block, contacts will yield each Jabber::Contact object
660
+ # in turn. This is used internally.
661
+ def contacts(*contacts, &block)
662
+ contacts.flatten!
663
+ @contacts ||= {}
664
+ contakts = []
665
+ contacts.each do |contact|
666
+ jid = contact.to_s
667
+ unless @contacts[jid]
668
+ @contacts[jid] = contact.respond_to?(:ask_for_authorization!) ? contact : Contact.new(self, contact)
669
+ end
670
+ yield @contacts[jid] if block_given?
671
+ contakts << @contacts[jid]
672
+ end
673
+ contakts.size > 1 ? contakts : contakts.first
674
+ end
675
+
676
+ def find_subids_for(node)
677
+ ret = []
678
+ pubsubscriptions.each do |subscription|
679
+ if subscription.node == node
680
+ ret << subscription.subid
681
+ end
682
+ end
683
+ return ret
684
+ end
685
+
686
+ def client=(client)
687
+ self.roster = nil # ensure we clear the roster, since that's now associated with a different client.
688
+ @client = client
689
+ end
690
+
691
+ def roster=(new_roster)
692
+ @roster = new_roster
693
+ end
694
+
695
+ def connect!
696
+ raise ConnectionError, "Connections are disabled - use Jabber::Simple::force_connect() to reconnect." if @disconnected
697
+ # Pre-connect
698
+ @connect_mutex ||= Mutex.new
699
+
700
+ # don't try to connect if another thread is already connecting.
701
+ return if @connect_mutex.locked?
702
+
703
+ @connect_mutex.lock
704
+ disconnect!(false) if connected?
705
+
706
+ # Connect
707
+ jid = JID.new(@jid)
708
+ my_client = Client.new(@jid)
709
+ my_client.connect(@host, @port)
710
+ # (TODO: test w/ gtalk --danbri) my_client.connect(@server)
711
+ my_client.auth(@password) unless @register
712
+ self.client = my_client
713
+
714
+ # Post-connect
715
+ register_default_callbacks unless @register
716
+ status(@presence, @status_message) unless @register
717
+ @connect_mutex.unlock
718
+ end
719
+
720
+ def disconnect!(auto_reconnect = true)
721
+ if client.respond_to?(:is_connected?) && client.is_connected?
722
+ begin
723
+ client.close
724
+ rescue Errno::EPIPE, IOError => e
725
+ # probably should log this.
726
+ nil
727
+ end
728
+ end
729
+ client = nil
730
+ @disconnected = auto_reconnect
731
+ end
732
+
733
+ def register_default_callbacks
734
+ client.add_message_callback do |message|
735
+ queue(:received_messages) << message unless message.body.nil?
736
+ from = roster.find(message.from).values.first
737
+ if !message.chat_state.nil? && from.chat_state != message.chat_state
738
+ from.chat_state = message.chat_state
739
+ end
740
+ end
741
+
742
+ roster.add_subscription_callback do |roster_item, presence|
743
+ if presence.type == :subscribed
744
+ queue(:new_subscriptions) << [roster_item, presence]
745
+ end
746
+ end
747
+
748
+ roster.add_subscription_request_callback do |roster_item, presence|
749
+ if accept_subscriptions?
750
+ roster.accept_subscription(presence.from)
751
+ else
752
+ queue(:subscription_requests) << [roster_item, presence]
753
+ end
754
+ end
755
+
756
+ @iq_mutex = Mutex.new
757
+ capture_iq_stanzas!(false)
758
+ client.add_iq_callback do |iq|
759
+ queue(:iq_messages) << iq if capture_iq_stanzas?
760
+ end
761
+
762
+ @presence_updates = {}
763
+ @presence_mutex = Mutex.new
764
+ roster.add_presence_callback do |roster_item, old_presence, new_presence|
765
+ simple_jid = roster_item.jid.strip.to_s
766
+ presence = case new_presence.type
767
+ when nil then new_presence.show || :online
768
+ when :unavailable then :unavailable
769
+ else
770
+ nil
771
+ end
772
+
773
+ if presence && @presence_updates[simple_jid] != presence
774
+ queue(:presence_updates) << simple_jid
775
+ @presence_mutex.synchronize { @presence_updates[simple_jid] = [presence, new_presence.status] }
776
+ end
777
+ end
778
+ end
779
+
780
+ # This thread facilitates the delivery of messages to users who haven't yet
781
+ # accepted an invitation from us. When we attempt to deliver a message, if
782
+ # the user hasn't subscribed, we place the message in a queue for later
783
+ # delivery. Once a user has accepted our authorization request, we deliver
784
+ # any messages that have been queued up in the meantime.
785
+ def start_deferred_delivery_thread #:nodoc:
786
+ Thread.new {
787
+ loop {
788
+ messages = [queue(:pending_messages).pop].flatten
789
+ messages.each do |message|
790
+ if subscribed_to?(message[:to])
791
+ deliver(message[:to], message[:message], message[:type])
792
+ else
793
+ queue(:pending_messages) << message
794
+ end
795
+ end
796
+ sleep 60
797
+ }
798
+ }
799
+ end
800
+
801
+ def queue(queue)
802
+ @queues ||= Hash.new { |h,k| h[k] = Queue.new }
803
+ @queues[queue]
804
+ end
805
+
806
+ def dequeue(queue, non_blocking = true, max_items = 100, &block)
807
+ queue_items = []
808
+ max_items.times do
809
+ break if queue(queue).empty?
810
+ queue_item = queue(queue).pop(non_blocking)
811
+ queue_items << queue_item
812
+ yield queue_item if block_given?
813
+ end
814
+ queue_items
815
+ end
816
+ end
817
+ end
818
+
819
+ true