changeset 494:385cd2169eb5

core: memory bug fix
author Goffi <goffi@goffi.org>
date Fri, 17 Aug 2012 03:20:40 +0200
parents b7c4bb2c0668 (diff) 655695f85e5b (current diff)
children a726b234d3bf
files src/memory/memory.py
diffstat 12 files changed, 213 insertions(+), 95 deletions(-) [+]
line wrap: on
line diff
--- a/frontends/src/bridge/DBus.py	Sun Aug 12 15:49:08 2012 +0200
+++ b/frontends/src/bridge/DBus.py	Fri Aug 17 03:20:40 2012 +0200
@@ -150,7 +150,7 @@
     def registerNewAccount(self, login, password, email, host, port=5222):
         return unicode(self.db_core_iface.registerNewAccount(login, password, email, host, port))
 
-    def sendMessage(self, to_jid, message, subject='', mess_type="chat", profile_key="@DEFAULT@"):
+    def sendMessage(self, to_jid, message, subject='', mess_type="auto", profile_key="@DEFAULT@"):
         return self.db_core_iface.sendMessage(to_jid, message, subject, mess_type, profile_key)
 
     def setParam(self, name, value, category, profile_key="@DEFAULT@"):
--- a/frontends/src/jp/jp	Sun Aug 12 15:49:08 2012 +0200
+++ b/frontends/src/jp/jp	Fri Aug 17 03:20:40 2012 +0200
@@ -60,6 +60,7 @@
 import tarfile
 import tempfile
 import shutil
+import unicodedata
 try:
     from progressbar import ProgressBar, Percentage, Bar, ETA, FileTransferSpeed
 except ImportError, e:
@@ -155,7 +156,7 @@
             self.bridge.asyncConnect(self.profile, self.connected, cantConnect)
             return
         elif not self.bridge.isConnected(self.profile):
-            error(_(u"SàT is not conneted, please connect before using jp"))
+            error(_(u"Profile [%(profile)s] is not connected, please connect it before using jp, or use --connect option") % { "profile": self.profile })
             exit(1)
 
         self.connected()
@@ -163,15 +164,23 @@
     def check_jids(self):
         """Check jids validity, transform roster name to corresponding jids"""
         names2jid = {}
+        nodes2jid = {}
 
         for contact in self.bridge.getContacts(self.options.profile):
             _jid, attr, groups = contact
             if attr.has_key("name"):
                 names2jid[attr["name"].lower()] = _jid
+            nodes2jid[JID(_jid).node.lower()] = _jid
 
         def expandJid(jid):
             _jid = jid.lower()
-            return unicode(names2jid[_jid] if _jid in names2jid else jid)
+            if _jid in names2jid:
+                expanded = names2jid[_jid]
+            elif _jid in nodes2jid:
+                expanded = nodes2jid[_jid]
+            else:
+                expanded = jid
+            return unicode(expanded)
 
         def check(jid):
             if not jid.is_valid:
@@ -190,6 +199,17 @@
         except AttributeError:
             pass
 
+    def clean_ustr(self, ustr):
+        """Clean unicode string
+        remove special characters from unicode string"""
+        def valid_chars(unicode_source):
+            for char in unicode_source:
+                if unicodedata.category(char) == 'Cc' and char!='\n':
+                    continue
+                yield char
+        return ''.join(valid_chars(ustr))
+
+
     def send_stdin(self):
         """Send incomming data on stdin to jabber contact"""
         header = "\n" if self.options.new_line else ""
@@ -198,12 +218,12 @@
             if header:
                 self.bridge.sendMessage(self.dest_jid, header, profile_key=self.profile)
             while (True):
-                line = sys.stdin.readline()
+                line = self.clean_ustr(sys.stdin.readline().decode('utf-8','ignore'))
                 if not line:
                     break
                 self.bridge.sendMessage(self.dest_jid, line.replace("\n",""), profile_key=self.profile)
         else:
