changeset 504:65ecbb473cbb

core, quick frontend, plugin xep-0054, bridge: use of memory's entities data for vcard: - bridge: new bridge method getEntityData and signal entityDataUpdated - core: entityDataUpdated signal sent on new string data - quick frontend: fixed avatars/vcard infos, fixed _replace in quick_contact_list - plugin xep-0054: dropped updatedValue signal, use entities data instead
author Goffi <goffi@goffi.org>
date Wed, 26 Sep 2012 01:23:56 +0200
parents 10119c2a9d33
children 2402668b5d05
files frontends/src/bridge/DBus.py frontends/src/quick_frontend/quick_app.py frontends/src/quick_frontend/quick_contact_list.py src/bridge/DBus.py src/bridge/bridge_constructor/bridge_template.ini src/core/sat_main.py src/memory/memory.py src/plugins/plugin_xep_0054.py
diffstat 8 files changed, 76 insertions(+), 80 deletions(-) [+]
line wrap: on
line diff
--- a/frontends/src/bridge/DBus.py	Wed Sep 26 00:38:41 2012 +0200
+++ b/frontends/src/bridge/DBus.py	Wed Sep 26 01:23:56 2012 +0200
@@ -99,6 +99,9 @@
     def getContactsFromGroup(self, group, profile_key="@DEFAULT@"):
         return self.db_core_iface.getContactsFromGroup(group, profile_key)
 
+    def getEntityData(self, jid, keys, profile):
+        return self.db_core_iface.getEntityData(jid, keys, profile)
+
     def getHistory(self, from_jid, to_jid, limit, between=True, callback=None, errback=None):
         return self.db_core_iface.getHistory(from_jid, to_jid, limit, between, reply_handler=callback, error_handler=lambda err:errback(err._dbus_error_name[len(const_ERROR_PREFIX)+1:]))
 
--- a/frontends/src/quick_frontend/quick_app.py	Wed Sep 26 00:38:41 2012 +0200
+++ b/frontends/src/quick_frontend/quick_app.py	Wed Sep 26 01:23:56 2012 +0200
@@ -52,7 +52,7 @@
         self.bridge.register("subscribe", self.subscribe)
         self.bridge.register("paramUpdate", self.paramUpdate)
         self.bridge.register("contactDeleted", self.contactDeleted)
-        self.bridge.register("updatedValue", self.updatedValue)
+        self.bridge.register("entityDataUpdated", self.entityDataUpdated)
         self.bridge.register("askConfirmation", self.askConfirmation)
         self.bridge.register("actionResult", self.actionResult)
         self.bridge.register("actionResultExt", self.actionResult)
@@ -165,6 +165,10 @@
                     priority = presences[contact][res][1]
                     statuses = presences[contact][res][2]
                     self.presenceUpdate(jabber_id, show, priority, statuses, profile)
+                    data = self.bridge.getEntityData(contact, ['avatar','nick'], profile)
+                    for key in ('avatar', 'nick'):
+                        if key in data:
+                            self.entityDataUpdated(contact, key, data[key], profile)
 
             #The waiting subscription requests
             waitingSub = self.bridge.getWaitingSub(profile)
@@ -476,21 +480,19 @@
         except KeyError:
             pass
 
-    def updatedValue(self, name, data, profile):
-        #FIXME: to be removed
+    def entityDataUpdated(self, jid_str, key, value, profile):
         if not self.check_profile(profile):
             return
-        if name == "card_nick":
-            target = JID(data['jid'])
-            if target in self.contact_list:
-                #self.CM.update(target, 'nick', unicode(data['nick']))
-                self.contact_list._replace(target)
-        elif name == "card_avatar":
-            target = JID(data['jid'])
-            if target in self.contact_list:
-                filename = self.bridge.getAvatarFile(data['avatar'])
-                #self.CM.update(target, 'avatar', filename)
-                self.contact_list._replace(target)
+        jid = JID(jid_str)
+        if key == "nick":
+            if jid in self.contact_list:
+                self.contact_list.setCache(jid, 'nick', value)
+                self.contact_list._replace(jid)
+        elif key == "avatar":
+            if jid in self.contact_list:
+                filename = self.bridge.getAvatarFile(value)
+                self.contact_list.setCache(jid, 'avatar', filename)
+                self.contact_list._replace(jid)
 
     def askConfirmation(self, type, id, data):
         raise NotImplementedError
