comparison src/core/xmpp.py @ 1367:f71a0fc26886

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