comparison src/core/xmpp.py @ 1262:f8a8434dbac7 frontends_multi_profiles

core: improved roster management + misc: - updated methods to no use anymore methods deprecated in Wokkel - use of full jid when it make sense instead of bare jid - getContacts, updateContact and delContact are now asynchronous
author Goffi <goffi@goffi.org>
date Wed, 10 Dec 2014 18:32:33 +0100
parents 8b891f9be183
children 3360074a2f00
comparison
equal deleted inserted replaced
1261:93bce9e4c9c8 1262:f8a8434dbac7
170 class SatRosterProtocol(xmppim.RosterClientProtocol): 170 class SatRosterProtocol(xmppim.RosterClientProtocol):
171 171
172 def __init__(self, host): 172 def __init__(self, host):
173 xmppim.RosterClientProtocol.__init__(self) 173 xmppim.RosterClientProtocol.__init__(self)
174 self.host = host 174 self.host = host
175 self.got_roster = defer.Deferred() 175 self.got_roster = defer.Deferred() # called when roster is received and ready
176 #XXX: the two following dicts keep a local copy of the roster 176 #XXX: the two following dicts keep a local copy of the roster
177 self._groups = {} # map from groups to bare jids: key=group value=set of bare jids 177 self._groups = {} # map from groups to jids: key=group value=set of jids
178 self._jids = {} # map from bare jids to RosterItem: key=jid value=RosterItem 178 self._jids = None # map from jids to RosterItem: key=jid value=RosterItem
179 179
180 def rosterCb(self, roster): 180 def rosterCb(self, roster):
181 for raw_jid, item in roster.iteritems(): 181 assert roster is not None # FIXME: must be managed with roster versioning
182 self.onRosterSet(item) 182 self._jids = roster
183
184 def _registerItem(self, item):
185 """Register item in local cache
186
187 item must be already registered in self._jids before this method is called
188 @param item (RosterIem): item added
189 """
190 log.debug("registering item: {}".format(item.jid.full()))
191 if item.entity.resource:
192 log.warning("Received a roster item with a resource, this is not common but not restricted by RFC 6121, this case may be not well tested.")
193 import ipdb; ipdb.set_trace()
194 if not item.subscriptionTo and not item.subscriptionFrom and not item.ask:
195 #XXX: current behaviour: we don't want contact in our roster list
196 # if there is no presence subscription
197 # may change in the future
198 self.removeItem(item.jid) # FIXME: to be checked
199 return
200 if not item.subscriptionTo:
201 if not item.subscriptionFrom:
202 log.info(_("There's no subscription between you and [{}]!").format(item.jid.full()))
203 else:
204 log.info(_("You are not subscribed to [{}]!").format(item.jid.full()))
205 if not item.subscriptionFrom:
206 log.info(_("[{}] is not subscribed to you!").format(item.jid.full()))
207 #self.host.memory.addContact(item.jid, item_attr, item.groups, self.parent.profile)
208
209 for group in item.groups:
210 self._groups.setdefault(group, set()).add(item.entity)
183 211
184 def requestRoster(self): 212 def requestRoster(self):
185 """ ask the server for Roster list """ 213 """ ask the server for Roster list """
186 log.debug("requestRoster") 214 log.debug("requestRoster")
187 d = self.getRoster().addCallback(self.rosterCb) 215 d = self.getRoster().addCallback(self.rosterCb)
188 d.chainDeferred(self.got_roster) 216 d.chainDeferred(self.got_roster)
189 217
190 def removeItem(self, to_jid): 218 def removeItem(self, to_jid):
191 """Remove a contact from roster list 219 """Remove a contact from roster list
192 @param to_jid: a JID instance 220 @param to_jid: a JID instance
221 @return: Deferred
193 """ 222 """
194 xmppim.RosterClientProtocol.removeItem(self, to_jid) 223 return xmppim.RosterClientProtocol.removeItem(self, to_jid)
195 #TODO: check IQ result
196
197 #XXX: disabled (cf http://wokkel.ik.nu/ticket/56))
198 #def addItem(self, to):
199 #"""Add a contact to roster list"""
200 #xmppim.RosterClientProtocol.addItem(self, to)
201 #TODO: check IQ result"""
202
203 def updateItem(self, roster_item):
204 """
205 Update an item of the contact list.
206
207 @param roster_item: item to update
208 """
209 iq = compat.IQ(self.xmlstream, 'set')
210 iq.addElement((xmppim.NS_ROSTER, 'query'))
211 item = iq.query.addElement('item')
212 item['jid'] = roster_item.jid.userhost()
213 if roster_item.name:
214 item['name'] = roster_item.name
215 for group in roster_item.groups:
216 item.addElement('group', content=group)
217 return iq.send()
218 224
219 def getAttributes(self, item): 225 def getAttributes(self, item):
220 """Return dictionary of attributes as used in bridge from a RosterItem 226 """Return dictionary of attributes as used in bridge from a RosterItem
221 @param item: RosterItem 227 @param item: RosterItem
222 @return: dictionary of attributes""" 228 @return: dictionary of attributes"""
226 } 232 }
227 if item.name: 233 if item.name:
228 item_attr['name'] = item.name 234 item_attr['name'] = item.name
229 return item_attr 235 return item_attr
230 236
231 def onRosterSet(self, item): 237 def setReceived(self, request):
232 """Called when a new/update roster item is received""" 238 #TODO: implement roster versioning (cf RFC 6121 ยง2.6)
233 #TODO: send a signal to frontends 239 item = request.item
234 if not item.subscriptionTo and not item.subscriptionFrom and not item.ask: 240 self._jids[item.entity] = item
235 #XXX: current behaviour: we don't want contact in our roster list
236 # if there is no presence subscription
237 # may change in the future
238 self.removeItem(item.jid)
239 return
240 log.debug(_("New contact in roster list: %s") % item.jid.full())
241 if not item.subscriptionTo:
242 if not item.subscriptionFrom:
243 log.info(_("There's no subscription between you and [%s]!") % item.jid.full())
244 else:
245 log.info(_("You are not subscribed to [%s]!") % item.jid.full())
246 if not item.subscriptionFrom:
247 log.info(_("[%s] is not subscribed to you!") % item.jid.full())
248 #self.host.memory.addContact(item.jid, item_attr, item.groups, self.parent.profile)
249
250 bare_jid = item.jid.userhostJID()
251 self._jids[bare_jid] = item
252 for group in item.groups:
253 self._groups.setdefault(group, set()).add(bare_jid)
254 self.host.bridge.newContact(item.jid.full(), self.getAttributes(item), item.groups, self.parent.profile) 241 self.host.bridge.newContact(item.jid.full(), self.getAttributes(item), item.groups, self.parent.profile)
255 242
256 def onRosterRemove(self, entity): 243 def removeReceived(self, request):
257 """Called when a roster removal event is received""" 244 entity = request.item.entity
258 print _("removing %s from roster list") % entity.full() 245 print _("removing %s from roster list") % entity.full()
259 bare_jid = entity.userhostJID()
260 246
261 # we first remove item from local cache (self._groups and self._jids) 247 # we first remove item from local cache (self._groups and self._jids)
262 try: 248 try:
263 item = self._jids.pop(bare_jid) 249 item = self._jids.pop(entity)
264 except KeyError: 250 except KeyError:
265 log.warning("Received a roster remove event for an item not in cache") 251 log.error("Received a roster remove event for an item not in cache ({})".format(entity))
266 return 252 return
267 for group in item.groups: 253 for group in item.groups:
268 try: 254 try:
269 jids_set = self._groups[group] 255 jids_set = self._groups[group]
270 jids_set.remove(bare_jid) 256 jids_set.remove(entity)
271 if not jids_set: 257 if not jids_set:
272 del self._groups[group] 258 del self._groups[group]
273 except KeyError: 259 except KeyError:
274 log.warning("there is not cache for the group [%(groups)s] of the removed roster item [%(jid)s]" % 260 log.warning("there is not cache for the group [%(groups)s] of the removed roster item [%(jid)s]" %
275 {"group": group, "jid": bare_jid}) 261 {"group": group, "jid": entity})
276 262
277 # then we send the bridge signal 263 # then we send the bridge signal
278 self.host.bridge.contactDeleted(entity.userhost(), self.parent.profile) 264 self.host.bridge.contactDeleted(entity.full(), self.parent.profile)
279 265
280 def getGroups(self): 266 def getGroups(self):
281 """Return a list of groups""" 267 """Return a list of groups"""
282 return self._groups.keys() 268 return self._groups.keys()
283 269
284 def getItem(self, jid): 270 def getItem(self, jid):
285 """Return RosterItem for a given jid 271 """Return RosterItem for a given jid
272
286 @param jid: jid of the contact 273 @param jid: jid of the contact
287 @return: RosterItem or None if contact is not in cache""" 274 @return: RosterItem or None if contact is not in cache
288 return self._jids.get(jid.userhostJID(), None) 275 """
289 276 return self._jids.get(jid, None)
290 def getBareJids(self): 277
291 """Return all bare jids (as unicode) of the roster""" 278 def getJids(self):
279 """Return all jids of the roster"""
292 return self._jids.keys() 280 return self._jids.keys()
293 281
294 def isJidInRoster(self, entity_jid): 282 def isJidInRoster(self, entity_jid):
295 """Return True if jid is in roster""" 283 """Return True if jid is in roster"""
296 return entity_jid.userhostJID() in self._jids 284 return entity_jid in self._jids
297 285
298 def getItems(self): 286 def getItems(self):
299 """Return all items of the roster""" 287 """Return all items of the roster"""
300 return self._jids.values() 288 return self._jids.values()
301 289
316 if not self.host.trigger.point("Presence send", self.parent, obj): 304 if not self.host.trigger.point("Presence send", self.parent, obj):
317 return 305 return
318 super(SatPresenceProtocol, self).send(obj) 306 super(SatPresenceProtocol, self).send(obj)
319 307
320 def availableReceived(self, entity, show=None, statuses=None, priority=0): 308 def availableReceived(self, entity, show=None, statuses=None, priority=0):
321 log.debug(_("presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)") % {'entity': entity, 'show': show, 'statuses': statuses, 'priority': priority}) 309 log.debug(_("presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)") % {'entity': entity, C.PRESENCE_SHOW: show, C.PRESENCE_STATUSES: statuses, C.PRESENCE_PRIORITY: priority})
322 310
323 if not statuses: 311 if not statuses:
324 statuses = {} 312 statuses = {}
325 313
326 if None in statuses: # we only want string keys 314 if None in statuses: # we only want string keys
339 self.host.bridge.presenceUpdate(entity.full(), show or "", 327 self.host.bridge.presenceUpdate(entity.full(), show or "",
340 int(priority), statuses, 328 int(priority), statuses,
341 self.parent.profile) 329 self.parent.profile)
342 330
343 def unavailableReceived(self, entity, statuses=None): 331 def unavailableReceived(self, entity, statuses=None):
344 log.debug(_("presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)") % {'entity': entity, 'statuses': statuses}) 332 log.debug(_("presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)") % {'entity': entity, C.PRESENCE_STATUSES: statuses})
345 333
346 if not statuses: 334 if not statuses:
347 statuses = {} 335 statuses = {}
348 336
349 if None in statuses: # we only want string keys 337 if None in statuses: # we only want string keys