--- a/frontends/src/quick_frontend/quick_contact_list.py	Wed Sep 26 00:38:41 2012 +0200
+++ b/frontends/src/quick_frontend/quick_contact_list.py	Wed Sep 26 01:23:56 2012 +0200
@@ -54,7 +54,7 @@
         raise NotImplementedError
     
     def _replace(self, jid, groups=None, attributes=None):
-        if 'name' in attributes:
+        if attributes and 'name' in attributes:
             self.setCache(jid, 'name', attributes['name'])
         self.replace(jid, groups, attributes)
     
@@ -65,7 +65,7 @@
     def remove(self, jid):
         """remove a contact from the list"""
         raise NotImplementedError
-    
+
     def add(self, jid, param_groups=None):
         """add a contact to the list"""
         raise NotImplementedError
--- a/src/bridge/DBus.py	Wed Sep 26 00:38:41 2012 +0200
+++ b/src/bridge/DBus.py	Wed Sep 26 01:23:56 2012 +0200
@@ -141,6 +141,11 @@
 
     @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
                          signature='ssss')
+    def entityDataUpdated(self, jid, name, value, profile):
+        pass
+
+    @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
+                         signature='ssss')
     def newAlert(self, message, title, alert_type, profile):
         pass
 
@@ -169,11 +174,6 @@
     def subscribe(self, sub_type, entity_jid, profile):
         pass
 
-    @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
-                         signature='sa{ss}s')
-    def updatedValue(self, name, value, profile):
-        pass
-
 
     ### methods ###    
     
@@ -262,6 +262,12 @@
         return self._callback("getContactsFromGroup", unicode(group), unicode(profile_key))
 
     @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
+                         in_signature='sass', out_signature='a{ss}',
+                         async_callbacks=None)
+    def getEntityData(self, jid, keys, profile):
+        return self._callback("getEntityData", unicode(jid), keys, unicode(profile))
+
+    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
                          in_signature='ssib', out_signature='a(dsss)',
                          async_callbacks=('callback', 'errback'))
     def getHistory(self, from_jid, to_jid, limit, between=True, callback=None, errback=None):
@@ -518,6 +524,9 @@
     def disconnected(self, profile):
         self.dbus_bridge.disconnected(profile)
 
+    def entityDataUpdated(self, jid, name, value, profile):
+        self.dbus_bridge.entityDataUpdated(jid, name, value, profile)
+
     def newAlert(self, message, title, alert_type, profile):
         self.dbus_bridge.newAlert(message, title, alert_type, profile)
 
@@ -536,9 +545,6 @@
     def subscribe(self, sub_type, entity_jid, profile):
         self.dbus_bridge.subscribe(sub_type, entity_jid, profile)
 
-    def updatedValue(self, name, value, profile):
-        self.dbus_bridge.updatedValue(name, value, profile)
-
 
     def register(self, name, callback):
         debug("registering DBus bridge method [%s]", name)
--- a/src/bridge/bridge_constructor/bridge_template.ini	Wed Sep 26 00:38:41 2012 +0200
+++ b/src/bridge/bridge_constructor/bridge_template.ini	Wed Sep 26 01:23:56 2012 +0200
@@ -136,14 +136,15 @@
 doc_param_1=id: Id of the action
 doc_param_2=data: answer_type specific data
 
-[updatedValue]
+[entityDataUpdated]
 type=signal
 category=core
-sig_in=sa{ss}s
-doc=A value has been updated
-doc_param_0=name: Name of the updated value
-doc_param_1=value: New value
-doc_param_2=%(doc_profile)s
+sig_in=ssss
+doc=An entity's data has been updated
+doc_param_0=jid: entity's bare jid
+doc_param_1=name: Name of the updated value
+doc_param_2=value: New value
+doc_param_3=%(doc_profile)s
 
 ;methods
 
@@ -171,6 +172,18 @@
 sig_out=as
 doc=Get all profiles
 
+[getEntityData]
+type=method
+category=core
+sig_in=sass
+sig_out=a{ss}
+doc=Get data for an entity
+doc_param_0=jid: entity's bare jid
+doc_param_1=keys: list of keys to get
+doc_param_2=%(doc_profile)s
+doc_return=dictionary of asked key,
+ if key doesn't exist, the resulting dictionary will neither have the key
+
 [createProfile]
 deprecated=
 type=method
--- a/src/core/sat_main.py	Wed Sep 26 00:38:41 2012 +0200
+++ b/src/core/sat_main.py	Wed Sep 26 01:23:56 2012 +0200
@@ -118,6 +118,7 @@
         self.bridge.register("getVersion", lambda: self.get_const('client_version'))
         self.bridge.register("getProfileName", self.memory.getProfileName)
         self.bridge.register("getProfilesList", self.memory.getProfilesList)
+        self.bridge.register("getEntityData", lambda _jid, keys, profile: self.memory.getEntityData(jid.JID(_jid),keys, profile))
         self.bridge.register("createProfile", self.memory.createProfile)
         self.bridge.register("asyncCreateProfile", self.memory.asyncCreateProfile)
         self.bridge.register("deleteProfile", self.memory.deleteProfile)
@@ -279,7 +280,7 @@
         if not client:
             raise ProfileUnknownError(_('Asking contacts for a non-existant profile'))
         ret = []
-        for item in client.roster.getItems(): #we get all item for client's roster
+        for item in client.roster.getItems(): #we get all items for client's roster
             #and convert them to expected format
             attr = client.roster.getAttributes(item)
             ret.append([item.jid.userhost(), attr, item.groups])
--- a/src/memory/memory.py	Wed Sep 26 00:38:41 2012 +0200
+++ b/src/memory/memory.py	Wed Sep 26 01:23:56 2012 +0200
@@ -720,6 +720,8 @@
             raise exceptions.ProfileNotInCacheError
         entity_data = self.entitiesCache[profile].setdefault(entity_jid.userhost(),{})
         entity_data[key] = value
+        if isinstance(value,basestring):
+            self.host.bridge.entityDataUpdated(entity_jid.userhost(), key, value, profile)
 
     def getEntityData(self, entity_jid, keys_list, profile_key):
         """Get a list of cached values for entity
@@ -727,7 +729,7 @@
         @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
+                 if there is no value of a given key, resulting 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 
--- a/src/plugins/plugin_xep_0054.py	Wed Sep 26 00:38:41 2012 +0200
+++ b/src/plugins/plugin_xep_0054.py	Wed Sep 26 01:23:56 2012 +0200
@@ -20,11 +20,9 @@
 """
 
 from logging import debug, info, error
-from twisted.words.xish import domish
-from twisted.internet import protocol, defer, threads, reactor
+from twisted.internet import threads
 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 import jid
 from twisted.words.protocols.jabber.xmlstream import IQ
 import os.path
 
@@ -34,8 +32,7 @@
 
 from base64 import b64decode
 from hashlib import sha1
-from time import sleep
-from sat.memory.persistent import PersistentBinaryDict
+from sat.core import exceptions
 
 try:
     from twisted.words.protocols.xmlstream import XMPPHandler
@@ -64,6 +61,9 @@
 }
 
 class XEP_0054():
+    #TODO: - check that nickname is ok
+    #      - refactor the code/better use of Wokkel
+    #      - get missing values
 
     def __init__(self, host):
         info(_("Plugin XEP_0054 initialization"))
@@ -73,7 +73,6 @@
             os.makedirs(self.avatar_path)
         host.bridge.addMethod("getCard", ".plugin", in_sign='ss', out_sign='s', method=self.getCard)
         host.bridge.addMethod("getAvatarFile", ".plugin", in_sign='s', out_sign='s', method=self.getAvatarFile)
