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.
All items in your roster
([JID] => [Roster::Helper::RosterItem])
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 wait_for_roster.
Attention: If you send presence and receive presences before the roster has arrived, the Roster helper will let them pass through and does not keep them!
# File lib/xmpp4r/roster/helper/roster.rb, line 39 def initialize(stream, startnow = true) @stream = stream @items = {} @items_lock = Mutex.new @roster_wait = Semaphore.new @query_cbs = CallbackList.new @update_cbs = CallbackList.new @presence_cbs = CallbackList.new @subscription_cbs = CallbackList.new @subscription_request_cbs = CallbackList.new # Register cbs stream.add_iq_callback(120, self) { |iq| if iq.query.kind_of?(IqQueryRoster) Thread.new do Thread.current.abort_on_exception = true handle_iq_query_roster(iq) end true else false end } stream.add_presence_callback(120, self) { |pres| Thread.new do Thread.current.abort_on_exception = true handle_presence(pres) end } get_roster if startnow end
Get an item by jid
If not available tries to look for it with the resource stripped
# File lib/xmpp4r/roster/helper/roster.rb, line 247 def [](jid) jid = JID.new(jid) unless jid.kind_of? JID @items_lock.synchronize { if @items.has_key?(jid) @items[jid] elsif @items.has_key?(jid.strip) @items[jid.strip] else nil end } end
Accept a subscription request
Sends a <presence type='subscribed'/> stanza
Adds the contact to your roster
of contact
Optional roster item name
# File lib/xmpp4r/roster/helper/roster.rb, line 350 def accept_subscription(jid, iname=nil) pres = Presence.new.set_type(:subscribed).set_to(jid.strip) @stream.send(pres) unless self[jid.strip] request = Iq.new_rosterset request.query.add(Jabber::Roster::RosterItem.new(jid.strip, iname)) @stream.send_with_id(request) end end
Add a user to your roster
Threading is encouraged as the function waits for a result. ServerError 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
to add
Optional item name
Whether to subscribe to this jid
# File lib/xmpp4r/roster/helper/roster.rb, line 326 def add(jid, iname=nil, subscribe=false) if self[jid] self[jid].send else request = Iq.new_rosterset request.query.add(Jabber::Roster::RosterItem.new(jid, iname)) @stream.send_with_id(request) # Adding to list is handled by handle_iq_query_roster end if subscribe # Actually the item *should* already be known now, # but we do it manually to exclude conditions. pres = Presence.new.set_type(:subscribe).set_to(jid.strip) @stream.send(pres) end 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:
the old Jabber::Presence (or nil)
the new Jabber::Presence (or nil)
# File lib/xmpp4r/roster/helper/roster.rb, line 119 def add_presence_callback(prio = 0, ref = nil, &block) @presence_cbs.add(prio, ref, block) 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
# File lib/xmpp4r/roster/helper/roster.rb, line 92 def add_query_callback(prio = 0, ref = nil, &block) @query_cbs.add(prio, ref, block) 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:
the Jabber::Roster::Helper::RosterItem (or nil)
the <presence/>
stanza
# File lib/xmpp4r/roster/helper/roster.rb, line 134 def add_subscription_callback(prio = 0, ref = nil, &block) @subscription_cbs.add(prio, ref, block) end
Add a callback for subscription requests, which will be called upon
receiving a <presence type='subscribe'/>
stanza
The block receives two objects:
the Jabber::Roster::Helper::RosterItem (or nil)
the <presence/>
stanza
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
# File lib/xmpp4r/roster/helper/roster.rb, line 157 def add_subscription_request_callback(prio = 0, ref = nil, &block) @subscription_request_cbs.add(prio, ref, block) 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:
# File lib/xmpp4r/roster/helper/roster.rb, line 105 def add_update_callback(prio = 0, ref = nil, &block) @update_cbs.add(prio, ref, block) end
Decline a subscription request
Sends a <presence type='unsubscribed'/> stanza
# File lib/xmpp4r/roster/helper/roster.rb, line 364 def decline_subscription(jid) pres = Presence.new.set_type(:unsubscribed).set_to(jid.strip) @stream.send(pres) end
Returns the list of RosterItems which, stripped, are equal to the one you are looking for.
# File lib/xmpp4r/roster/helper/roster.rb, line 264 def find(jid) jid = JID.new(jid) unless jid.kind_of? JID j = jid.strip l = {} @items_lock.synchronize { @items.each_pair do |k, v| l[k] = v if k.strip == j end } l end
Get items in a group
When group is nil, return ungrouped items
Group name
Array of [RosterItem]
# File lib/xmpp4r/roster/helper/roster.rb, line 300 def find_by_group(group) res = [] @items_lock.synchronize { @items.each_pair do |jid,item| res.push(item) if item.groups.include?(group) res.push(item) if item.groups == [] and group.nil? end } res end
# File lib/xmpp4r/roster/helper/roster.rb, line 72 def get_roster # Request the roster rosterget = Iq.new_rosterget @stream.send(rosterget) end
Groups in this Roster, sorted by name
Contains nil
if there are ungrouped items
containing group names (String)
# File lib/xmpp4r/roster/helper/roster.rb, line 283 def groups res = [] @items_lock.synchronize { @items.each_pair do |jid,item| res += item.groups res += [nil] if item.groups == [] end } res.uniq.sort { |a,b| a.to_s <=> b.to_s } end
Wait for first roster query result to arrive
# File lib/xmpp4r/roster/helper/roster.rb, line 80 def wait_for_roster @roster_wait.wait @roster_wait.run end
Handle received <iq/>
stanzas, used internally
# File lib/xmpp4r/roster/helper/roster.rb, line 166 def handle_iq_query_roster(iq) # If the <iq/> contains <error/> we just ignore that # and assume an empty roster iq.query.each_element('item') do |item| olditem, newitem = nil, nil @items_lock.synchronize { olditem = @items[item.jid] # Handle deletion of item if item.subscription == :remove @items.delete(item.jid) else newitem = @items[item.jid] = RosterItem.new(@stream).import(item) end } @update_cbs.process(olditem, newitem) end @roster_wait.run @query_cbs.process(iq) end
Handle received <presence/>
stanzas, used internally
# File lib/xmpp4r/roster/helper/roster.rb, line 192 def handle_presence(pres) item = self[pres.from] if [:subscribed, :unsubscribe, :unsubscribed].include?(pres.type) @subscription_cbs.process(item, pres) true elsif pres.type == :subscribe @subscription_request_cbs.process(item, pres) true else unless item.nil? update_presence(item, pres) true # Callback consumed stanza else false # Callback did not consume stanza end end end
Update the presence of an item, used internally
Callbacks are called here
# File lib/xmpp4r/roster/helper/roster.rb, line 218 def update_presence(item, pres) # This requires special handling, to announce all resources offline if pres.from.resource.nil? and pres.type == :error oldpresences = [] item.each_presence do |oldpres| oldpresences << oldpres end item.add_presence(pres) oldpresences.each { |oldpres| @presence_cbs.process(item, oldpres, pres) } else oldpres = item.presence(pres.from).nil? ? nil : Presence.new.import(item.presence(pres.from)) item.add_presence(pres) @presence_cbs.process(item, oldpres, pres) end end