-            self.bridge.sendMessage(self.dest_jid, header + "".join(sys.stdin.readlines()), profile_key=self.profile)
+            self.bridge.sendMessage(self.dest_jid, header + self.clean_ustr(u"".join([stream.decode('utf-8','ignore') for stream in sys.stdin.readlines()])), profile_key=self.profile)
 
 
     def pipe_out(self):
--- a/frontends/src/wix/main_window.py	Sun Aug 12 15:49:08 2012 +0200
+++ b/frontends/src/wix/main_window.py	Fri Aug 17 03:20:40 2012 +0200
@@ -442,9 +442,9 @@
             dlg.ShowModal()
             dlg.Destroy()
             return
-        id = self.bridge.getCard(target.short, profile_key=self.profile) 
-        self.current_action_ids.add(id)
-        self.current_action_ids_cb[id] = self.onProfileReceived
+        _id = self.bridge.getCard(target.short, profile_key=self.profile) 
+        self.current_action_ids.add(_id)
+        self.current_action_ids_cb[_id] = self.onProfileReceived
    
     def onProfileReceived(self, data):
         """Called when a profile is received"""
@@ -467,9 +467,9 @@
 
     def onFindGateways(self, e):
         debug(_("Find Gateways request"))
-        id = self.bridge.findGateways(self.profiles[self.profile]['whoami'].domain, self.profile)
-        self.current_action_ids.add(id)
-        self.current_action_ids_cb[id] = self.onGatewaysFound
+        _id = self.bridge.findGateways(self.profiles[self.profile]['whoami'].domain, self.profile)
+        self.current_action_ids.add(_id)
+        self.current_action_ids_cb[_id] = self.onGatewaysFound
 
     def onGatewaysFound(self, data):
         """Called when SàT has found the server gateways"""
--- a/src/bridge/bridge_constructor/bridge_template.ini	Sun Aug 12 15:49:08 2012 +0200
+++ b/src/bridge/bridge_constructor/bridge_template.ini	Fri Aug 17 03:20:40 2012 +0200
@@ -309,13 +309,13 @@
 sig_in=sssss
 sig_out=
 param_2_default=''
-param_3_default="chat"
+param_3_default="auto"
 param_4_default="@DEFAULT@"
 doc=Send a message
 doc_param_0=to_jid: JID of the recipient
 doc_param_1=message: body of the message
 doc_param_2=subject: Subject of the message ('' if no subject)
