xmpp4r 0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. data/COPYING +340 -0
  2. data/ChangeLog +28 -0
  3. data/LICENSE +59 -0
  4. data/README +20 -0
  5. data/Rakefile +103 -0
  6. data/UPDATING +40 -0
  7. data/data/doc/xmpp4r/examples/advanced/adventure/README +57 -0
  8. data/data/doc/xmpp4r/examples/advanced/adventure/adventure.rb +23 -0
  9. data/data/doc/xmpp4r/examples/advanced/adventure/adventuremuc.rb +136 -0
  10. data/data/doc/xmpp4r/examples/advanced/adventure/cube.xml +15 -0
  11. data/data/doc/xmpp4r/examples/advanced/adventure/tower.xml +69 -0
  12. data/data/doc/xmpp4r/examples/advanced/adventure/world.rb +425 -0
  13. data/data/doc/xmpp4r/examples/advanced/fileserve.conf +11 -0
  14. data/data/doc/xmpp4r/examples/advanced/fileserve.rb +344 -0
  15. data/data/doc/xmpp4r/examples/advanced/getonline.rb +56 -0
  16. data/data/doc/xmpp4r/examples/advanced/gtkmucclient.rb +315 -0
  17. data/data/doc/xmpp4r/examples/advanced/migrate.rb +89 -0
  18. data/data/doc/xmpp4r/examples/advanced/minimuc.rb +266 -0
  19. data/data/doc/xmpp4r/examples/advanced/recvfile.rb +83 -0
  20. data/data/doc/xmpp4r/examples/advanced/rosterdiscovery.rb +130 -0
  21. data/data/doc/xmpp4r/examples/advanced/sendfile.conf +10 -0
  22. data/data/doc/xmpp4r/examples/advanced/sendfile.rb +72 -0
  23. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr.rb +51 -0
  24. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_jabber.rb +43 -0
  25. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_test.rb +10 -0
  26. data/data/doc/xmpp4r/examples/advanced/versionpoll.rb +90 -0
  27. data/data/doc/xmpp4r/examples/advanced/xmpping.rb +134 -0
  28. data/data/doc/xmpp4r/examples/advanced/xmppingrc.sample +9 -0
  29. data/data/doc/xmpp4r/examples/basic/change_password.rb +41 -0
  30. data/data/doc/xmpp4r/examples/basic/client.rb +68 -0
  31. data/data/doc/xmpp4r/examples/basic/component.rb +11 -0
  32. data/data/doc/xmpp4r/examples/basic/echo_nonthreaded.rb +32 -0
  33. data/data/doc/xmpp4r/examples/basic/echo_threaded.rb +32 -0
  34. data/data/doc/xmpp4r/examples/basic/jabbersend.rb +41 -0
  35. data/data/doc/xmpp4r/examples/basic/mass_sender.rb +67 -0
  36. data/data/doc/xmpp4r/examples/basic/mucinfo.rb +39 -0
  37. data/data/doc/xmpp4r/examples/basic/mucsimplebot.rb +83 -0
  38. data/data/doc/xmpp4r/examples/basic/register.rb +25 -0
  39. data/data/doc/xmpp4r/examples/basic/remove_registration.rb +18 -0
  40. data/data/doc/xmpp4r/examples/basic/roster.rb +42 -0
  41. data/data/doc/xmpp4r/examples/basic/rosterprint.rb +50 -0
  42. data/data/doc/xmpp4r/examples/basic/rosterrename.rb +34 -0
  43. data/data/doc/xmpp4r/examples/basic/rosterwatch.rb +172 -0
  44. data/data/doc/xmpp4r/examples/basic/send_vcard.rb +68 -0
  45. data/data/doc/xmpp4r/examples/basic/versionbot.rb +75 -0
  46. data/data/doc/xmpp4r/examples/buggy/jabber2jabber/jabber2jabber.rb +18 -0
  47. data/data/doc/xmpp4r/examples/buggy/miniedgarr_cgi.rb +192 -0
  48. data/data/doc/xmpp4r/examples/buggy/miniedgarr_watch.rb +82 -0
  49. data/lib/callbacks.rb +122 -0
  50. data/lib/xmpp4r/authenticationfailure.rb +13 -0
  51. data/lib/xmpp4r/bytestreams/helper/filetransfer.rb +315 -0
  52. data/lib/xmpp4r/bytestreams/helper/ibb/base.rb +256 -0
  53. data/lib/xmpp4r/bytestreams/helper/ibb/initiator.rb +30 -0
  54. data/lib/xmpp4r/bytestreams/helper/ibb/target.rb +46 -0
  55. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb +151 -0
  56. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb +85 -0
  57. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/server.rb +178 -0
  58. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/socks5.rb +56 -0
  59. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb +61 -0
  60. data/lib/xmpp4r/bytestreams/iq/bytestreams.rb +177 -0
  61. data/lib/xmpp4r/bytestreams/iq/si.rb +221 -0
  62. data/lib/xmpp4r/bytestreams.rb +11 -0
  63. data/lib/xmpp4r/client.rb +249 -0
  64. data/lib/xmpp4r/component.rb +103 -0
  65. data/lib/xmpp4r/connection.rb +166 -0
  66. data/lib/xmpp4r/dataforms/x/data.rb +248 -0
  67. data/lib/xmpp4r/dataforms.rb +1 -0
  68. data/lib/xmpp4r/debuglog.rb +34 -0
  69. data/lib/xmpp4r/delay/x/delay.rb +100 -0
  70. data/lib/xmpp4r/delay.rb +1 -0
  71. data/lib/xmpp4r/discovery/iq/discoinfo.rb +225 -0
  72. data/lib/xmpp4r/discovery/iq/discoitems.rb +162 -0
  73. data/lib/xmpp4r/discovery.rb +2 -0
  74. data/lib/xmpp4r/error.rb +230 -0
  75. data/lib/xmpp4r/errorexception.rb +32 -0
  76. data/lib/xmpp4r/feature_negotiation/iq/feature.rb +42 -0
  77. data/lib/xmpp4r/feature_negotiation.rb +1 -0
  78. data/lib/xmpp4r/idgenerator.rb +37 -0
  79. data/lib/xmpp4r/iq.rb +229 -0
  80. data/lib/xmpp4r/jid.rb +167 -0
  81. data/lib/xmpp4r/message.rb +171 -0
  82. data/lib/xmpp4r/muc/helper/mucbrowser.rb +107 -0
  83. data/lib/xmpp4r/muc/helper/mucclient.rb +382 -0
  84. data/lib/xmpp4r/muc/helper/simplemucclient.rb +222 -0
  85. data/lib/xmpp4r/muc/x/muc.rb +98 -0
  86. data/lib/xmpp4r/muc/x/mucuserinvite.rb +58 -0
  87. data/lib/xmpp4r/muc/x/mucuseritem.rb +148 -0
  88. data/lib/xmpp4r/muc.rb +6 -0
  89. data/lib/xmpp4r/presence.rb +255 -0
  90. data/lib/xmpp4r/query.rb +43 -0
  91. data/lib/xmpp4r/rexmladdons.rb +826 -0
  92. data/lib/xmpp4r/roster/helper/roster.rb +514 -0
  93. data/lib/xmpp4r/roster/iq/roster.rb +244 -0
  94. data/lib/xmpp4r/roster/x/roster.rb +155 -0
  95. data/lib/xmpp4r/roster.rb +4 -0
  96. data/lib/xmpp4r/sasl.rb +167 -0
  97. data/lib/xmpp4r/stream.rb +543 -0
  98. data/lib/xmpp4r/streamparser.rb +77 -0
  99. data/lib/xmpp4r/vcard/helper/vcard.rb +86 -0
  100. data/lib/xmpp4r/vcard/iq/vcard.rb +102 -0
  101. data/lib/xmpp4r/vcard.rb +3 -0
  102. data/lib/xmpp4r/version/helper/responder.rb +71 -0
  103. data/lib/xmpp4r/version/helper/simpleresponder.rb +44 -0
  104. data/lib/xmpp4r/version/iq/version.rb +118 -0
  105. data/lib/xmpp4r/version.rb +3 -0
  106. data/lib/xmpp4r/x.rb +43 -0
  107. data/lib/xmpp4r/xmlstanza.rb +174 -0
  108. data/lib/xmpp4r/xmpp4r.rb +16 -0
  109. data/lib/xmpp4r.rb +122 -0
  110. data/setup.rb +1360 -0
  111. data/test/bytestreams/tc_ibb.rb +186 -0
  112. data/test/bytestreams/tc_socks5bytestreams.rb +57 -0
  113. data/test/delay/tc_xdelay.rb +51 -0
  114. data/test/lib/clienttester.rb +110 -0
  115. data/test/muc/tc_muc_mucclient.rb +569 -0
  116. data/test/muc/tc_muc_simplemucclient.rb +72 -0
  117. data/test/roster/.tc_helper.rb.swp +0 -0
  118. data/test/roster/tc_helper.rb +389 -0
  119. data/test/roster/tc_iqqueryroster.rb +140 -0
  120. data/test/roster/tc_xroster.rb +70 -0
  121. data/test/tc_callbacks.rb +128 -0
  122. data/test/tc_class_names.rb +129 -0
  123. data/test/tc_client.rb +30 -0
  124. data/test/tc_error.rb +103 -0
  125. data/test/tc_idgenerator.rb +30 -0
  126. data/test/tc_iq.rb +109 -0
  127. data/test/tc_iqquery.rb +31 -0
  128. data/test/tc_jid.rb +202 -0
  129. data/test/tc_message.rb +114 -0
  130. data/test/tc_presence.rb +148 -0
  131. data/test/tc_stream.rb +182 -0
  132. data/test/tc_streamError.rb +87 -0
  133. data/test/tc_streamSend.rb +59 -0
  134. data/test/tc_streamThreaded.rb +168 -0
  135. data/test/tc_xmlstanza.rb +76 -0
  136. data/test/ts_xmpp4r.rb +34 -0
  137. data/test/vcard/tc_iqvcard.rb +52 -0
  138. data/test/version/tc_helper.rb +46 -0
  139. data/test/version/tc_iqqueryversion.rb +96 -0
  140. data/tools/doctoweb.bash +30 -0
  141. data/tools/gen_requires.bash +10 -0
  142. metadata +232 -0
