Class Jabber::Roster::Helper
In: lib/xmpp4r/roster/helper/roster.rb
Parent: Object

The Roster helper intercepts <iq/> stanzas with Jabber::IqQueryRoster and <presence/> stanzas, but provides cbs which allow the programmer to keep track of updates.

A thread for any received stanza is spawned, so the user can invoke accept_subscription et al in the callback blocks, without stopping the current (= parser) thread when waiting for a reply.

Methods

Classes and Modules

Class Jabber::Roster::Helper::RosterItem

Attributes

items  [R]  All items in your roster
items:[Hash] ([JID] => [Roster::Helper::RosterItem])

Public Class methods

Initialize a new Roster helper

Registers its cbs (prio = 120, ref = self)

Request a roster (Remember to send initial presence afterwards!)

The initialization will not wait for the roster being received, use add_query_callback to get notifyed when Roster::Helper#items has been filled.

[Source]

    # File lib/xmpp4r/roster/helper/roster.rb, line 36
36:       def initialize(stream)
37:         @stream = stream
38:         @items = {}
39:         @items_lock = Mutex.new
40:         @query_cbs = CallbackList.new
41:         @update_cbs = CallbackList.new
42:         @presence_cbs = CallbackList.new
43:         @subscription_cbs = CallbackList.new
44:         @subscription_request_cbs = CallbackList.new
45: 
46:         # Register cbs
47:         stream.add_iq_callback(120, self) { |iq|
48:           if iq.query.kind_of?(IqQueryRoster)
49:             Thread.new do
50:               Thread.current.abort_on_exception = true
51:               handle_iq_query_roster(iq)
52:             end
53: 
54:             true
55:           else
56:             false
57:           end
58:         }
59:         stream.add_presence_callback(120, self) { |pres|
60:           Thread.new do
61:             Thread.current.abort_on_exception = true
62:             handle_presence(pres)
63:           end
64:         }
65: 
66:         # Request the roster
67:         rosterget = Iq.new_rosterget
68:         stream.send(rosterget)
69:       end

Public Instance methods

Get an item by jid

If not available tries to look for it with the resource stripped

[Source]

     # File lib/xmpp4r/roster/helper/roster.rb, line 232
232:       def [](jid)
233:         jid = JID.new(jid) unless jid.kind_of? JID
234: 
235:         @items_lock.synchronize {
236:           if @items.has_key?(jid)
237:             @items[jid]
238:           elsif @items.has_key?(jid.strip)
239:             @items[jid.strip]
240:           else
241:             nil
242:           end
243:         }
244:       end

Accept a subscription request

  • Sends a <presence type=‘subscribed’/> stanza
  • Adds the contact to your roster
jid:[JID] of contact
iname:[String] Optional roster item name

[Source]

     # File lib/xmpp4r/roster/helper/roster.rb, line 335
335:       def accept_subscription(jid, iname=nil)
336:         pres = Presence.new.set_type(:subscribed).set_to(jid.strip)
337:         @stream.send(pres)
338: 
339:         unless self[jid.strip]
340:           request = Iq.new_rosterset
341:           request.query.add(Jabber::Roster::RosterItem.new(jid.strip, iname))
342:           @stream.send_with_id(request) { true }
343:         end
344:       end

Add a user to your roster

Threading is encouraged as the function waits for a result. ErrorException is thrown upon error.

See Jabber::Roster::Helper::RosterItem#subscribe for details about subscribing. (This method isn‘t used here but the same functionality applies.)

If the item is already in the local roster it will simply send itself

jid:[JID] to add
iname:[String] Optional item name
subscribe:[Boolean] Whether to subscribe to this jid

[Source]

     # File lib/xmpp4r/roster/helper/roster.rb, line 311
311:       def add(jid, iname=nil, subscribe=false)
312:         if self[jid]
313:           self[jid].send
314:         else
315:           request = Iq.new_rosterset
316:           request.query.add(Jabber::Roster::RosterItem.new(jid, iname))
317:           @stream.send_with_id(request) { true }
318:           # Adding to list is handled by handle_iq_query_roster
319:         end
320: 
321:         if subscribe
322:           # Actually the item *should* already be known now,
323:           # but we do it manually to exclude conditions.
324:           pres = Presence.new.set_type(:subscribe).set_to(jid.strip)
325:           @stream.send(pres)
326:         end
327:       end

Add a callback for Jabber::Presence updates

This will be called for <presence/> stanzas for known RosterItems. Unknown JIDs may still pass and can be caught via Jabber::Stream#add_presence_callback.

The block receives three objects:

[Source]

     # File lib/xmpp4r/roster/helper/roster.rb, line 105
105:       def add_presence_callback(prio = 0, ref = nil, &block)
106:         @presence_cbs.add(prio, ref, block)
107:       end

Add a callback to be called when a query has been processed

Because update callbacks are called for each roster item, this may be appropriate to notify that anything has updated.

Arguments for callback block: The received <iq/> stanza

[Source]

    # File lib/xmpp4r/roster/helper/roster.rb, line 78
78:       def add_query_callback(prio = 0, ref = nil, &block)
79:         @query_cbs.add(prio, ref, block)
80:       end

Add a callback for subscription updates, which will be called upon receiving a <presence/> stanza with type:

  • :subscribed
  • :unsubscribe
  • :unsubscribed

The block receives two objects:

[Source]

     # File lib/xmpp4r/roster/helper/roster.rb, line 120
