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.
- data/COPYING +339 -0
- data/README +87 -0
- data/lib/observable_thing.rb +187 -0
- data/lib/thread_store.rb +58 -0
- data/lib/xmpp4r-observable.rb +648 -0
- data/test/simple_observer.rb +28 -0
- data/test/tc_observable_thing.rb +211 -0
- data/test/tc_thread_store.rb +75 -0
- data/test/tc_xmpp4r-observable.rb +178 -0
- metadata +73 -0
data/lib/thread_store.rb
ADDED
@@ -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
|