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