Package pyxmpp :: Module roster
[hide private]

Source Code for Module pyxmpp.roster

  1  # 
  2  # (C) Copyright 2003-2006 Jacek Konieczny <jajcus@jajcus.net> 
  3  # 
  4  # This program is free software; you can redistribute it and/or modify 
  5  # it under the terms of the GNU Lesser General Public License Version 
  6  # 2.1 as published by the Free Software Foundation. 
  7  # 
  8  # This program is distributed in the hope that it will be useful, 
  9  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 10  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 11  # GNU Lesser General Public License for more details. 
 12  # 
 13  # You should have received a copy of the GNU Lesser General Public 
 14  # License along with this program; if not, write to the Free Software 
 15  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 16  # 
 17   
 18  """XMPP-IM roster handling. 
 19   
 20  Normative reference: 
 21    - `RFC 3921 <http://www.ietf.org/rfc/rfc3921.txt>`__ 
 22  """ 
 23   
 24  __revision__="$Id: roster.py 647 2006-08-26 18:27:39Z jajcus $" 
 25  __docformat__="restructuredtext en" 
 26   
 27  import libxml2 
 28   
 29  from pyxmpp.xmlextra import common_doc, get_node_ns_uri 
 30  from pyxmpp.iq import Iq 
 31  from pyxmpp.jid import JID 
 32   
 33  from pyxmpp.utils import to_utf8,from_utf8 
 34  from pyxmpp.objects import StanzaPayloadObject 
 35   
 36  ROSTER_NS="jabber:iq:roster" 
 37   