120:       def add_subscription_callback(prio = 0, ref = nil, &block)
121:         @subscription_cbs.add(prio, ref, block)
122:       end

Add a callback for subscription requests, which will be called upon receiving a <presence type=‘subscribe’/> stanza

The block receives two objects:

Response to this event can be taken with accept_subscription and decline_subscription.

Example usage:

 my_roster.add_subscription_request_callback do |item,presence|
   if accept_subscription_requests
     my_roster.accept_subscription(presence.from)
   else
     my_roster.decline_subscription(presence.from)
   end
 end

[Source]

     # File lib/xmpp4r/roster/helper/roster.rb, line 143
143:       def add_subscription_request_callback(prio = 0, ref = nil, &block)
144:         @subscription_request_cbs.add(prio, ref, block)
145:       end

Add a callback for Jabber::Roster::Helper::RosterItem updates

Note that this will be called much after initialization for the answer of the initial roster request

The block receives two objects:

[Source]

    # File lib/xmpp4r/roster/helper/roster.rb, line 91
91:       def add_update_callback(prio = 0, ref = nil, &block)
92:         @update_cbs.add(prio, ref, block)
93:       end

Decline a subscription request

  • Sends a <presence type=‘unsubscribed’/> stanza

[Source]

     # File lib/xmpp4r/roster/helper/roster.rb, line 349
349:       def decline_subscription(jid)
350:         pres = Presence.new.set_type(:unsubscribed).set_to(jid.strip)
351:         @stream.send(pres)
352:       end

Returns the list of RosterItems which, stripped, are equal to the one you are looking for.

[Source]

     # File lib/xmpp4r/roster/helper/roster.rb, line 249
249:       def find(jid)
250:         jid = JID.new(jid) unless jid.kind_of? JID
251: 
252:         j = jid.strip
253:         l = {}
254:         @items_lock.synchronize {
255:           @items.each_pair do |k, v|
256:             l[k] = v if k.strip == j
257:           end
258:         }
259:         l
260:       end

Get items in a group

When group is nil, return ungrouped items

group:[String] Group name
result:Array of [RosterItem]

[Source]

     # File lib/xmpp4r/roster/helper/roster.rb, line 285
285:       def find_by_group(group)
286:         res = []
287:         @items_lock.synchronize {
288:           @items.each_pair do |jid,item|
289:             res.push(item) if item.groups.include?(group)
290:             res.push(item) if item.groups == [] and group.nil?
291:           end
292:         }
293:         res
294:       end

Groups in this Roster, sorted by name

Contains nil if there are ungrouped items

result:[Array] containing group names (String)

[Source]

     # File lib/xmpp4r/roster/helper/roster.rb, line 268
268:       def groups
269:         res = []
270:         @items_lock.synchronize {
271:           @items.each_pair do |jid,item|
272:             res += item.groups
273:             res += [nil] if item.groups == []
274:           end
275:         }
276:         res.uniq.sort { |a,b| a.to_s <=> b.to_s }
277:       end

Private Instance methods

Handle received <iq/> stanzas, used internally

[Source]

     # File lib/xmpp4r/roster/helper/roster.rb, line 152
152:       def handle_iq_query_roster(iq)
153:         # If the <iq/> contains <error/> we just ignore that
154:         # and assume an empty roster
155:         iq.query.each_element('item') do |item|
156:           olditem, newitem = nil, nil
157: 
158:           @items_lock.synchronize {
159:             olditem = @items[item.jid]
160: 
161:             # Handle deletion of item
162:             if item.subscription == :remove
163:               @items.delete(item.jid)
164:             else
165:               newitem = @items[item.jid] = RosterItem.new(@stream).import(item)
166:             end
167:           }
168:           @update_cbs.process(olditem, newitem)
169:         end
170:           
171:         @query_cbs.process(iq)
172:       end

Handle received <presence/> stanzas, used internally

[Source]

     # File lib/xmpp4r/roster/helper/roster.rb, line 177
177:       def handle_presence(pres)
178:         item = self[pres.from]
179: 
180:         if [:subscribed, :unsubscribe, :unsubscribed].include?(pres.type)
181:           @subscription_cbs.process(item, pres)
182:           true
183: 
184:         elsif pres.type == :subscribe
185:           @subscription_request_cbs.process(item, pres)
186:           true
187: 
188:         else
189:           unless item.nil?
190:             update_presence(item, pres)
191:             true  # Callback consumed stanza
192:           else
193:             false # Callback did not consume stanza
194:           end
195:         end
196:       end

Update the presence of an item, used internally

Callbacks are called here

[Source]

     # File lib/xmpp4r/roster/helper/roster.rb, line 203
203:       def update_presence(item, pres)
204: 
205:         # This requires special handling, to announce all resources offline
206:         if pres.from.resource.nil? and pres.type == :error
207:           oldpresences = []
208:           item.each_presence do |oldpres|
209:             oldpresences << oldpres
210:           end
211: 
212:           item.add_presence(pres)
213:           oldpresences.each { |oldpres|
214:             @presence_cbs.process(item, oldpres, pres)
215:           }
216:         else
217:           oldpres = item.presence(pres.from).nil? ?
218:             nil :
219:             Presence.new.import(item.presence(pres.from))
220: 
221:           item.add_presence(pres)
222:           @presence_cbs.process(item, oldpres, pres)
223:         end
224:       end

[Validate]