gajim
view src/common/xmpp/roster_nb.py @ 10773:1076fc9700f5
merge elghinn's branch (roster versioning) to trunk. Fixes #4661, #3190
| author | Yann Leboulanger <asterix@lagaule.org> |
|---|---|
| date | Fri, 10 Jul 2009 15:05:01 +0200 |
| parents | d079ffe7bb5a ce797a2b9b5d |
| children | 130d308b573a |
line source
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.
17 # $Id: roster.py,v 1.17 2005/05/02 08:38:49 snakeru Exp $
19 '''
20 Simple roster implementation. Can be used though for different tasks like
21 mass-renaming of contacts.
22 '''
24 from protocol import JID, Iq, Presence, Node, NodeProcessed, NS_ROSTER
25 from plugin import PlugIn
27 import logging
28 log = logging.getLogger('gajim.c.x.roster_nb')
31 class NonBlockingRoster(PlugIn):
32 ''' Defines a plenty of methods that will allow you to manage roster.
33 Also automatically track presences from remote JIDs taking into
34 account that every JID can have multiple resources connected. Does not
35 currently support 'error' presences.
36 You can also use mapping interface for access to the internal representation of
37 contacts in roster.
38 '''
39 def __init__(self, version=''):
40 ''' Init internal variables. '''
41 PlugIn.__init__(self)
42 self.version = version
43 self._data = {}
44 self.set=None
45 self._exported_methods=[self.getRoster]
46 self.received_from_server = False
48 def Request(self,force=0):
49 ''' Request roster from server if it were not yet requested
50 (or if the 'force' argument is set). '''
51 if self.set is None: self.set=0
52 elif not force: return
54 iq = Iq('get',NS_ROSTER)
55 iq.setTagAttr('query', 'ver', self.version)
56 id_ = self._owner.getAnID()
57 iq.setID(id_)
58 self._owner.send(iq)
59 log.info('Roster requested from server')
60 return id_
62 def RosterIqHandler(self,dis,stanza):
63 ''' Subscription tracker. Used internally for setting items state in
64 internal roster representation. '''
65 sender = stanza.getAttr('from')
66 if not sender is None and not sender.bareMatch(
67 self._owner.User + '@' + self._owner.Server):
68 return
69 query = stanza.getTag('query')
70 if query:
71 self.received_from_server = True
72 self.version = stanza.getTagAttr('query', 'ver')
73 if self.version is None:
74 self.version = ''
75 for item in query.getTags('item'):
76 jid=item.getAttr('jid')
77 if item.getAttr('subscription')=='remove':
78 if self._data.has_key(jid): del self._data[jid]
79 # Looks like we have a workaround
80 # raise NodeProcessed # a MUST
81 log.info('Setting roster item %s...' % jid)
82 if not self._data.has_key(jid): self._data[jid]={}
83 self._data[jid]['name']=item.getAttr('name')
84 self._data[jid]['ask']=item.getAttr('ask')
85 self._data[jid]['subscription']=item.getAttr('subscription')
86 self._data[jid]['groups']=[]
87 if not self._data[jid].has_key('resources'): self._data[jid]['resources']={}
88 for group in item.getTags('group'): self._data[jid]['groups'].append(group.getData())
89 self._data[self._owner.User+'@'+self._owner.Server]={'resources':{},'name':None,'ask':None,'subscription':None,'groups':None,}
90 self.set=1
91 # Looks like we have a workaround
92 # raise NodeProcessed # a MUST. Otherwise you'll get back an <iq type='error'/>
94 def PresenceHandler(self,dis,pres):
95 ''' Presence tracker. Used internally for setting items' resources state in
96 internal roster representation. '''
97 jid=pres.getFrom()
98 if not jid:
99 # If no from attribue, it's from server
100 jid=self._owner.Server
101 jid=JID(jid)
102 if not self._data.has_key(jid.getStripped()): self._data[jid.getStripped()]={'name':None,'ask':None,'subscription':'none','groups':['Not in roster'],'resources':{}}
103 if type(self._data[jid.getStripped()]['resources'])!=type(dict()):
104 self._data[jid.getStripped()]['resources']={}
105 item=self._data[jid.getStripped()]
106 typ=pres.getType()
108 if not typ:
109 log.info('Setting roster item %s for resource %s...'%(jid.getStripped(),jid.getResource()))
110 item['resources'][jid.getResource()]=res={'show':None,'status':None,'priority':'0','timestamp':None}
111 if pres.getTag('show'): res['show']=pres.getShow()
112 if pres.getTag('status'): res['status']=pres.getStatus()
113 if pres.getTag('priority'): res['priority']=pres.getPriority()
114 if not pres.getTimestamp(): pres.setTimestamp()
115 res['timestamp']=pres.getTimestamp()
116 elif typ=='unavailable' and item['resources'].has_key(jid.getResource()): del item['resources'][jid.getResource()]
117 # Need to handle type='error' also
119 def _getItemData(self,jid,dataname):
120 ''' Return specific jid's representation in internal format. Used internally. '''
121 jid=jid[:(jid+'/').find('/')]
122 return self._data[jid][dataname]
123 def _getResourceData(self,jid,dataname):
124 ''' Return specific jid's resource representation in internal format. Used internally. '''
125 if jid.find('/')+1:
126 jid,resource=jid.split('/',1)
127 if self._data[jid]['resources'].has_key(resource): return self._data[jid]['resources'][resource][dataname]
128 elif self._data[jid]['resources'].keys():
129 lastpri=-129
130 for r in self._data[jid]['resources'].keys():
131 if int(self._data[jid]['resources'][r]['priority'])>lastpri: resource,lastpri=r,int(self._data[jid]['resources'][r]['priority'])
132 return self._data[jid]['resources'][resource][dataname]
133 def delItem(self,jid):
134 ''' Delete contact 'jid' from roster.'''
135 self._owner.send(Iq('set',NS_ROSTER,payload=[Node('item',{'jid':jid,'subscription':'remove'})]))
136 def getAsk(self,jid):
137 ''' Returns 'ask' value of contact 'jid'.'''
138 return self._getItemData(jid,'ask')
139 def getGroups(self,jid):
140 ''' Returns groups list that contact 'jid' belongs to.'''
141 return self._getItemData(jid,'groups')
142 def getName(self,jid):
143 ''' Returns name of contact 'jid'.'''
144 return self._getItemData(jid,'name')
145 def getPriority(self,jid):
146 ''' Returns priority of contact 'jid'. 'jid' should be a full (not bare) JID.'''
147 return self._getResourceData(jid,'priority')
148 def getRawRoster(self):
149 ''' Returns roster representation in internal format. '''
150 return self._data
151 def getRawItem(self,jid):
152 ''' Returns roster item 'jid' representation in internal format. '''
153 return self._data[jid[:(jid+'/').find('/')]]
154 def getShow(self, jid):
155 ''' Returns 'show' value of contact 'jid'. 'jid' should be a full (not bare) JID.'''
156 return self._getResourceData(jid,'show')
157 def getStatus(self, jid):
158 ''' Returns 'status' value of contact 'jid'. 'jid' should be a full (not bare) JID.'''
159 return self._getResourceData(jid,'status')
160 def getSubscription(self,jid):
161 ''' Returns 'subscription' value of contact 'jid'.'''
162 return self._getItemData(jid,'subscription')
163 def getResources(self,jid):
164 ''' Returns list of connected resources of contact 'jid'.'''
165 return self._data[jid[:(jid+'/').find('/')]]['resources'].keys()
166 def setItem(self,jid,name=None,groups=[]):
167 ''' Renames contact 'jid' and sets the groups list that it now belongs to.'''
168 iq=Iq('set',NS_ROSTER)
169 query=iq.getTag('query')
170 attrs={'jid':jid}
171 if name: attrs['name']=name
172 item=query.setTag('item',attrs)
173 for group in groups: item.addChild(node=Node('group',payload=[group]))
174 self._owner.send(iq)
175 def getItems(self):
176 ''' Return list of all [bare] JIDs that the roster is currently tracks.'''
177 return self._data.keys()
178 def keys(self):
179 ''' Same as getItems. Provided for the sake of dictionary interface.'''
180 return self._data.keys()
181 def __getitem__(self,item):
182 ''' Get the contact in the internal format. Raises KeyError if JID 'item' is not in roster.'''
183 return self._data[item]
184 def getItem(self,item):
185 ''' Get the contact in the internal format (or None if JID 'item' is not in roster).'''
186 if self._data.has_key(item): return self._data[item]
187 def Subscribe(self,jid):
188 ''' Send subscription request to JID 'jid'.'''
189 self._owner.send(Presence(jid,'subscribe'))
190 def Unsubscribe(self,jid):
191 ''' Ask for removing our subscription for JID 'jid'.'''
192 self._owner.send(Presence(jid,'unsubscribe'))
193 def Authorize(self,jid):
194 ''' Authorise JID 'jid'. Works only if these JID requested auth previously. '''
195 self._owner.send(Presence(jid,'subscribed'))
196 def Unauthorize(self,jid):
197 ''' Unauthorise JID 'jid'. Use for declining authorisation request
198 or for removing existing authorization. '''
199 self._owner.send(Presence(jid,'unsubscribed'))
200 def getRaw(self):
201 '''Returns the internal data representation of the roster.'''
202 return self._data
203 def setRaw(self, data):
204 '''Returns the internal data representation of the roster.'''
205 self._data = data
206 self._data[self._owner.User+'@'+self._owner.Server]={'resources':{},'name':None,'ask':None,'subscription':None,'groups':None,}
207 self.set=1
208 # copypasted methods for roster.py from constructor to here
211 def plugin(self, owner, request=1):
212 ''' Register presence and subscription trackers in the owner's dispatcher.
213 Also request roster from server if the 'request' argument is set.
214 Used internally.'''
215 self._owner.RegisterHandler('iq', self.RosterIqHandler, 'result', NS_ROSTER, makefirst = 1)
216 self._owner.RegisterHandler('iq', self.RosterIqHandler, 'set', NS_ROSTER)
217 self._owner.RegisterHandler('presence', self.PresenceHandler)
218 if request:
219 return self.Request()
221 def _on_roster_set(self, data):
222 if data:
223 self._owner.Dispatcher.ProcessNonBlocking(data)
224 if not self.set:
225 return
226 self._owner.onreceive(None)
227 if self.on_ready:
228 self.on_ready(self)
229 self.on_ready = None
230 return True
232 def getRoster(self, on_ready=None, force=False):
233 ''' Requests roster from server if neccessary and returns self. '''
234 return_self = True
235 if not self.set:
236 self.on_ready = on_ready
237 self._owner.onreceive(self._on_roster_set)
238 return_self = False
239 elif on_ready:
240 on_ready(self)
241 return_self = False
242 if return_self or force:
243 return self
244 return None
246 # vim: se ts=3:
