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

# Source Code for Module pyxmpp.jabber.disco

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