Package nbxmpp :: Module roster_nb |
[hide private]
[frames] | no frames]

# Source Code for Module nbxmpp.roster_nb

      1  ##   roster_nb.py
      2  ##         based on roster.py
      3  ##
      4  ##   Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
      5  ##         modified by Dimitur Kirov <dkirov@gmail.com>
      6  ##
      7  ##   This program is free software; you can redistribute it and/or modify
      8  ##   it under the terms of the GNU General Public License as published by
      9  ##   the Free Software Foundation; either version 2, or (at your option)
     10  ##   any later version.
     11  ##
     12  ##   This program is distributed in the hope that it will be useful,
     13  ##   but WITHOUT ANY WARRANTY; without even the implied warranty of
     14  ##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15  ##   GNU General Public License for more details.
     16
     17  # $Id: roster.py,v 1.17 2005/05/02 08:38:49 snakeru Exp $
     18
     19
     20  """
     21  Simple roster implementation. Can be used though for different tasks like
     22  mass-renaming of contacts.
     23  """
     24
     25  from protocol import JID, Iq, Presence, Node, NodeProcessed, NS_MUC_USER, NS_ROSTER
     26  from plugin import PlugIn
     27
     28  import logging
     29  log = logging.getLogger('nbxmpp.roster_nb')
     30
     31
     32 -class NonBlockingRoster(PlugIn):
     33      """
     34      Defines a plenty of methods that will allow you to manage roster. Also
     35      automatically track presences from remote JIDs taking into account that
     36      every JID can have multiple resources connected. Does not currently support
     37      'error' presences. You can also use mapping interface for access to the
     38      internal representation of contacts in roster
     39      """
     40
     41 -    def __init__(self, version=None):
     42          """
     43          Init internal variables
     44          """
     45          PlugIn.__init__(self)
     46          self.version = version
     47          self._data = {}
     48          self._set=None
     49          self._exported_methods=[self.getRoster]
     50          self.received_from_server = False
     51
     52 -    def Request(self, force=0):
     53          """
     54          Request roster from server if it were not yet requested (or if the
     55          'force' argument is set)
     56          """
     57          if self._set is None:
     58              self._set = 0
     59          elif not force:
     60              return
     61
     62          iq = Iq('get', NS_ROSTER)
     63          if self.version is not None:
     64              iq.setTagAttr('query', 'ver', self.version)
     65          id_ = self._owner.getAnID()
     66          iq.setID(id_)
     67          self._owner.send(iq)
     68          log.info('Roster requested from server')
     69          return id_
     70
     71 -    def RosterIqHandler(self, dis, stanza):
     72          """
     73          Subscription tracker. Used internally for setting items state in internal
     74          roster representation
     75          """
     76          sender = stanza.getAttr('from')
     77          if not sender is None and not sender.bareMatch(
     78          self._owner.User + '@' + self._owner.Server):
     79              return
     80          query = stanza.getTag('query')
     81          if query:
     82              self.received_from_server = True
     83              self.version = stanza.getTagAttr('query', 'ver')
     84              if self.version is None:
     85                  self.version = ''
     86              for item in query.getTags('item'):
     87                  jid=item.getAttr('jid')
     88                  if item.getAttr('subscription')=='remove':
     89                      if self._data.has_key(jid): del self._data[jid]
     90                      # Looks like we have a workaround
     91                      # raise NodeProcessed # a MUST
     92                  log.info('Setting roster item %s...' % jid)
     93                  if not self._data.has_key(jid): self._data[jid]={}
     94                  self._data[jid]['name']=item.getAttr('name')
     95                  self._data[jid]['ask']=item.getAttr('ask')
     96                  self._data[jid]['subscription']=item.getAttr('subscription')
     97                  self._data[jid]['groups']=[]
     98                  if not self._data[jid].has_key('resources'): self._data[jid]['resources']={}
     99                  for group in item.getTags('group'):
    100                      if group.getData() not in self._data[jid]['groups']:
    101                          self._data[jid]['groups'].append(group.getData())
    102          self._data[self._owner.User+'@'+self._owner.Server]={'resources': {}, 'name': None, 'ask': None, 'subscription': None, 'groups': None,}
    103          self._set=1
    104          # Looks like we have a workaround
    105          # raise NodeProcessed # a MUST. Otherwise you'll get back an <iq type='error'/>
    106
    107 -    def PresenceHandler(self, dis, pres):
    108          """
    109          Presence tracker. Used internally for setting items' resources state in
    110          internal roster representation
    111          """
    112          if pres.getTag('x', namespace=NS_MUC_USER):
    113              return
    114          jid=pres.getFrom()
    115          if not jid:
    116              # If no from attribue, it's from server
    117              jid=self._owner.Server
    118          jid=JID(jid)
    119          if not self._data.has_key(jid.getStripped()): self._data[jid.getStripped()]={'name':None,'ask':None,'subscription':'none','groups':['Not in roster'],'resources':{}}
    120          if type(self._data[jid.getStripped()]['resources'])!=type(dict()):
    121              self._data[jid.getStripped()]['resources']={}
    122          item=self._data[jid.getStripped()]
    123          typ=pres.getType()
    124
    125          if not typ:
    126              log.info('Setting roster item %s for resource %s...'%(jid.getStripped(), jid.getResource()))
    127              item['resources'][jid.getResource()]=res={'show':None,'status':None,'priority':'0','timestamp':None}
    128              if pres.getTag('show'): res['show']=pres.getShow()
    129              if pres.getTag('status'): res['status']=pres.getStatus()
    130              if pres.getTag('priority'): res['priority']=pres.getPriority()
    131              if not pres.getTimestamp(): pres.setTimestamp()
    132              res['timestamp']=pres.getTimestamp()
    133          elif typ=='unavailable' and item['resources'].has_key(jid.getResource()): del item['resources'][jid.getResource()]
    134          # Need to handle type='error' also
    135
    136 -    def _getItemData(self, jid, dataname):
    137          """
    138          Return specific jid's representation in internal format. Used internally
    139          """
    140          jid = jid[:(jid+'/').find('/')]
    141          return self._data[jid][dataname]
    142
    143 -    def _getResourceData(self, jid, dataname):
    144          """
    145          Return specific jid's resource representation in internal format. Used
    146          internally
    147          """
    148          if jid.find('/') + 1:
    149              jid, resource = jid.split('/', 1)
    150              if self._data[jid]['resources'].has_key(resource):
    151                  return self._data[jid]['resources'][resource][dataname]
    152          elif self._data[jid]['resources'].keys():
    153              lastpri = -129
    154              for r in self._data[jid]['resources'].keys():
    155                  if int(self._data[jid]['resources'][r]['priority']) > lastpri:
    156                      resource, lastpri=r, int(self._data[jid]['resources'][r]['priority'])
    157              return self._data[jid]['resources'][resource][dataname]
    158
    159 -    def delItem(self, jid):
    160          """
    161          Delete contact 'jid' from roster
    162          """
    163          self._owner.send(Iq('set', NS_ROSTER, payload=[Node('item', {'jid': jid, 'subscription': 'remove'})]))
    164
    165 -    def getAsk(self, jid):
    166          """
    167          Return 'ask' value of contact 'jid'
    168          """
    169          return self._getItemData(jid, 'ask')
    170
    171 -    def getGroups(self, jid):
    172          """
    173          Return groups list that contact 'jid' belongs to
    174          """
    175          return self._getItemData(jid, 'groups')
    176
    177 -    def getName(self, jid):
    178          """
    179          Return name of contact 'jid'
    180          """
    181          return self._getItemData(jid, 'name')
    182
    183 -    def getPriority(self, jid):
    184          """
    185          Return priority of contact 'jid'. 'jid' should be a full (not bare) JID
    186          """
    187          return self._getResourceData(jid, 'priority')
    188
    189 -    def getRawRoster(self):
    190          """
    191          Return roster representation in internal format
    192          """
    193          return self._data
    194
    195 -    def getRawItem(self, jid):
    196          """
    197          Return roster item 'jid' representation in internal format
    198          """
    199          return self._data[jid[:(jid+'/').find('/')]]
    200
    201 -    def getShow(self, jid):
    202          """
    203          Return 'show' value of contact 'jid'. 'jid' should be a full (not bare)
    204          JID
    205          """
    206          return self._getResourceData(jid, 'show')
    207
    208 -    def getStatus(self, jid):
    209          """
    210          Return 'status' value of contact 'jid'. 'jid' should be a full (not bare)
    211          JID
    212          """
    213          return self._getResourceData(jid, 'status')
    214
    215 -    def getSubscription(self, jid):
    216          """
    217          Return 'subscription' value of contact 'jid'
    218          """
    219          return self._getItemData(jid, 'subscription')
    220
    221 -    def getResources(self, jid):
    222          """
    223          Return list of connected resources of contact 'jid'
    224          """
    225          return self._data[jid[:(jid+'/').find('/')]]['resources'].keys()
    226
    227 -    def setItem(self, jid, name=None, groups=[]):
    228          """
    229          Rename contact 'jid' and sets the groups list that it now belongs to
    230          """
    231          iq = Iq('set', NS_ROSTER)
    232          query = iq.getTag('query')
    233          attrs = {'jid': jid}
    234          if name:
    235              attrs['name'] = name
    236          item = query.setTag('item', attrs)
    237          for group in groups:
    238              item.addChild(node=Node('group', payload=[group]))
    239          self._owner.send(iq)
    240
    241 -    def setItemMulti(self, items):
    242          """
    243          Rename multiple contacts and sets their group lists
    244          """
    245          iq = Iq('set', NS_ROSTER)
    246          query = iq.getTag('query')
    247          for i in items:
    248              attrs = {'jid': i['jid']}
    249              if i['name']:
    250                  attrs['name'] = i['name']
    251              item = query.setTag('item', attrs)
    252              for group in i['groups']:
    253                  item.addChild(node=Node('group', payload=[group]))
    254          self._owner.send(iq)
    255
    256 -    def getItems(self):
    257          """
    258          Return list of all [bare] JIDs that the roster is currently tracks
    259          """
    260          return self._data.keys()
    261
    262 -    def keys(self):
    263          """
    264          Same as getItems. Provided for the sake of dictionary interface
    265          """
    266          return self._data.keys()
    267
    268 -    def __getitem__(self, item):
    269          """
    270          Get the contact in the internal format. Raises KeyError if JID 'item' is
    271          not in roster
    272          """
    273          return self._data[item]
    274
    275 -    def getItem(self, item):
    276          """
    277          Get the contact in the internal format (or None if JID 'item' is not in
    278          roster)
    279          """
    280          if self._data.has_key(item):
    281              return self._data[item]
    282
    283 -    def Subscribe(self, jid):
    284          """
    285          Send subscription request to JID 'jid'
    286          """
    287          self._owner.send(Presence(jid, 'subscribe'))
    288
    289 -    def Unsubscribe(self, jid):
    290          """
    291          Ask for removing our subscription for JID 'jid'
    292          """
    293          self._owner.send(Presence(jid, 'unsubscribe'))
    294
    295 -    def Authorize(self, jid):
    296          """
    297          Authorize JID 'jid'. Works only if these JID requested auth previously
    298          """
    299          self._owner.send(Presence(jid, 'subscribed'))
    300
    301 -    def Unauthorize(self, jid):
    302          """
    303          Unauthorise JID 'jid'. Use for declining authorisation request or for
    304          removing existing authorization
    305          """
    306          self._owner.send(Presence(jid, 'unsubscribed'))
    307
    308 -    def getRaw(self):
    309          """
    310          Return the internal data representation of the roster
    311          """
    312          return self._data
    313
    314 -    def setRaw(self, data):
    315          """
    316          Return the internal data representation of the roster
    317          """
    318          self._data = data
    319          self._data[self._owner.User + '@' + self._owner.Server] = {
    320                          'resources': {},
    321                          'name': None,
    322                          'ask': None,
    323                          'subscription': None,
    324                          'groups': None
    325          }
    326          self._set = 1
    327
    328 -    def plugin(self, owner, request=1):
    329          """
    330          Register presence and subscription trackers in the owner's dispatcher.
    331          Also request roster from server if the 'request' argument is set. Used
    332          internally
    333          """
    334          self._owner.RegisterHandler('iq', self.RosterIqHandler, 'result', NS_ROSTER, makefirst = 1)
    335          self._owner.RegisterHandler('iq', self.RosterIqHandler, 'set', NS_ROSTER)
    336          self._owner.RegisterHandler('presence', self.PresenceHandler)
    337          if request:
    338              return self.Request()
    339
    340 -    def _on_roster_set(self, data):
    341          if data:
    342              self._owner.Dispatcher.ProcessNonBlocking(data)
    343          if not self._set:
    344              return
    345          if not hasattr(self, '_owner') or not self._owner:
    346              # Connection has been closed by receiving a <stream:error> for ex,
    347              return
    348          self._owner.onreceive(None)
    349          if self.on_ready:
    350              self.on_ready(self)
    351              self.on_ready = None
    352          return True
    353
    354 -    def getRoster(self, on_ready=None, force=False):
    355          """
    356          Request roster from server if neccessary and returns self
    357          """
    358          return_self = True
    359          if not self._set:
    360              self.on_ready = on_ready
    361              self._owner.onreceive(self._on_roster_set)
    362              return_self = False
    363          elif on_ready:
    364              on_ready(self)
    365              return_self = False
    366          if return_self or force:
    367              return self
    368          return None
    369