-        host.bridge.addMethod("getCardCache", ".plugin", in_sign='ss', out_sign='a{ss}', method=self.getCardCache)
 
     def getHandler(self, profile):
         return XEP_0054_handler(self)  
@@ -81,35 +80,29 @@
     def update_cache(self, jid, name, value, profile):
         """update cache value
         - save value in memory in case of change
-        - send updatedValue signal if the value is new or updated
         @param jid: jid of the owner of the vcard
         @param name: name of the item which changed
         @param value: new value of the item
         @param profile: profile which received the update
         """
-        client = self.host.getClient(profile)
-        if not jid.userhost() in client._vcard_cache:
-            client._vcard_cache[jid.userhost()] = {}
-        
-        cache = client._vcard_cache[jid.userhost()]
-        old_value = cache[name] if name in cache else None
-        if not old_value or value != old_value:
-            cache[name] = value
-            client._vcard_cache.force(jid.userhost()) #we force saving of data to storage
-            self.host.bridge.updatedValue('card_'+name, {'jid':jid.userhost(), name:value}, profile)
-
+        try:
+            cached = self.host.memory.getEntityData(jid, [name], profile)
+        except exceptions.UnknownEntityError:
+            cached = {}
+        if not name in cached or cached[name] != value:
+            self.host.memory.updateEntityData(jid, name, value, profile)
+            
     def get_cache(self, jid, name, profile):
         """return cached value for jid
         @param jid: target contact
         @param name: name of the value ('nick' or 'avatar')
         @param profile: %(doc_profile)s
         @return: wanted value or None"""
-        client = self.host.getClient(profile)
         try:
-            return client._vcard_cache[jid.userhost()][name]
-        except KeyError:
+            data = self.host.memory.getEntityData(jid, [name], profile)
+        except exceptions.UnknownEntityError:
             return None
-
+        return data.get(name)
 
     def save_photo(self, photo_xml):
         """Parse a <PHOTO> elem and save the picture"""
@@ -134,7 +127,6 @@
         """Convert a VCard to a dict, and save binaries"""
         debug (_("parsing vcard"))
         dictionary = {}
-        d = defer.Deferred()
         
         for elem in vcard.elements():
             if elem.name == 'FN':
@@ -189,7 +181,7 @@
         reg_request=IQ(xmlstream,'get')
         reg_request["from"]=current_jid.full()
         reg_request["to"] = to_jid.userhost()
-        query=reg_request.addElement('vCard', NS_VCARD)
+        reg_request.addElement('vCard', NS_VCARD)
         reg_request.send(to_jid.userhost()).addCallbacks(self.vcard_ok, self.vcard_err, [profile])
         return reg_request["id"] 
 
@@ -204,27 +196,6 @@
             return ""
         return filename
 
-    def getCardCache(self, target, profile_key):
-        """Request for cached values of profile 
-        return the cached nickname and avatar if exists, else get VCard
-        @param target: target's jid
-        @param profile_key: %(doc_profile_key)s
-        """
-        profile = self.host.memory.getProfileName(profile_key)
-        if not profile:
-            error(_("Profile not found"))
-            return {}
-        to_jid = jid.JID(target)
-        result = {}
-        nick = self.get_cache(to_jid, 'nick', profile)
-        if nick:
-            result['nick'] = nick
-        avatar = self.get_cache(to_jid, 'avatar', profile)
-        if avatar:
-            result['avatar'] = avatar
-        return result
-
-
 
 class XEP_0054_handler(XMPPHandler):
     implements(iwokkel.IDisco)
@@ -234,8 +205,6 @@
         self.host = plugin_parent.host
 
     def connectionInitialized(self):
-        self.parent._vcard_cache = PersistentBinaryDict(NS_VCARD, self.parent.profile)
-        self.parent._vcard_cache.load()
         self.xmlstream.addObserver(VCARD_UPDATE, self.update)
     
     def getDiscoInfo(self, requestor, target, nodeIdentifier=''):