-doc_param_3=mess_type: Type of the message (cf RFC 3921 #2.1.1)
+doc_param_3=mess_type: Type of the message (cf RFC 3921 #2.1.1) or "auto" for automatic type detection
 doc_param_4=%(doc_profile_key)s
 
 [setPresence]
--- a/src/core/exceptions.py	Sun Aug 12 15:49:08 2012 +0200
+++ b/src/core/exceptions.py	Fri Aug 17 03:20:40 2012 +0200
@@ -21,3 +21,12 @@
 
 class ProfileUnknownError(Exception):
     pass
+
+class ProfileNotInCacheError(Exception):
+    pass
+
+class ConnectedProfileError(Exception):
+    pass
+
+class UnknownEntityError(Exception):
+    pass
--- a/src/core/sat_main.py	Sun Aug 12 15:49:08 2012 +0200
+++ b/src/core/sat_main.py	Fri Aug 17 03:20:40 2012 +0200
@@ -26,6 +26,7 @@
 
 from twisted.application import service
 from twisted.internet import defer
+from twisted.internet.defer import inlineCallbacks, returnValue
 
 from twisted.words.protocols.jabber import jid, xmlstream
 from twisted.words.xish import domish
@@ -43,7 +44,7 @@
 import os.path
 
 from sat.core import xmpp
-from sat.core.exceptions import ProfileUnknownError
+from sat.core.exceptions import ProfileUnknownError, UnknownEntityError
 from sat.memory.memory import Memory
 from sat.tools.xml_tools import tupleList2dataForm
 from sat.tools.misc import TriggerManager
@@ -256,6 +257,7 @@
 
             return current.getConnectionDeferred()
 
+        self.memory.startProfileSession(profile)
         return self.memory.loadIndividualParams(profile).addCallback(afterMemoryInit)
 
     def disconnect(self, profile_key):
@@ -290,7 +292,7 @@
             del self.profiles[profile]
         except KeyError:
             error(_("Trying to remove reference to a client not referenced"))
-        self.memory.purgeProfile(profile)
+        self.memory.purgeProfileSession(profile)
 
     def startService(self):
         info("Salut à toi ô mon frère !")
@@ -444,24 +446,53 @@
 
     ## jabber methods ##
     
-    def sendMessage(self, to, msg, subject=None, type='chat', profile_key='@DEFAULT@'):
+    def sendMessage(self, to, msg, subject=None, mess_type='auto', profile_key='@DEFAULT@'):
         #FIXME: check validity of recipient
         profile = self.memory.getProfileName(profile_key)
         assert(profile)
-        current_jid = self.profiles[profile].jid
-        debug(_("Sending jabber message to %s..."), to)
+        client = self.profiles[profile]
+        current_jid = client.jid
+        mess_data = { #we put data in a dict, so trigger methods can change them 
+                      "to": jid.JID(to),
+                      "message": msg,
+                      "subject": subject,
+                      "type": mess_type
+                    }
+        if not self.trigger.point("sendMessage", mess_data, profile):
+            return
+        if mess_data["type"] == "auto":
+            #XXX: if the type has not been changed by a trigger method,
+            #     we try to guess it
+            if mess_data["subject"]:
+                mess_data["type"] = 'normal'
+            elif not mess_data["to"].resource: #if to JID has a resource, the type is not 'groupchat'
+                #we may have a groupchat message, we check if the we know this jid
+                try:
+                    entity_type = self.memory.getEntityData(mess_data["to"], ['type'], profile)["type"]
+                    #FIXME: should entity_type manage ressources ?
+                except (UnknownEntityError, KeyError):
+                    entity_type = "contact"
+                
+                if entity_type == "chatroom":
+                    mess_data["type"] = 'groupchat'
+                else:
+                    mess_data["type"] = 'chat'
+            else:
+                mess_data["type"] == 'chat'
+            mess_data["type"] == "chat" if mess_data["subject"] else "normal"
+        debug(_("Sending jabber message of type [%(type)s] to %(to)s...") % {"type": mess_data["type"], "to": to})
         message = domish.Element(('jabber:client','message'))
-        message["to"] = jid.JID(to).full()
+        message["to"] = mess_data["to"].full()
         message["from"] = current_jid.full()
-        message["type"] = type
-        if subject:
+        message["type"] = mess_data["type"]
+        if mess_data["subject"]:
             message.addElement("subject", "jabber:client", subject)
         message.addElement("body", "jabber:client", msg)
-        self.profiles[profile].xmlstream.send(message)
-        if type!="groupchat":
+        client.xmlstream.send(message)
+        if mess_data["type"]!="groupchat":
             self.memory.addToHistory(current_jid, jid.JID(to), unicode(msg), profile=profile) #we don't add groupchat message to history, as we get them back
-                                                                                              #and they will we added then
-            self.bridge.newMessage(message['from'], unicode(msg), mess_type=type, to_jid=message['to'], profile=profile) #We send back the message, so all clients are aware of it
+                                                                                              #and they will be added then
+            self.bridge.newMessage(message['from'], unicode(msg), mess_type=mess_data["type"], to_jid=message['to'], profile=profile) #We send back the message, so all clients are aware of it
 
 
     def setPresence(self, to="", show="", priority = 0, statuses={}, profile_key='@DEFAULT@'):
--- a/src/core/xmpp.py	Sun Aug 12 15:49:08 2012 +0200
+++ b/src/core/xmpp.py	Fri Aug 17 03:20:40 2012 +0200
@@ -227,6 +227,10 @@
     def getBareJids(self):
         """Return all bare jids (as unicode) of the roster"""
         return self._jids.keys()
+
+    def isJidInRoster(self, entity_jid):
+        """Return True if jid is in roster"""
+        return entity_jid.userhost() in self._jids
     
     def getItems(self):
         """Return all items of the roster"""
@@ -249,7 +253,7 @@
             statuses["default"] = statuses[None]
             del statuses[None]
 
-        self.host.memory.addPresenceStatus(entity, show or "",
+        self.host.memory.setPresenceStatus(entity, show or "",
                 int(priority), statuses, self.parent.profile)
 
         #now it's time to notify frontends
@@ -265,7 +269,7 @@
         if statuses.has_key(None):   #we only want string keys
             statuses["default"] = statuses[None]
             del statuses[None]
-        self.host.memory.addPresenceStatus(entity, "unavailable", 0, statuses, self.parent.profile)
+        self.host.memory.setPresenceStatus(entity, "unavailable", 0, statuses, self.parent.profile)
 
         #now it's time to notify frontends
         self.host.bridge.presenceUpdate(entity.full(), "unavailable", 0, statuses, self.parent.profile)
--- a/src/memory/memory.py	Sun Aug 12 15:49:08 2012 +0200
+++ b/src/memory/memory.py	Fri Aug 17 03:20:40 2012 +0200
@@ -31,15 +31,11 @@
 from sat.core.default_config import default_config
 from sat.memory.sqlite import SqliteStorage
 from sat.memory.persistent import PersistentDict
+from sat.core import exceptions
 
 SAVEFILE_PARAM_XML="/param" #xml parameters template
 SAVEFILE_DATABASE="/sat.db"
 
-class ProfileNotInCacheError(Exception):
-    pass
-
-class ConnectedProfileError(Exception):
-    pass
 
 class Params():
     """This class manage parameters with xml"""
@@ -153,7 +149,7 @@
             return True
         if self.host.isConnected(profile):
             error(_("Trying to delete a connected profile"))
-            raise ConnectedProfileError
+            raise exceptions.ConnectedProfileError
         self.storage.deleteProfile(profile)
         return False
 
@@ -293,12 +289,12 @@
         try:
             value = self.__getParam(profile, category, name)
             return defer.succeed(value if value!=None else default)
-        except ProfileNotInCacheError:
+        except exceptions.ProfileNotInCacheError:
             #We have to ask data to the storage manager
             d = self.storage.getIndParam(category, name, profile)
             return d.addCallback(lambda value: value if value!=None else default)
 
-    def __getParam(self, profile, category, name, type='individual', cache=None):
+    def __getParam(self, profile, category, name, _type='individual', cache=None):
         """Return the param, or None if it doesn't exist
         @param profile: the profile name (not profile key, i.e. name and not something like @DEFAULT@)
         @param category: param category
@@ -307,16 +303,16 @@
         @param cache: temporary cache, to use when profile is not logged
         @return: param value or None if it doesn't exist
         """
-        if type == 'general':
+        if _type == 'general':
             if self.params_gen.has_key((category, name)):
                 return self.params_gen[(category, name)]
             return None  #This general param has the default value
-        assert (type == 'individual')
+        assert (_type == 'individual')
         if self.params.has_key(profile):
             cache = self.params[profile] # if profile is in main cache, we use it,
                                          # ignoring the temporary cache
         elif cache == None: #else we use the temporary cache if it exists, or raise an exception
-            raise ProfileNotInCacheError
+            raise exceptions.ProfileNotInCacheError
         if not cache.has_key((category, name)):
             return None
         return cache[(category, name)]
@@ -419,19 +415,19 @@
         d = self.__constructProfileXml(profile)
         return d.addCallback(returnCategoryXml)
 
-    def __getParamNode(self, name, category, type="@ALL@"): #FIXME: is type useful ?
+    def __getParamNode(self, name, category, _type="@ALL@"): #FIXME: is _type useful ?
         """Return a node from the param_xml
         @param name: name of the node
         @param category: category of the node
-        @type: keyword for search:
+        @_type: keyword for search:
                                     @ALL@ search everywhere
                                     @GENERAL@ only search in general type
                                     @INDIVIDUAL@ only search in individual type
         @return: a tuple with the node type and the the node, or None if not found"""
 
         for type_node in self.dom.documentElement.childNodes:
-            if ( ((type == "@ALL@" or type == "@GENERAL@") and type_node.nodeName == 'general') 
-            or ( (type == "@ALL@" or type == "@INDIVIDUAL@") and type_node.nodeName == 'individual') ):
+            if ( ((_type == "@ALL@" or _type == "@GENERAL@") and type_node.nodeName == 'general') 
+            or ( (_type == "@ALL@" or _type == "@INDIVIDUAL@") and type_node.nodeName == 'individual') ):
                 for node in type_node.getElementsByTagName('category'):
                     if node.getAttribute("name") == category:
                         params = node.getElementsByTagName("param")
@@ -474,8 +470,8 @@
         assert (node[0] == 'individual')
         assert (profile_key != "@NONE@")
         
-        type = node[1].getAttribute("type")
-        if type=="button":
+        _type = node[1].getAttribute("type")
+        if _type=="button":
             print "clique",node.toxml()
         else:
             if self.host.isConnected(profile): #key can not exists if profile is not connected
@@ -490,8 +486,8 @@
         info (_("Memory manager init"))
         self.initialized = defer.Deferred() 
         self.host = host
-        self.presenceStatus={}
-        self.lastResource={} #tmp, will be refactored with bdd integration
+        self.entitiesCache={} #XXX: keep presence/last resource/other data in cache
+                              #     /!\ an entity is not necessarily in roster
         self.subscriptions={}
         self.server_features={} #used to store discovery's informations
         self.server_identities={}
@@ -561,10 +557,22 @@
         @param profile: %(doc_profile)s"""
         return self.params.loadIndParams(profile)
 
-    def purgeProfile(self, profile):
+    def startProfileSession(self, profile):
+        """"Iniatialise session for a profile
+        @param profile: %(doc_profile)s"""
+        info(_("[%s] Profile session started" % profile))
+        self.entitiesCache[profile] = {}
+    
+    def purgeProfileSession(self, profile):
         """Delete cache of data of profile
         @param profile: %(doc_profile)s"""
+        info(_("[%s] Profile session purge" % profile))
         self.params.purgeProfile(profile)
+        try:
+            del self.entitiesCache[profile]
+        except KeyError:
+            error(_("Trying to purge roster status cache for a profile not in memory: [%s]") % profile)
+
 
     def save(self):
         """Save parameters and all memory things to file/db"""
@@ -617,29 +625,29 @@
             self.server_features[profile] = []
         self.server_features[profile].append(feature)
     
-    def addServerIdentity(self, category, type, entity, profile):
+    def addServerIdentity(self, category, _type, entity, profile):
         """Add an identity discovered from server
         @param feature: string of the feature
         @param profile: which profile is using this server ?"""
         if not self.server_identities.has_key(profile):
             self.server_identities[profile] = {}
-        if not self.server_identities[profile].has_key((category, type)):
-            self.server_identities[profile][(category, type)]=set()
-        self.server_identities[profile][(category, type)].add(entity)
+        if not self.server_identities[profile].has_key((category, _type)):
+            self.server_identities[profile][(category, _type)]=set()
+        self.server_identities[profile][(category, _type)].add(entity)
 
-    def getServerServiceEntities(self, category, type, profile):
+    def getServerServiceEntities(self, category, _type, profile):
         """Return all available entities for a service"""
         if self.server_identities.has_key(profile):
-            return self.server_identities[profile].get((category, type), set())
+            return self.server_identities[profile].get((category, _type), set())
         else:
             return None
 
-    def getServerServiceEntity(self, category, type, profile):
+    def getServerServiceEntity(self, category, _type, profile):
         """Helper method to get first available entity for a service"""
-        entities = self.getServerServiceEntities(category, type, profile)
+        entities = self.getServerServiceEntities(category, _type, profile)
         if entities == None:
             warning(_("Entities (%(category)s/%(type)s) not available, maybe they haven't been asked to server yet ?") % {"category":category,
-                                                                                                                          "type":type})
+                                                                                                                          "type":_type})
             return None
         else:
             return list(entities)[0] if entities else None
@@ -658,45 +666,102 @@
         @param contact: contact jid (unicode)
         @param profile_key: %(doc_profile_key)s"""
         profile = self.getProfileName(profile_key)
-        if not profile:
-            error(_('Asking contacts for a non-existant profile'))
+        if not profile or not self.host.isConnected(profile):
+            error(_('Asking contacts for a non-existant or not connected profile'))
+            return ""
+        entity = jid.JID(contact).userhost()
+        if not entity in self.entitiesCache[profile]:
+            info(_("Entity not in cache"))
             return ""
         try:
-            return self.lastResource[profile][jid.JID(contact).userhost()]
-        except:
+            return self.entitiesCache[profile][entity]["last_resource"]
+        except KeyError:
             return ""
     
-    def addPresenceStatus(self, contact_jid, show, priority, statuses, profile_key):
+    def getPresenceStatus(self, profile_key):
+        profile = self.getProfileName(profile_key)
+        if not profile:
+            error(_('Asking contacts for a non-existant profile'))
+            return {}
+        entities_presence = {}
+        for entity in self.entitiesCache[profile]:
+            # if entity exists, "presence" key must exist
+            entities_presence[entity] = self.entitiesCache[profile][entity]["presence"]
+
+        debug ("Memory getPresenceStatus (%s)", entities_presence)
+        return entities_presence
+
+    def setPresenceStatus(self, entity_jid, show, priority, statuses, profile_key):
+        """Change the presence status of an entity"""
         profile = self.getProfileName(profile_key)
         if not profile:
             error(_('Trying to add presence status to a non-existant profile'))
             return
-        if not self.presenceStatus.has_key(profile):
-            self.presenceStatus[profile] = {}
-        if not self.lastResource.has_key(profile):
-            self.lastResource[profile] = {}
-        if not self.presenceStatus[profile].has_key(contact_jid.userhost()):
-            self.presenceStatus[profile][contact_jid.userhost()] = {}
-        resource = jid.parse(contact_jid.full())[2] or ''
+        entity_data = self.entitiesCache[profile].setdefault(entity_jid.userhost(),{})
+        resource = jid.parse(entity_jid.full())[2] or ''
         if resource:
-            self.lastResource[profile][contact_jid.userhost()] = resource
+            entity_data["last_resource"] = resource
+        if not "last_resource" in entity_data:
+            entity_data["last_resource"] = ''
+
+        entity_data.setdefault("presence",{})[resource] = (show, priority, statuses)
+
+    def updateEntityData(self, entity_jid, key, value, profile_key):
+        """Set a misc data for an entity
+        @param entity_jid: JID of the entity
+        @param key: key to set (eg: "type")
+        @param value: value for this key (eg: "chatroom")
+        @param profile_key: %(doc_profile_key)s
+        """
+        profile = self.getProfileName(profile_key)
+        if not profile:
+            raise exceptions.UnknownProfileError(_('Trying to get entity data for a non-existant profile'))
+        if not profile in self.entitiesCache:
+            raise exceptions.ProfileNotInCacheError
+        entity_data = self.entitiesCache[profile].setdefault(entity_jid.userhost(),{})
+        entity_data[key] = value
 
-        self.presenceStatus[profile][contact_jid.userhost()][resource] = (show, priority, statuses)
+    def getEntityData(self, entity_jid, keys_list, profile_key):
+        """Get a list of cached values for entity
+        @param entity_jid: JID of the entity
+        @param keys_list: list of keys to get, empty list for everything
+        @param profile_key: %(doc_profile_key)s
+        @return: dict withs values for each key in keys_list.
+                 if there is no value of a given key, resultint dict will
+                 have nothing with that key nether
+        @raise: exceptions.UnknownEntityError if entity is not in cache
+                exceptions.ProfileNotInCacheError if profile is not in cache 
+        """
+        profile = self.getProfileName(profile_key)
+        if not profile:
+            raise exceptions.UnknownProfileError(_('Trying to get entity data for a non-existant profile'))
+        if not profile in self.entitiesCache:
+            raise exceptions.ProfileNotInCacheError
+        if not entity_jid.userhost() in self.entitiesCache[profile]:
+            raise exceptions.UnknownEntityError
+        entity_data = self.entitiesCache[profile][entity_jid.userhost()]
+        if not keys_list:
+            return entity_data
+        ret = {}
+        for key in keys_list:
+            if key in entity_data:
+                ret[key] = entity_data[key]
+        return ret
 
-    def addWaitingSub(self, type, contact_jid, profile_key):
+    def addWaitingSub(self, _type, entity_jid, profile_key):
         """Called when a subcription request is received"""
         profile = self.getProfileName(profile_key)
         assert(profile)
         if not self.subscriptions.has_key(profile):
             self.subscriptions[profile] = {}
-        self.subscriptions[profile][contact_jid] = type
+        self.subscriptions[profile][entity_jid] = _type
     
-    def delWaitingSub(self, contact_jid, profile_key):
+    def delWaitingSub(self, entity_jid, profile_key):
         """Called when a subcription request is finished"""
         profile = self.getProfileName(profile_key)
         assert(profile)
-        if self.subscriptions.has_key(profile) and self.subscriptions[profile].has_key(contact_jid):
-            del self.subscriptions[profile][contact_jid]
+        if self.subscriptions.has_key(profile) and self.subscriptions[profile].has_key(entity_jid):
+            del self.subscriptions[profile][entity_jid]
     
     def getWaitingSub(self, profile_key):
         """Called to get a list of currently waiting subscription requests"""
@@ -709,16 +774,6 @@
         
         return self.subscriptions[profile]
 
-    def getPresenceStatus(self, profile_key):
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error(_('Asking contacts for a non-existant profile'))
-            return {}
-        if not self.presenceStatus.has_key(profile):
-            self.presenceStatus[profile] = {}
-        debug ("Memory getPresenceStatus (%s)", self.presenceStatus[profile])
-        return self.presenceStatus[profile]
-
     def getParamA(self, name, category, attr="value", profile_key='@DEFAULT@'):
         return self.params.getParamA(name, category, attr, profile_key)
     
--- a/src/plugins/plugin_xep_0045.py	Sun Aug 12 15:49:08 2012 +0200
+++ b/src/plugins/plugin_xep_0045.py	Fri Aug 17 03:20:40 2012 +0200
@@ -85,6 +85,7 @@
             self.host.bridge.roomJoined(room.roomJID.userhost(), [user.nick for user in room.roster.values()], room.nick, profile)
 
         room_jid_s = room.roomJID.userhost()
+        self.host.memory.updateEntityData(room.roomJID, "type", "chatroom", profile)
         self.clients[profile].joined_rooms[room_jid_s] = room
         if room.locked:
             #FIXME: the current behaviour is to create an instant room
--- a/src/plugins/plugin_xep_0054.py	Sun Aug 12 15:49:08 2012 +0200
+++ b/src/plugins/plugin_xep_0054.py	Fri Aug 17 03:20:40 2012 +0200
@@ -22,6 +22,7 @@
 from logging import debug, info, error
 from twisted.words.xish import domish
 from twisted.internet import protocol, defer, threads, reactor
+from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.words.protocols.jabber import client, jid, xmlstream
 from twisted.words.protocols.jabber import error as jab_error
 from twisted.words.protocols.jabber.xmlstream import IQ
@@ -128,7 +129,7 @@
                     debug(_("file [%s] already in cache") % hash)
                 return hash
 
-    @defer.deferredGenerator
+    @inlineCallbacks
     def vCard2Dict(self, vcard, target, profile):
         """Convert a VCard to a dict, and save binaries"""
         debug (_("parsing vcard"))
@@ -148,10 +149,7 @@
             elif elem.name == 'BDAY':
                 dictionary['birthday'] = unicode(elem) 
             elif elem.name == 'PHOTO':
-                d2 = defer.waitForDeferred(
-                            threads.deferToThread(self.save_photo, elem))
-                yield d2
-                dictionary["avatar"] = d2.getResult()
+                dictionary["avatar"] = yield threads.deferToThread(self.save_photo, elem)
                 if not dictionary["avatar"]:  #can happen in case of e.g. empty photo elem
                     del dictionary['avatar']
                 else:
@@ -159,7 +157,7 @@
             else:
                 info (_('FIXME: [%s] VCard tag is not managed yet') % elem.name)
 
-        yield dictionary
+        returnValue(dictionary)
 
     def vcard_ok(self, answer, profile):
         """Called after the first get IQ"""
@@ -170,12 +168,12 @@
             d.addCallback(lambda data: self.host.bridge.actionResult("RESULT", answer['id'], data))
         else:
             error (_("FIXME: vCard not found as first child element"))
-            self.host.bridge.actionResult("SUPPRESS", answer['id'], {}) #FIXME: maybe an error message would be best
+            self.host.bridge.actionResult("SUPPRESS", answer['id'], {}) #FIXME: maybe an error message would be better
 
     def vcard_err(self, failure):
         """Called when something is wrong with registration"""
         error (_("Can't find VCard of %s") % failure.value.stanza['from'])
-        self.host.bridge.actionResult("SUPPRESS", failure.value.stanza['id'], {}) #FIXME: maybe an error message would be best
+        self.host.bridge.actionResult("SUPPRESS", failure.value.stanza['id'], {}) #FIXME: maybe an error message would be better
   
     def getCard(self, target, profile_key='@DEFAULT@'):
         """Ask server for VCard
--- a/src/plugins/plugin_xep_0277.py	Sun Aug 12 15:49:08 2012 +0200
+++ b/src/plugins/plugin_xep_0277.py	Fri Aug 17 03:20:40 2012 +0200
@@ -92,7 +92,7 @@
                 microblog_data['author'] = _entry.authors[0].name.text
             microblog_data['timestamp'] = str(int(_entry.updated.tf))
             microblog_data['id'] = item['id']
-        except AttributeError, KeyError:
+        except (AttributeError, KeyError):
             error(_('Error while parsing atom entry for microblogging event'))
             return {}
 
--- a/src/test/helpers.py	Sun Aug 12 15:49:08 2012 +0200
+++ b/src/test/helpers.py	Fri Aug 17 03:20:40 2012 +0200
@@ -66,7 +66,7 @@
     def addContact(self, contact_jid, attributes, groups, profile_key='@DEFAULT@'):
         pass
     
-    def addPresenceStatus(self, contact_jid, show, priority, statuses, profile_key='@DEFAULT@'):
+    def setPresenceStatus(self, contact_jid, show, priority, statuses, profile_key='@DEFAULT@'):
         pass
     
     def addWaitingSub(self, type, contact_jid, profile_key):