@@ -0,0 +1,514 @@
1
+ # =XMPP4R - XMPP Library for Ruby
2
+ # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
3
+ # Website::http://home.gna.org/xmpp4r/
4
+
5
+ require 'callbacks'
6
+ require 'thread'
7
+ require 'xmpp4r/roster/iq/roster'
8
+
9
+ module Jabber
10
+ module Roster
11
+ ##
12
+ # The Roster helper intercepts <tt><iq/></tt> stanzas with Jabber::IqQueryRoster
13
+ # and <tt><presence/></tt> stanzas, but provides cbs which allow the programmer
14
+ # to keep track of updates.
15
+ class Helper
16
+ ##
17
+ # All items in your roster
18
+ # items:: [Hash] ([JID] => [Roster::Helper::RosterItem])
19
+ attr_reader :items
20
+
21
+ ##
22
+ # Initialize a new Roster helper
23
+ #
24
+ # Registers its cbs (prio = 120, ref = self)
25
+ #
26
+ # Request a roster
27
+ # (Remember to send initial presence afterwards!)
28
+ def initialize(stream)
29
+ @stream = stream
30
+ @items = {}
31
+ @items_lock = Mutex.new
32
+ @query_cbs = CallbackList.new
33
+ @update_cbs = CallbackList.new
34
+ @presence_cbs = CallbackList.new
35
+ @subscription_cbs = CallbackList.new
36
+ @subscription_request_cbs = CallbackList.new
37
+
38
+ # Register cbs
39
+ stream.add_iq_callback(120, self) { |iq|
40
+ handle_iq(iq)
41
+ }
42
+ stream.add_presence_callback(120, self) { |pres|
43
+ handle_presence(pres)
44
+ }
45
+
46
+ # Request the roster
47
+ rosterget = Iq.new_rosterget
48
+ stream.send(rosterget)
49
+ end
50
+
51
+ ##
52
+ # Add a callback to be called when a query has been processed
53
+ #
54
+ # Because update callbacks are called for each roster item,
55
+ # this may be appropriate to notify that *anything* has updated.
56
+ #
57
+ # Arguments for callback block: The received <tt><iq/></tt> stanza
58
+ def add_query_callback(prio = 0, ref = nil, &block)
59
+ @query_cbs.add(prio, ref, block)
60
+ end
61
+
62
+ ##
63
+ # Add a callback for Jabber::Roster::Helper::RosterItem updates
64
+ #
65
+ # Note that this will be called much after initialization
66
+ # for the answer of the initial roster request
67
+ #
68
+ # The block receives two objects:
69
+ # * the old Jabber::Roster::Helper::RosterItem
70
+ # * the new Jabber::Roster::Helper::RosterItem
71
+ def add_update_callback(prio = 0, ref = nil, &block)
72
+ @update_cbs.add(prio, ref, block)
73
+ end
74
+
75
+ ##
76
+ # Add a callback for Jabber::Presence updates
77
+ #
78
+ # This will be called for <tt><presence/></tt> stanzas for known RosterItems.
79
+ # Unknown JIDs may still pass and can be caught via Jabber::Stream#add_presence_callback.
80
+ #
81
+ # The block receives three objects:
82
+ # * the Jabber::Roster::Helper::RosterItem
83
+ # * the old Jabber::Presence (or nil)
84
+ # * the new Jabber::Presence (or nil)
85
+ def add_presence_callback(prio = 0, ref = nil, &block)
86
+ @presence_cbs.add(prio, ref, block)
87
+ end
88
+
89
+ ##
90
+ # Add a callback for subscription updates,
91
+ # which will be called upon receiving a <tt><presence/></tt> stanza
92
+ # with type:
93
+ # * :subscribed
94
+ # * :unsubscribe
95
+ # * :unsubscribed
96
+ #
97
+ # The block receives two objects:
98
+ # * the Jabber::Roster::Helper::RosterItem (or nil)
99
+ # * the <tt><presence/></tt> stanza
100
+ def add_subscription_callback(prio = 0, ref = nil, &block)
101
+ @subscription_cbs.add(prio, ref, block)
102
+ end
103
+
104
+ ##
105
+ # Add a callback for subscription requests,
106
+ # which will be called upon receiving a <tt><presence type='subscribe'/></tt> stanza
107
+ #
108
+ # The block receives two objects:
109
+ # * the Jabber::Roster::Helper::RosterItem (or nil)
110
+ # * the <tt><presence/></tt> stanza
111
+ #
112
+ # Response to this event can be taken with accept_subscription
113
+ # and decline_subscription.
114
+ #
115
+ # Example usage:
116
+ # my_roster.add_subscription_request_callback do |item,presence|
117
+ # if accept_subscription_requests
118
+ # my_roster.accept_subscription(presence.from)
119
+ # else
120
+ # my_roster.decline_subscription(presence.from)
121
+ # end
122
+ # end
123
+ def add_subscription_request_callback(prio = 0, ref = nil, &block)
124
+ @subscription_request_cbs.add(prio, ref, block)
125
+ end
126
+
127
+ private
128
+
129
+ ##
130
+ # Handle received <tt><iq/></tt> stanzas,
131
+ # used internally
132
+ def handle_iq(iq)
133
+ if iq.query.kind_of?(IqQueryRoster)
134
+ # If the <iq/> contains <error/> we just ignore that
135
+ # and assume an empty roster
136
+ iq.query.each_element('item') do |item|
137
+ # Handle deletion of item
138
+ if item.subscription == :remove
139
+ @items_lock.synchronize {
140
+ @items.delete(item.jid)
141
+ }
142
+
143
+ else
144
+ olditem = nil
145
+ @items_lock.synchronize {
146
+ if @items.has_key?(item.jid)
147
+ olditem = RosterItem.new(@stream).import(@items[item.jid])
148
+
149
+ # Clear first, because import doesn't
150
+ @items[item.jid].iname = nil
151
+ @items[item.jid].subscription = nil
152
+ @items[item.jid].ask = nil
153
+
154
+ @items[item.jid].import(item)
155
+ else
156
+ @items[item.jid] = RosterItem.new(@stream).import(item)
157
+ end
158
+ }
159
+ @update_cbs.process(olditem, @items[item.jid])
160
+ end
161
+ end
162
+
163
+ @query_cbs.process(iq)
164
+ else
165
+ false
166
+ end
167
+ end
168
+
169
+ ##
170
+ # Handle received <tt><presence/></tt> stanzas,
171
+ # used internally
172
+ def handle_presence(pres)
173
+ item = self[pres.from]
174
+ if [:subscribed, :unsubscribe, :unsubscribed].include?(pres.type)
175
+ @subscription_cbs.process(item, pres)
176
+ true
177
+ elsif pres.type == :subscribe
178
+ @subscription_request_cbs.process(item, pres)
179
+ true
180
+ else
181
+ unless item.nil?
182
+ update_presence(item, pres)
183
+ true # Callback consumed stanza
184
+ else
185
+ false # Callback did not consume stanza
186
+ end
187
+ end
188
+ end
189
+
190
+ ##
191
+ # Update the presence of an item,
192
+ # used internally
193
+ #
194
+ # Callbacks are called here
195
+ def update_presence(item, pres)
196
+
197
+ # This requires special handling, to announce all resources offline
198
+ if pres.from.resource.nil? and pres.type == :error
199
+ oldpresences = []
200
+ item.each_presence do |oldpres|
201
+ oldpresences << oldpres
202
+ end
203
+
204
+ item.add_presence(pres)
205
+ oldpresences.each { |oldpres|
206
+ @presence_cbs.process(item, oldpres, pres)
207
+ }
208
+ else
209
+ oldpres = item.presence(pres.from).nil? ?
210
+ nil :
211
+ Presence.new.import(item.presence(pres.from))
212
+
213
+ item.add_presence(pres)
214
+ @presence_cbs.process(item, oldpres, pres)
215
+ end
216
+ end
217
+
218
+ public
219
+
220
+ ##
221
+ # Get an item by jid
222
+ #
223
+ # If not available tries to look for it with the resource stripped
224
+ def [](jid)
225
+ jid = JID.new(jid) unless jid.kind_of? JID
226
+
227
+ @items_lock.synchronize {
228
+ if @items.has_key?(jid)
229
+ @items[jid]
230
+ elsif @items.has_key?(jid.strip)
231
+ @items[jid.strip]
232
+ else
233
+ nil
234
+ end
235
+ }
236
+ end
237
+
238
+ ##
239
+ # Returns the list of RosterItems which, stripped, are equal to the
240
+ # one you are looking for.
241
+ def find(jid)
242
+ jid = JID.new(jid) unless jid.kind_of? JID
243
+
244
+ j = jid.strip
245
+ l = {}
246
+ @items_lock.synchronize {
247
+ @items.each_pair do |k, v|
248
+ l[k] = v if k.strip == j
249
+ end
250
+ }
251
+ l
252
+ end
253
+
254
+ ##
255
+ # Groups in this Roster,
256
+ # sorted by name
257
+ #
258
+ # Contains +nil+ if there are ungrouped items
259
+ # result:: [Array] containing group names (String)
260
+ def groups
261
+ res = []
262
+ @items_lock.synchronize {
263
+ @items.each_pair do |jid,item|
264
+ res += item.groups
265
+ res += [nil] if item.groups == []
266
+ end
267
+ }
268
+ res.uniq.sort { |a,b| a.to_s <=> b.to_s }
269
+ end
270
+
271
+ ##
272
+ # Get items in a group
273
+ #
274
+ # When group is nil, return ungrouped items
275
+ # group:: [String] Group name
276
+ # result:: Array of [RosterItem]
277
+ def find_by_group(group)
278
+ res = []
279
+ @items_lock.synchronize {
280
+ @items.each_pair do |jid,item|
281
+ res.push(item) if item.groups.include?(group)
282
+ res.push(item) if item.groups == [] and group.nil?
283
+ end
284
+ }
285
+ res
286
+ end
287
+
288
+ ##
289
+ # Add a user to your roster
290
+ #
291
+ # Threading is encouraged as the function waits for
292
+ # a result. ErrorException is thrown upon error.
293
+ #
294
+ # See Jabber::Roster::Helper::RosterItem#subscribe for details
295
+ # about subscribing. (This method isn't used here but the
296
+ # same functionality applies.)
297
+ #
298
+ # If the item is already in the local roster
299
+ # it will simply send itself
300
+ # jid:: [JID] to add
301
+ # iname:: [String] Optional item name
302
+ # subscribe:: [Boolean] Whether to subscribe to this jid
303
+ def add(jid, iname=nil, subscribe=false)
304
+ if self[jid]
305
+ self[jid].send
306
+ else
307
+ request = Iq.new_rosterset
308
+ request.query.add(Jabber::Roster::RosterItem.new(jid, iname))
309
+ @stream.send_with_id(request) { true }
310
+ # Adding to list is handled by handle_iq
311
+ end
312
+
313
+ if subscribe
314
+ # Actually the item *should* already be known now,
315
+ # but we do it manually to exclude conditions.
316
+ pres = Presence.new.set_type(:subscribe).set_to(jid.strip)
317
+ @stream.send(pres)
318
+ end
319
+ end
320
+
321
+ ##
322
+ # Accept a subscription request
323
+ # * Sends a <presence type='subscribed'/> stanza
324
+ # * Adds the contact to your roster
325
+ # jid:: [JID] of contact
326
+ # iname:: [String] Optional roster item name
327
+ def accept_subscription(jid, iname=nil)
328
+ pres = Presence.new.set_type(:subscribed).set_to(jid.strip)
329
+ @stream.send(pres)
330
+
331
+ unless self[jid.strip]
332
+ request = Iq.new_rosterset
333
+ request.query.add(Jabber::Roster::RosterItem.new(jid.strip, iname))
334
+ @stream.send_with_id(request) { true }
335
+ end
336
+ end
337
+
338
+ ##
339
+ # Decline a subscription request
340
+ # * Sends a <presence type='unsubscribed'/> stanza
341
+ def decline_subscription(jid)
342
+ pres = Presence.new.set_type(:unsubscribed).set_to(jid.strip)
343
+ @stream.send(pres)
344
+ end
345
+
346
+ ##
347
+ # These are extensions to RosterItem to carry presence information.
348
+ # This information is *not* stored in XML!
349
+ class RosterItem < Jabber::Roster::RosterItem
350
+ ##
351
+ # Tracked (online) presences of this RosterItem
352
+ attr_reader :presences
353
+
354
+ ##
355
+ # Initialize an empty RosterItem
356
+ def initialize(stream)
357
+ super()
358
+ @stream = stream
359
+ @presences = []
360
+ @presences_lock = Mutex.new
361
+ end
362
+
363
+ ##
364
+ # Import another element,
365
+ # also import presences if xe is a RosterItem
366
+ # return:: [RosterItem] self
367
+ def import(xe)
368
+ super
369
+ if xe.kind_of?(RosterItem)
370
+ xe.each_presence { |pres|
371
+ add_presence(Presence.new.import(pres))
372
+ }
373
+ end
374
+ self
375
+ end
376
+
377
+ ##
378
+ # Send the updated RosterItem to the server,
379
+ # i.e. if you modified iname, groups, ...
380
+ def send
381
+ request = Iq.new_rosterset
382
+ request.query.add(self)
383
+ @stream.send(request)
384
+ end
385
+
386
+ ##
387
+ # Remove item
388
+ #
389
+ # This cancels both subscription *from* the contact to you
390
+ # and from you *to* the contact.
391
+ #
392
+ # The methods waits for a roster push from the server (success)
393
+ # or throws ErrorException upon failure.
394
+ def remove
395
+ request = Iq.new_rosterset
396
+ request.query.add(Jabber::Roster::RosterItem.new(jid, nil, :remove))
397
+ @stream.send_with_id(request) { true }
398
+ # Removing from list is handled by Roster#handle_iq
399
+ end
400
+
401
+ ##
402
+ # Is any presence of this person on-line?
403
+ #
404
+ # (Or is there any presence? Unavailable presences are
405
+ # deleted.)
406
+ def online?
407
+ @presences_lock.synchronize {
408
+ @presences.select { |pres|
409
+ pres.type.nil?
410
+ }.size > 0
411
+ }
412
+ end
413
+
414
+ ##
415
+ # Iterate through all received <tt><presence/></tt> stanzas
416
+ def each_presence(&block)
417
+ # Don't lock here, we don't know what block does...
418
+ @presences.each { |pres|
419
+ yield(pres)
420
+ }
421
+ end
422
+
423
+ ##
424
+ # Get specific presence
425
+ # jid:: [JID] Full JID
426
+ def presence(jid)
427
+ @presences_lock.synchronize {
428
+ @presences.each { |pres|
429
+ return(pres) if pres.from == jid
430
+ }
431
+ }
432
+ nil
433
+ end
434
+
435
+ ##
436
+ # Add presence and sort presences
437
+ # (unless type is :unavailable or :error)
438
+ #
439
+ # This overwrites previous stanzas with the same destination
440
+ # JID to keep track of resources. Presence stanzas with
441
+ # <tt>type == :unavailable</tt> or <tt>type == :error</tt> will
442
+ # be deleted as this indicates that this resource has gone
443
+ # offline.
444
+ #
445
+ # If <tt>type == :error</tt> and the presence's origin has no
446
+ # specific resource the contact is treated completely offline.
447
+ def add_presence(newpres)
448
+ @presences_lock.synchronize {
449
+ # Delete old presences with the same JID
450
+ @presences.delete_if do |pres|
451
+ pres.from == newpres.from or pres.from.resource.nil?
452
+ end
453
+
454
+ if newpres.type == :error and newpres.from.resource.nil?
455
+ # Replace by single error presence
456
+ @presences = [newpres]
457
+ else
458
+ # Add new presence
459
+ @presences.push(newpres)
460
+ end
461
+
462
+ @presences.sort!
463
+ }
464
+ end
465
+
466
+ ##
467
+ # Send subscription request to the user
468
+ #
469
+ # The block given to Jabber::Roster::Roster#add_update_callback will
470
+ # be called, carrying the RosterItem with ask="subscribe"
471
+ #
472
+ # This function returns immediately after sending the subscription
473
+ # request and will not wait of approval or declination as it may
474
+ # take months for the contact to decide. ;-)
475
+ def subscribe
476
+ pres = Presence.new.set_type(:subscribe).set_to(jid.strip)
477
+ @stream.send(pres)
478
+ end
479
+
480
+ ##
481
+ # Unsubscribe from a contact's presence
482
+ #
483
+ # This method waits for a presence with type='unsubscribed'
484
+ # from the contact. It may throw ErrorException upon failure.
485
+ #
486
+ # subscription attribute of the item is *from* or *none*
487
+ # afterwards. As long as you don't remove that item and
488
+ # subscription='from' the contact is subscribed to your
489
+ # presence.
490
+ def unsubscribe
491
+ pres = Presence.new.set_type(:unsubscribe).set_to(jid.strip)
492
+ @stream.send(pres) { |answer|
493
+ answer.type == :unsubscribed and
494
+ answer.from.strip == pres.to
495
+ }
496
+ end
497
+
498
+ ##
499
+ # Deny the contact to see your presence.
500
+ #
501
+ # This method will not wait and returns immediately
502
+ # as you will need no confirmation for this action.
503
+ #
504
+ # Though, you will get a roster update for that item,
505
+ # carrying either subscription='to' or 'none'.
506
+ def cancel_subscription
507
+ pres = Presence.new.set_type(:unsubscribed).set_to(jid)
508
+ @stream.send(pres)
509
+ end
510
+ end
511
+ end #Class Roster
512
+ end #Module Roster
513
+ end #Module Jabber
514
+