38 -class RosterItem(StanzaPayloadObject):
39 """ 40 Roster item. 41 42 Represents part of a roster, or roster update request. 43 """ 44 45 xml_element_name = "item" 46 xml_element_namespace = ROSTER_NS 47
48 - def __init__(self,node_or_jid,subscription="none",name=None,groups=(),ask=None):
49 """ 50 Initialize a roster item from XML node or jid and optional attributes. 51 52 :Parameters: 53 - `node_or_jid`: XML node or JID 54 - `subscription`: subscription type ("none", "to", "from" or "both" 55 - `name`: item visible name 56 - `groups`: sequence of groups the item is member of 57 - `ask`: True if there was unreplied subsription or unsubscription 58 request sent.""" 59 if isinstance(node_or_jid,libxml2.xmlNode): 60 self.from_xml(node_or_jid) 61 else: 62 node_or_jid=JID(node_or_jid) 63 if subscription not in ("none","from","to","both","remove"): 64 raise ValueError,"Bad subscription type: %r" % (subscription,) 65 if ask not in ("subscribe",None): 66 raise ValueError,"Bad ask type: %r" % (ask,) 67 self.jid=node_or_jid 68 self.ask=ask 69 self.subscription=subscription 70 self.name=name 71 self.groups=list(groups)
72
73 - def from_xml(self,node):
74 """Initialize RosterItem from XML node.""" 75 if node.type!="element": 76 raise ValueError,"XML node is not a roster item (not en element)" 77 ns=get_node_ns_uri(node) 78 if ns and ns!=ROSTER_NS or node.name!="item": 79 raise ValueError,"XML node is not a roster item" 80 jid=JID(node.prop("jid").decode("utf-8")) 81 subscription=node.prop("subscription") 82 if subscription not in ("none","from","to","both","remove"): 83 subscription="none" 84 ask=node.prop("ask") 85 if ask not in ("subscribe",None): 86 ask=None 87 name=from_utf8(node.prop("name")) 88 groups=[] 89 n=node.children 90 while n: 91 if n.type!="element": 92 n=n.next 93 continue 94 ns=get_node_ns_uri(n) 95 if ns and ns!=ROSTER_NS or n.name!="group": 96 n=n.next 97 continue 98 group=n.getContent() 99 if group: 100 groups.append(from_utf8(group)) 101 n=n.next 102 self.jid=jid 103 self.name=name 104 self.groups=groups 105 self.subscription=subscription 106 self.ask=ask
107
108 - def complete_xml_element(self, xmlnode, _unused):
109 """Complete the XML node with `self` content. 110 111 Should be overriden in classes derived from `StanzaPayloadObject`. 112 113 :Parameters: 114 - `xmlnode`: XML node with the element being built. It has already 115 right name and namespace, but no attributes or content. 116 - `_unused`: document to which the element belongs. 117 :Types: 118 - `xmlnode`: `libxml2.xmlNode` 119 - `_unused`: `libxml2.xmlDoc`""" 120 xmlnode.setProp("jid",self.jid.as_utf8()) 121 if self.name: 122 xmlnode.setProp("name",to_utf8(self.name)) 123 xmlnode.setProp("subscription",self.subscription) 124 if self.ask: 125 xmlnode.setProp("ask",to_utf8(self.ask)) 126 for g in self.groups: 127 xmlnode.newTextChild(None, "group", to_utf8(g))
128
129 - def __str__(self):
130 n=self.as_xml(doc=common_doc) 131 r=n.serialize() 132 n.unlinkNode() 133 n.freeNode() 134 return r
135
136 - def make_roster_push(self):
137 """ 138 Make "roster push" IQ stanza from the item representing roster update 139 request. 140 """ 141 iq=Iq(stanza_type="set") 142 q=iq.new_query(ROSTER_NS) 143 self.as_xml(parent=q, doc=common_doc) 144 return iq
145
146 -class Roster(StanzaPayloadObject):
147 """Class representing XMPP-IM roster. 148 149 Iteration over `Roster` object iterates over roster items. 150 151 ``for item in roster: ...`` may be used to iterate over roster items, 152 ``roster[jid]`` to get roster item by jid, ``jid in roster`` to test roster 153 for jid presence. 154 155 :Ivariables: 156 - `items_dict`: items indexed by JID. 157 :Properties: 158 - `items`: roster items. 159 :Types: 160 - `items_dict`: `dict` of `JID` -> `RosterItem` 161 - `items`: `list` of `RosterItem`""" 162 163 xml_element_name = "query" 164 xml_element_namespace = ROSTER_NS 165
166 - def __init__(self,node=None,server=False,strict=True):
167 """ 168 Initialize Roster object. 169 170 `node` should be an XML representation of the roster (e.g. as sent 171 from server in response to roster request). When `node` is None empty 172 roster will be created. 173 174 If `server` is true the object is considered server-side roster. 175 176 If `strict` is False, than invalid items in the XML will be ignored. 177 """ 178 self.items_dict={} 179 self.server=server 180 self.node=None 181 if node: 182 self.from_xml(node,strict)
183
184 - def from_xml(self,node,strict=True):
185 """ 186 Initialize Roster object from XML node. 187 188 If `strict` is False, than invalid items in the XML will be ignored. 189 """ 190 self.items_dict={} 191 if node.type!="element": 192 raise ValueError,"XML node is not a roster (not en element)" 193 ns=get_node_ns_uri(node) 194 if ns and ns!=ROSTER_NS or node.name!="query": 195 raise ValueError,"XML node is not a roster" 196 n=node.children 197 while n: 198 if n.type!="element": 199 n=n.next 200 continue 201 ns=get_node_ns_uri(n) 202 if ns and ns!=ROSTER_NS or n.name!="item": 203 n=n.next 204 continue 205 try: 206 item=RosterItem(n) 207 self.items_dict[item.jid]=item 208 except ValueError: 209 if strict: 210 raise 211 n=n.next
212
213 - def complete_xml_element(self, xmlnode, doc):
214 """Complete the XML node with `self` content. 215 216 Should be overriden in classes derived from `StanzaPayloadObject`. 217 218 :Parameters: 219 - `xmlnode`: XML node with the element being built. It has already 220 right name and namespace, but no attributes or content. 221 - `doc`: document to which the element belongs. 222 :Types: 223 - `xmlnode`: `libxml2.xmlNode` 224 - `doc`: `libxml2.xmlDoc`""" 225 for it in self.items_dict.values(): 226 it.as_xml(parent=xmlnode, doc=doc)
227
228 - def __str__(self):
229 n=self.as_xml(doc=common_doc) 230 r=n.serialize() 231 n.unlinkNode() 232 n.freeNode() 233 return r
234
235 - def __iter__(self):
236 return self.items_dict.itervalues()
237
238 - def __contains__(self, jid):
239 return jid in self.items_dict
240
241 - def __getitem__(self, jid):
242 return self.items_dict[jid]
243
244 - def get_items(self):
245 """Return a list of items in the roster.""" 246 return self.items_dict.values()
247 248 items = property(get_items) 249
250 - def get_groups(self):
251 """Return a list of groups in the roster.""" 252 r={} 253 for it in self.items_dict.values(): 254 it.groups=[g for g in it.groups if g] 255 if it.groups: 256 for g in it.groups: 257 r[g]=True 258 else: 259 r[None]=True 260 return r.keys()
261
262 - def get_items_by_name(self, name, case_sensitive = True):
263 """ 264 Return a list of items with given `name`. 265 266 If `case_sensitive` is False the matching will be case insensitive. 267 """ 268 if not case_sensitive and name: 269 name = name.lower() 270 r = [] 271 for it in self.items_dict.values(): 272 if it.name == name: 273 r.append(it) 274 elif it.name is None: 275 continue 276 elif not case_sensitive and it.name.lower() == name: 277 r.append(it) 278 return r
279
280 - def get_items_by_group(self,group,case_sensitive=True):
281 """ 282 Return a list of groups with given name. 283 284 If `case_sensitive` is False the matching will be case insensitive. 285 """ 286 r=[] 287 if not group: 288 for it in self.items_dict.values(): 289 it.groups=[g for g in it.groups if g] 290 if not it.groups: 291 r.append(it) 292 return r 293 if not case_sensitive: 294 group=group.lower() 295 for it in self.items_dict.values(): 296 if group in it.groups: 297 r.append(it) 298 elif not case_sensitive and group in [g.lower() for g in it.groups]: 299 r.append(it) 300 return r
301
302 - def get_item_by_jid(self,jid):
303 """ 304 Return roster item with given `jid`. 305 306 :raise KeyError: if the item is not found. 307 """ 308 if not jid: 309 raise ValueError,"jid is None" 310 return self.items_dict[jid]
311
312 - def add_item(self,item_or_jid,subscription="none",name=None,groups=(),ask=None):
313 """ 314 Add an item to the roster. 315 316 The `item_or_jid` argument may be a `RosterItem` object or a `JID`. If 317 it is a JID then `subscription`, `name`, `groups` and `ask` may also be 318 specified. 319 """ 320 if isinstance(item_or_jid,RosterItem): 321 item=item_or_jid 322 if self.items_dict.has_key(item.jid): 323 raise ValueError,"Item already exists" 324 else: 325 if self.items_dict.has_key(item_or_jid): 326 raise ValueError,"Item already exists" 327 if not self.server or subscription not in ("none","from","to","both"): 328 subscription="none" 329 if not self.server: 330 ask=None 331 item=RosterItem(item_or_jid,subscription,name,groups,ask) 332 self.items_dict[item.jid]=item 333 return item
334
335 - def remove_item(self,jid):
336 """Remove item from the roster.""" 337 del self.items_dict[jid] 338 return RosterItem(jid,"remove")
339
340 - def update(self,query):
341 """ 342 Apply an update request to the roster. 343 344 `query` should be a query included in a "roster push" IQ received. 345 """ 346 ctxt=common_doc.xpathNewContext() 347 ctxt.setContextNode(query) 348 ctxt.xpathRegisterNs("r",ROSTER_NS) 349 item=ctxt.xpathEval("r:item") 350 ctxt.xpathFreeContext() 351 if not item: 352 raise ValueError,"No item to update" 353 item=item[0] 354 item=RosterItem(item) 355 jid=item.jid 356 subscription=item.subscription 357 try: 358 local_item=self.get_item_by_jid(jid) 359 local_item.subscription=subscription 360 except KeyError: 361 if subscription=="remove": 362 return RosterItem(jid,"remove") 363 if self.server or subscription not in ("none","from","to","both"): 364 subscription="none" 365 local_item=RosterItem(jid,subscription) 366 if subscription=="remove": 367 del self.items_dict[local_item.jid] 368 return RosterItem(jid,"remove") 369 local_item.name=item.name 370 local_item.groups=list(item.groups) 371 if not self.server: 372 local_item.ask=item.ask 373 self.items_dict[local_item.jid]=local_item 374 return local_item
375 376 # vi: sts=4 et sw=4 377