Package pyxmpp :: Package jabber :: Module disco |
[hide private]

# Source Code for Module pyxmpp.jabber.disco

      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  # pylint: disable-msg=W0201
     18  """Jabber Service Discovery support.
     19
     20  Normative reference:
     21    - `JEP 30 <http://www.jabber.org/jeps/jep-0030.html>`__
     22  """
     23
     24  __revision__="$Id: disco.py 666 2006-12-03 15:40:54Z jajcus $"
     25  __docformat__="restructuredtext en"
     26
     27  import warnings
     28
     29  import libxml2
     30
     31  from pyxmpp.xmlextra import common_doc,common_root
     32  from pyxmpp.jid import JID
     33  from pyxmpp import cache
     34
     35  from pyxmpp.utils import to_utf8
     36  from pyxmpp.objects import StanzaPayloadWrapperObject
     37  from pyxmpp.exceptions import ProtocolError
     38
     39  DISCO_NS="http://jabber.org/protocol/disco"
     40  DISCO_ITEMS_NS=DISCO_NS+"#items"
     41  DISCO_INFO_NS=DISCO_NS+"#info"
     42
     43 -class DiscoItem(StanzaPayloadWrapperObject):
     44      """An item of disco#items reply.
     45
     46      :Ivariables:
     47          - `jid`: the JID of the item.
     48          - `node`: node name of the item.
     49          - `name`: name of the item.
     50          - `action`: action of the item.
     51          - `disco`: the disco reply this is the part of.
     52          - `xmlnode`: XML element describing the item.
     53      :Types:
     54          - `jid`: `JID`
     55          - `node`: `unicode`
     56          - `name`: `unicode`
     57          - `action`: `unicode`
     58          - `disco`: `DiscoItems`
     59          - `xmlnode`: `libxml2.xmlNode`
     60      """
     61 -    def __init__(self,disco,xmlnode_or_jid,node=None,name=None,action=None):
     62          """Initialize an `DiscoItem` object.
     63
     64          :Parameters:
     65              - `disco`: the disco#items reply `self` is a part of.
     66              - `xmlnode_or_jid`: XML element describing the item or the JID of
     67                the item.
     68              - `node`: disco node of the item.
     69              - `name`: name of the item.
     70              - `action`: 'action' attribute of the item.
     71          :Types:
     72              - `disco`: `DiscoItems`
     73              - `xmlnode_or_jid`: `libxml2.xmlNode` or `JID`
     74              - `node`: `unicode`
     75              - `name`: `unicode`
     76              - `action`: `unicode`
     77          """
     78          self.disco=disco
     79          if isinstance(xmlnode_or_jid,JID):
     80              if disco:
     81                  self.xmlnode=disco.xmlnode.newChild(None,"item",None)
     82              else:
     83                  self.xmlnode=common_root.newChild(None,"item",None)
     84                  ns=self.xmlnode.newNs(DISCO_ITEMS_NS,None)
     85                  self.xmlnode.setNs(ns)
     86              self.set_jid(xmlnode_or_jid)
     87              self.set_name(name)
     88              self.set_node(node)
     89              self.set_action(action)
     90          else:
     91              if disco is None:
     92                  self.xmlnode=xmlnode_or_jid.copyNode(1)
     93              else:
     94                  self.xmlnode=xmlnode_or_jid
     95              if name:
     96                  self.set_name(name)
     97              if node:
     98                  self.set_node(node)
     99              if action:
    100                  self.set_action(action)
    101          self.xpath_ctxt=common_doc.xpathNewContext()
    102          self.xpath_ctxt.setContextNode(self.xmlnode)
    103          self.xpath_ctxt.xpathRegisterNs("d",DISCO_ITEMS_NS)
    104
    105 -    def __del__(self):
    106          if self.disco is None:
    107              if self.xmlnode:
    108                  self.xmlnode.unlinkNode()
    109                  self.xmlnode.freeNode()
    110                  self.xmlnode=None
    111          if self.xpath_ctxt:
    112              self.xpath_ctxt.xpathFreeContext()
    113
    114 -    def __str__(self):
    115          return self.xmlnode.serialize()
    116
    117 -    def remove(self):
    118          """Remove `self` from the containing `DiscoItems` object."""
    119          if self.disco is None:
    120              return
    121          self.xmlnode.unlinkNode()
    122          oldns=self.xmlnode.ns()
    123          ns=self.xmlnode.newNs(oldns.getContent(),None)
    124          self.xmlnode.replaceNs(oldns,ns)
    125          common_root.addChild(self.xmlnode())
    126          self.disco=None
    127
    128 -    def get_name(self):
    129          """Get the name of the item.
    130
    131          :return: the name of the item or `None`.
    132          :returntype: `unicode`"""
    133          name = self.xmlnode.prop("name")
    134          if name is None:
    135              return None
    136          return name.decode("utf-8")
    137
    138 -    def set_name(self, name):
    139          """Set the name of the item.
    140
    141          :Parameters:
    142              - `name`: the new name or `None`.
    143          :Types:
    144              - `name`: `unicode` """
    145          if name is None:
    146              if self.xmlnode.hasProp("name"):
    147                  self.xmlnode.unsetProp("name")
    148              return
    149          name = unicode(name)
    150          self.xmlnode.setProp("name", name.encode("utf-8"))
    151
    152      name = property(get_name, set_name)
    153
    154 -    def get_node(self):
    155          """Get the node of the item.
    156
    157          :return: the node of the item or `None`.
    158          :returntype: `unicode`"""
    159          node = self.xmlnode.prop("node")
    160          if node is None:
    161              return None
    162          return node.decode("utf-8")
    163
    164 -    def set_node(self,node):
    165          """Set the node of the item.
    166
    167          :Parameters:
    168              - `node`: the new node or `None`.
    169          :Types:
    170              - `node`: `unicode`
    171          """
    172          if node is None:
    173              if self.xmlnode.hasProp("node"):
    174                  self.xmlnode.unsetProp("node")
    175              return
    176          node = unicode(node)
    177          self.xmlnode.setProp("node", node.encode("utf-8"))
    178
    179      node = property(get_node, set_node)
    180
    181 -    def get_action(self):
    182          """Get the action attribute of the item.
    183
    184          :return: the action of the item or `None`.
    185          :returntype: `unicode`"""
    186          action=self.xmlnode.prop("action")
    187          if action is None:
    188              return None
    189          return action.decode("utf-8")
    190
    191 -    def set_action(self,action):
    192          """Set the action of the item.
    193
    194          :Parameters:
    195              - `action`: the new action or `None`.
    196          :Types:
    197              - `action`: `unicode`
    198          """
    199          if action is None:
    200              if self.xmlnode.hasProp("action"):
    201                  self.xmlnode.unsetProp("action")
    202              return
    203          if action not in ("remove","update"):
    204              raise ValueError, "Action must be 'update' or 'remove'"
    205          action = unicode(action)
    206          self.xmlnode.setProp("action", action.encode("utf-8"))
    207
    208      action = property(get_action, set_action)
    209
    210 -    def get_jid(self):
    211          """Get the JID of the item.
    212
    213          :return: the JID of the item.
    214          :returntype: `JID`"""
    215          jid = self.xmlnode.prop("jid")
    216          return JID( jid.decode("utf-8") )
    217
    218 -    def set_jid(self,jid):
    219          """Set the JID of the item.
    220
    221          :Parameters:
    222              - `jid`: the new jid.
    223          :Types:
    224              - `jid`: `JID`
    225          """
    226          self.xmlnode.setProp("jid", jid.as_unicode().encode("utf-8"))
    227
    228      jid = property(get_jid, set_jid)
    229
    230 -class DiscoIdentity(StanzaPayloadWrapperObject):
    231      """An <identity/> element of disco#info reply.
    232
    233      Identifies an item by its name, category and type.
    234
    235      :Ivariables:
    236          - `disco`: the disco reply this is the part of.
    237          - `xmlnode`: XML element describing the identity.
    238      :Types:
    239          - `disco`: `DiscoInfo`
    240          - `xmlnode`: `libxml2.xmlNode`
    241      """
    242 -    def __init__(self, disco, xmlnode_or_name, item_category=None, item_type=None, replace=False):
    243          """Initialize an `DiscoIdentity` object.
    244
    245          :Parameters:
    246              - `disco`: the disco#info reply `self` is a part of.
    247              - `xmlnode_or_name`: XML element describing the identity or the
    248                name of the item described.
    249              - `item_category`: category of the item described.
    250              - `item_type`: type of the item described.
    251              - `replace`: if `True` than all other <identity/> elements in
    252                `disco` will be removed.
    253          :Types:
    254              - `disco`: `DiscoItems`
    255              - `xmlnode_or_name`: `libxml2.xmlNode` or `unicode`
    256              - `item_category`: `unicode`
    257              - `item_type`: `unicode`
    258              - `replace`: `bool`
    259          """
    260          self.disco=disco
    261          if disco and replace:
    262              old=disco.xpath_ctxt.xpathEval("d:identity")
    263              if old:
    264                  for n in old:
    265                      n.unlinkNode()
    266                      n.freeNode()
    267          if isinstance(xmlnode_or_name,libxml2.xmlNode):
    268              if disco is None:
    269                  self.xmlnode=xmlnode_or_name.copyNode(1)
    270              else:
    271                  self.xmlnode=xmlnode_or_name
    272          elif not item_category:
    273              raise ValueError,"DiscoInfo requires category"
    274          elif not item_type:
    275              raise ValueError,"DiscoInfo requires type"
    276          else:
    277              if disco:
    278                  self.xmlnode=disco.xmlnode.newChild(None,"identity",None)
    279              else:
    280                  self.xmlnode=common_root.newChild(None,"identity",None)
    281                  ns=self.xmlnode.newNs(DISCO_INFO_NS,None)
    282                  self.xmlnode.setNs(ns)
    283              self.set_name(xmlnode_or_name)
    284              self.set_category(item_category)
    285              self.set_type(item_type)
    286          self.xpath_ctxt=common_doc.xpathNewContext()
    287          self.xpath_ctxt.setContextNode(self.xmlnode)
    288          self.xpath_ctxt.xpathRegisterNs("d",DISCO_INFO_NS)
    289
    290 -    def __del__(self):
    291          if self.disco is None:
    292              if self.xmlnode:
    293                  self.xmlnode.unlinkNode()
    294                  self.xmlnode.freeNode()
    295                  self.xmlnode=None
    296          if self.xpath_ctxt:
    297              self.xpath_ctxt.xpathFreeContext()
    298
    299 -    def __str__(self):
    300          return self.xmlnode.serialize()
    301
    302 -    def remove(self):
    303          """Remove `self` from the containing `DiscoInfo` object."""
    304          if self.disco is None:
    305              return
    306          self.xmlnode.unlinkNode()
    307          oldns=self.xmlnode.ns()
    308          ns=self.xmlnode.newNs(oldns.getContent(),None)
    309          self.xmlnode.replaceNs(oldns,ns)
    310          common_root.addChild(self.xmlnode())
    311          self.disco=None
    312
    313 -    def get_name(self):
    314          """Get the name of the item.
    315
    316          :return: the name of the item or `None`.
    317          :returntype: `unicode`"""
    318          var = self.xmlnode.prop("name")
    319          if not var:
    320              var = ""
    321          return var.decode("utf-8")
    322
    323 -    def set_name(self,name):
    324          """Set the name of the item.
    325
    326          :Parameters:
    327              - `name`: the new name or `None`.
    328          :Types:
    329              - `name`: `unicode` """
    330          if not name:
    331              raise ValueError, "name is required in DiscoIdentity"
    332          name = unicode(name)
    333          self.xmlnode.setProp("name", name.encode("utf-8"))
    334
    335      name = property(get_name, set_name)
    336
    337 -    def get_category(self):
    338          """Get the category of the item.
    339
    340          :return: the category of the item.
    341          :returntype: `unicode`"""
    342          var = self.xmlnode.prop("category")
    343          if not var:
    344              var = "?"
    345          return var.decode("utf-8")
    346
    347 -    def set_category(self, category):
    348          """Set the category of the item.
    349
    350          :Parameters:
    351              - `category`: the new category.
    352          :Types:
    353              - `category`: `unicode` """
    354          if not category:
    355              raise ValueError, "Category is required in DiscoIdentity"
    356          category = unicode(category)
    357          self.xmlnode.setProp("category", category.encode("utf-8"))
    358
    359      category = property(get_category, set_category)
    360
    361 -    def get_type(self):
    362          """Get the type of the item.
    363
    364          :return: the type of the item.
    365          :returntype: `unicode`"""
    366          item_type = self.xmlnode.prop("type")
    367          if not item_type:
    368              item_type = "?"
    369          return item_type.decode("utf-8")
    370
    371 -    def set_type(self, item_type):
    372          """Set the type of the item.
    373
    374          :Parameters:
    375              - `item_type`: the new type.
    376          :Types:
    377              - `item_type`: `unicode` """
    378          if not item_type:
    379              raise ValueError,"Type is required in DiscoIdentity"
    380          item_type = unicode(item_type)
    381          self.xmlnode.setProp("type", item_type.encode("utf-8"))
    382
    383      type = property(get_type, set_type)
    384
    385 -class DiscoItems(StanzaPayloadWrapperObject):
    386      """A disco#items response or publish-request object.
    387
    388      :Ivariables:
    389          - `node`: node name of the disco#items element.
    390          - `items`: items in the disco#items element.
    391          - `xmlnode`: XML element listing the items.
    392      :Types:
    393          - `node`: `unicode`
    394          - `items`: `tuple` of `DiscoItem`
    395          - `xmlnode`: `libxml2.xmlNode`
    396      """
    397 -    def __init__(self,xmlnode_or_node=None):
    398          """Initialize an `DiscoItems` object.
    399
    400          Wrap an existing disco#items XML element or create a new one.
    401
    402          :Parameters:
    403              - `xmlnode_or_node`: XML node to be wrapped into `self` or an item
    404                node name.
    405          :Types:
    406              - `xmlnode_or_node`: `libxml2.xmlNode` or `unicode`"""
    407          self.xmlnode=None
    408          self.xpath_ctxt=None
    409          if isinstance(xmlnode_or_node,libxml2.xmlNode):
    410              ns=xmlnode_or_node.ns()
    411              if ns.getContent() != DISCO_ITEMS_NS:
    412                  raise ValueError, "Bad disco-items namespace"
    413              self.xmlnode=xmlnode_or_node.docCopyNode(common_doc,1)
    414              common_root.addChild(self.xmlnode)
    415              self.ns=self.xmlnode.ns()
    416          else:
    417              self.xmlnode=common_root.newChild(None,"query",None)
    418              self.ns=self.xmlnode.newNs(DISCO_ITEMS_NS,None)
    419              self.xmlnode.setNs(self.ns)
    420              self.set_node(xmlnode_or_node)
    421          self.xpath_ctxt=common_doc.xpathNewContext()
    422          self.xpath_ctxt.setContextNode(self.xmlnode)
    423          self.xpath_ctxt.xpathRegisterNs("d",DISCO_ITEMS_NS)
    424
    425 -    def __del__(self):
    426          if self.xmlnode:
    427              self.xmlnode.unlinkNode()
    428              self.xmlnode.freeNode()
    429              self.xmlnode=None
    430          if self.xpath_ctxt:
    431              self.xpath_ctxt.xpathFreeContext()
    432              self.xpath_ctxt=None
    433
    434 -    def get_node(self):
    435          """Get the node address of the `DiscoItems` object.
    436
    437          :return: the node name.
    438          :returntype: `unicode`"""
    439          node = self.xmlnode.prop("node")
    440          if not node:
    441              return None
    442          return node.decode("utf-8")
    443
    444 -    def set_node(self, node):
    445          """Set the node of the disco#item element.
    446
    447          :Parameters:
    448              - `node`: the new node or `None`.
    449          :Types:
    450              - `node`: `unicode`
    451          """
    452          if node is None:
    453              if self.xmlnode.hasProp("node"):
    454                  self.xmlnode.unsetProp("node")
    455              return
    456          node = unicode(node)
    457          self.xmlnode.setProp("node", node.encode("utf-8"))
    458
    459      node = property(get_node, set_node)
    460
    461 -    def get_items(self):
    462          """Get the items contained in `self`.
    463
    464          :return: the items contained.
    465          :returntype: `list` of `DiscoItem`"""
    466          ret=[]
    467          l=self.xpath_ctxt.xpathEval("d:item")
    468          if l is not None:
    469              for i in l:
    470                  ret.append(DiscoItem(self, i))
    471          return ret
    472
    473 -    def set_items(self, item_list):
    474          """Set items in the disco#items object.
    475
    476          All previous items are removed.
    477
    478          :Parameters:
    479              - `item_list`: list of items or item properties
    480                (jid,node,name,action).
    481          :Types:
    482              - `item_list`: sequence of `DiscoItem` or sequence of sequences
    483          """
    484          for item in self.items:
    485              item.remove()
    486          for item in item_list:
    487              try:
    488                  self.add_item(item.jid,item.node,item.name,item.action)
    489              except AttributeError:
    490                  self.add_item(*item)
    491
    492      items = property(get_items, set_items, doc = "List of `DiscoItems`")
    493
    494 -    def invalidate_items(self):
    495          """Clear cached item list."""
    496          warnings.warn("DiscoItems.invalidate_items() is deprecated and not needed any more.", DeprecationWarning, stacklevel=1)
    497
    498 -    def add_item(self,jid,node=None,name=None,action=None):
    499          """Add a new item to the `DiscoItems` object.
    500
    501          :Parameters:
    502              - `jid`: item JID.
    503              - `node`: item node name.
    504              - `name`: item name.
    505              - `action`: action for a "disco push".
    506          :Types:
    507              - `jid`: `pyxmpp.JID`
    508              - `node`: `unicode`
    509              - `name`: `unicode`
    510              - `action`: `unicode`
    511
    512          :returns: the item created.
    513          :returntype: `DiscoItem`."""
    514          return DiscoItem(self,jid,node,name,action)
    515
    516 -    def has_item(self,jid,node=None):
    517          """Check if `self` contains an item.
    518
    519          :Parameters:
    520              - `jid`: JID of the item.
    521              - `node`: node name of the item.
    522          :Types:
    523              - `jid`: `JID`
    524              - `node`: `libxml2.xmlNode`
    525
    526          :return: `True` if the item is found in `self`.
    527          :returntype: `bool`"""
    528          l=self.xpath_ctxt.xpathEval("d:item")
    529          if l is None:
    530              return False
    531          for it in l:
    532              di=DiscoItem(self,it)
    533              if di.jid==jid and di.node==node:
    534                  return True
    535          return False
    536
    537 -class DiscoInfo(StanzaPayloadWrapperObject):
    538      """A disco#info response object.
    539
    540      :Ivariables:
    541          - `node`: node name of the disco#info element (cached).
    542          - `identities`: identities in the disco#info object.
    543          - `features`: features in the disco#info object.
    544          - `xmlnode`: XML element listing the items.
    545      :Types:
    546          - `node`: `unicode`
    547          - `identities`: `tuple` of `DiscoIdentity`
    548          - `features`: `tuple` of `unicode`
    549          - `xmlnode`: `libxml2.xmlNode`
    550      """
    551 -    def __init__(self,xmlnode_or_node=None, parent=None, doc=None):
    552          """Initialize an `DiscoInfo` object.
    553
    554          Wrap an existing disco#info XML element or create a new one.
    555
    556          :Parameters:
    557              - `xmlnode_or_node`: XML node to be wrapped into `self` or an item
    558                node name.
    559              - `parent`: parent node for the `DiscoInfo` element.
    560              - `doc`: document for the `DiscoInfo` element.
    561          :Types:
    562              - `xmlnode_or_node`: `libxml2.xmlNode` or `unicode`
    563              - `parent`: `libxml2.xmlNode`
    564              - `doc`: `libxml2.xmlDoc`
    565              """
    566          self.xmlnode=None
    567          self.xpath_ctxt=None
    568          if not doc:
    569              doc=common_doc
    570          if not parent:
    571              parent=common_root
    572          if isinstance(xmlnode_or_node,libxml2.xmlNode):
    573              ns=xmlnode_or_node.ns()
    574              if ns.getContent() != DISCO_INFO_NS:
    575                  raise ValueError, "Bad disco-info namespace"
    576              self.xmlnode=xmlnode_or_node.docCopyNode(doc,1)
    577              parent.addChild(self.xmlnode)
    578          else:
    579              self.xmlnode=parent.newChild(None,"query",None)
    580              self.ns=self.xmlnode.newNs(DISCO_INFO_NS,None)
    581              self.xmlnode.setNs(self.ns)
    582              self.set_node(xmlnode_or_node)
    583
    584          self.xpath_ctxt=doc.xpathNewContext()
    585          self.xpath_ctxt.setContextNode(self.xmlnode)
    586          self.xpath_ctxt.xpathRegisterNs("d",DISCO_INFO_NS)
    587
    588 -    def __del__(self):
    589          if self.xmlnode:
    590              self.xmlnode.unlinkNode()
    591              self.xmlnode.freeNode()
    592              self.xmlnode=None
    593          if self.xpath_ctxt:
    594              self.xpath_ctxt.xpathFreeContext()
    595              self.xpath_ctxt=None
    596
    597 -    def get_node(self):
    598          """Get the node address of the `DiscoInfo` object.
    599
    600          :return: the node name.
    601          :returntype: `unicode`"""
    602
    603          node=self.xmlnode.prop("node")
    604          if not node:
    605              return None
    606          return node.decode("utf-8")
    607
    608 -    def set_node(self,node):
    609          """Set the node of the disco#info element.
    610
    611          :Parameters:
    612              - `node`: the new node or `None`.
    613          :Types:
    614              - `node`: `unicode`
    615          """
    616          if node is None:
    617              if self.xmlnode.hasProp("node"):
    618                  self.xmlnode.unsetProp("node")
    619              return
    620          node = unicode(node)
    621          self.xmlnode.setProp("node", node.encode("utf-8"))
    622
    623      node = property(get_node, set_node)
    624
    625 -    def get_features(self):
    626          """Get the features contained in `self`.
    627
    628          :return: the list of features.
    629          :returntype: `list` of `unicode`"""
    630          l = self.xpath_ctxt.xpathEval("d:feature")
    631          ret = []
    632          for f in l:
    633              if f.hasProp("var"):
    634                  ret.append( f.prop("var").decode("utf-8") )
    635          return ret
    636
    637 -    def set_features(self, features):
    638          """Set features in the disco#info object.
    639
    640          All existing features are removed from `self`.
    641
    642          :Parameters:
    643              - `features`: list of features.
    644          :Types:
    645              - `features`: sequence of `unicode`
    646          """
    647          for var in self.features:
    648              self.remove_feature(var)
    649
    650          for var in features:
    651              self.add_feature(var)
    652
    653      features = property(get_features, set_features)
    654
    655 -    def has_feature(self,var):
    656          """Check if `self` contains the named feature.
    657
    658          :Parameters:
    659              - `var`: the feature name.
    660          :Types:
    661              - `var`: `unicode`
    662
    663          :return: `True` if the feature is found in `self`.
    664          :returntype: `bool`"""
    665          if not var:
    666              raise ValueError,"var is None"
    667          if '"' not in var:
    668              expr=u'd:feature[@var="%s"]' % (var,)
    669          elif "'" not in var:
    670              expr=u"d:feature[@var='%s']" % (var,)
    671          else:
    672              raise ValueError, "Invalid feature name"
    673
    674          l=self.xpath_ctxt.xpathEval(to_utf8(expr))
    675          if l:
    676              return True
    677          else:
    678              return False
    679
    680 -    def invalidate_features(self):
    681          """Clear cached feature list."""
    682          warnings.warn("DiscoInfo.invalidate_features() is deprecated and not needed any more.", DeprecationWarning, stacklevel=1)
    683
    684 -    def add_feature(self,var):
    685          """Add a feature to `self`.
    686
    687          :Parameters:
    688              - `var`: the feature name.
    689          :Types:
    690              - `var`: `unicode`"""
    691          if self.has_feature(var):
    692              return
    693          n=self.xmlnode.newChild(None, "feature", None)
    694          n.setProp("var", to_utf8(var))
    695
    696 -    def remove_feature(self,var):
    697          """Remove a feature from `self`.
    698
    699          :Parameters:
    700              - `var`: the feature name.
    701          :Types:
    702              - `var`: `unicode`"""
    703          if not var:
    704              raise ValueError,"var is None"
    705          if '"' not in var:
    706              expr='d:feature[@var="%s"]' % (var,)
    707          elif "'" not in var:
    708              expr="d:feature[@var='%s']" % (var,)
    709          else:
    710              raise ValueError, "Invalid feature name"
    711
    712          l=self.xpath_ctxt.xpathEval(expr)
    713          if not l:
    714              return
    715
    716          for f in l:
    717              f.unlinkNode()
    718              f.freeNode()
    719
    720 -    def get_identities(self):
    721          """List the identity objects contained in `self`.
    722
    723          :return: the list of identities.
    724          :returntype: `list` of `DiscoIdentity`"""
    725          ret=[]
    726          l=self.xpath_ctxt.xpathEval("d:identity")
    727          if l is not None:
    728              for i in l:
    729                  ret.append(DiscoIdentity(self,i))
    730          return ret
    731
    732 -    def set_identities(self,identities):
    733          """Set identities in the disco#info object.
    734
    735          Remove all existing identities from `self`.
    736
    737          :Parameters:
    738              - `identities`: list of identities or identity properties
    739                (jid,node,category,type,name).
    740          :Types:
    741              - `identities`: sequence of `DiscoIdentity` or sequence of sequences
    742          """
    743          for identity in self.identities:
    744              identity.remove()
    745          for identity in identities:
    746              try:
    747                  self.add_identity(identity.name,identity.category,identity.type)
    748              except AttributeError:
    749                  self.add_identity(*identity)
    750
    751      identities = property(get_identities, set_identities)
    752
    753 -    def identity_is(self,item_category,item_type=None):
    754          """Check if the item described by `self` belongs to the given category
    755          and type.
    756
    757          :Parameters:
    758              - `item_category`: the category name.
    759              - `item_type`: the type name. If `None` then only the category is
    760                checked.
    761          :Types:
    762              - `item_category`: unicode
    763              - `item_type`: unicode
    764
    765          :return: `True` if `self` contains at least one <identity/> object with
    766              given type and category.
    767          :returntype: `bool`"""
    768          if not item_category:
    769              raise ValueError, "bad category"
    770          if not item_type:
    771              type_expr=u""
    772          elif '"' not in item_type:
    773              type_expr=u' and @type="%s"' % (item_type,)
    774          elif "'" not in type:
    775              type_expr=u" and @type='%s'" % (item_type,)
    776          else:
    777              raise ValueError, "Invalid type name"
    778          if '"' not in item_category:
    779              expr=u'd:identity[@category="%s"%s]' % (item_category,type_expr)
    780          elif "'" not in item_category:
    781              expr=u"d:identity[@category='%s'%s]" % (item_category,type_expr)
    782          else:
    783              raise ValueError, "Invalid category name"
    784
    785          l=self.xpath_ctxt.xpathEval(to_utf8(expr))
    786          if l:
    787              return True
    788          else:
    789              return False
    790
    791 -    def invalidate_identities(self):
    792          """Clear cached identity list."""
    793          warnings.warn("DiscoInfo.invalidate_identities() is deprecated and not needed any more.", DeprecationWarning, stacklevel=1)
    794
    795 -    def add_identity(self,item_name,item_category=None,item_type=None):
    796          """Add an identity to the `DiscoInfo` object.
    797
    798          :Parameters:
    799              - `item_name`: name of the item.
    800              - `item_category`: category of the item.
    801              - `item_type`: type of the item.
    802          :Types:
    803              - `item_name`: `unicode`
    804              - `item_category`: `unicode`
    805              - `item_type`: `unicode`
    806
    807          :returns: the identity created.
    808          :returntype: `DiscoIdentity`"""
    809          return DiscoIdentity(self,item_name,item_category,item_type)
    810
    811 -class DiscoCacheFetcherBase(cache.CacheFetcher):
    812      """Base class for disco cache fetchers.
    813
    814      :Cvariables:
    815          - `stream`: stream used by the fetcher.
    816          - `disco_class`: disco class to be used (`DiscoInfo` or `DiscoItems`).
    817      :Types:
    818          - `stream`: `pyxmpp.stream.Stream`
    819          - `disco_class`: `classobj`
    820      """
    821      stream=None
    822      disco_class=None
    823 -    def fetch(self):
    824          """Initialize the Service Discovery process."""
    825          from pyxmpp.iq import Iq
    826          jid,node = self.address
    827          iq = Iq(to_jid = jid, stanza_type = "get")
    828          disco = self.disco_class(node)
    829          iq.add_content(disco.xmlnode)
    830          self.stream.set_response_handlers(iq,self.__response, self.__error,
    831                  self.__timeout)
    832          self.stream.send(iq)
    833
    834 -    def __response(self,stanza):
    835          """Handle successful disco response.
    836
    837          :Parameters:
    838              - `stanza`: the stanza received.
    839          :Types:
    840              - `stanza`: `pyxmpp.stanza.Stanza`"""
    841          try:
    842              d=self.disco_class(stanza.get_query())
    843              self.got_it(d)
    844          except ValueError,e:
    845              self.error(e)
    846
    847 -    def __error(self,stanza):
    848          """Handle disco error response.
    849
    850          :Parameters:
    851              - `stanza`: the stanza received.
    852          :Types:
    853              - `stanza`: `pyxmpp.stanza.Stanza`"""
    854          try:
    855              self.error(stanza.get_error())
    856          except ProtocolError:
    857              from pyxmpp.error import StanzaErrorNode
    858              self.error(StanzaErrorNode("undefined-condition"))
    859
    860 -    def __timeout(self,stanza):
    861          """Handle disco timeout."""
    862          pass
    863
    864 -def register_disco_cache_fetchers(cache_suite,stream):
    865      """Register Service Discovery cache fetchers into given
    866      cache suite and using the stream provided.
    867
    868      :Parameters:
    869          - `cache_suite`: the cache suite where the fetchers are to be
    870            registered.
    871          - `stream`: the stream to be used by the fetchers.
    872      :Types:
    873          - `cache_suite`: `cache.CacheSuite`
    874          - `stream`: `pyxmpp.stream.Stream`
    875      """
    876      tmp=stream
    877      class DiscoInfoCacheFetcher(DiscoCacheFetcherBase):
    878          """Cache fetcher for DiscoInfo."""
    879          stream=tmp
    880          disco_class=DiscoInfo
    881      class DiscoItemsCacheFetcher(DiscoCacheFetcherBase):
    882          """Cache fetcher for DiscoItems."""
    883          stream=tmp
    884          disco_class=DiscoItems
    885      cache_suite.register_fetcher(DiscoInfo,DiscoInfoCacheFetcher)
    886      cache_suite.register_fetcher(DiscoItems,DiscoItemsCacheFetcher)
    887
    888  # vi: sts=4 et sw=4
    889