changeset 608:ea27925ef2a8 frontends_multi_profiles

merges souliane changes
author Goffi <goffi@goffi.org>
date Mon, 09 Feb 2015 21:58:49 +0100
parents 537649f6a2d0 (diff) c22b47d63fe2 (current diff)
children ec77c2bc18d3
files src/browser/sat_browser/base_widget.py src/browser/sat_browser/contact_list.py src/server/server.py
diffstat 10 files changed, 114 insertions(+), 57 deletions(-) [+]
line wrap: on
line diff
--- a/src/browser/libervia_main.py	Sat Feb 07 20:35:45 2015 +0100
+++ b/src/browser/libervia_main.py	Mon Feb 09 21:58:49 2015 +0100
@@ -51,6 +51,7 @@
 from sat_browser import notification
 
 from sat_browser.constants import Const as C
+import os.path
 
 
 try:
@@ -98,7 +99,6 @@
         self.cached_params = {}
 
         #FIXME: to be removed (managed with cache and in quick_frontend
-        self.avatars_cache = {}  # keep track of jid's avatar hash (key=jid, value=file)
         #FIXME: microblog cache should be managed directly in blog module
         self.mblog_cache = []  # used to keep our own blog entries in memory, to show them in new mblog panel
 
@@ -197,26 +197,21 @@
 
     # FIXME: must not call _entityDataUpdatedCb by itself
     #        should not get VCard, backend plugin must be fixed too
-    # def getAvatar(self, jid_str):
-    #     """Return avatar of a jid if in cache, else ask for it.
+    def getAvatarURL(self, jid_):
+        """Return avatar of a jid if in cache, else ask for it.
 
-    #     @param jid_str (str): JID of the contact
-    #     @return: the URL to the avatar (str)
-    #     """
-    #     def dataReceived(result):
-    #         if 'avatar' in result:
-    #             self._entityDataUpdatedCb(jid_str, 'avatar', result['avatar'])
-    #         else:
-    #             self.bridge.call("getCard", None, jid_str)
-
-    #     def avatarError(error_data):
-    #         # The jid is maybe not in our roster, we ask for the VCard
-    #         self.bridge.call("getCard", None, jid_str)
-
-    #     if jid_str not in self.avatars_cache:
-    #         self.bridge.call('getEntityData', (dataReceived, avatarError), jid_str, ['avatar'])
-    #         self.avatars_cache[jid_str] = C.DEFAULT_AVATAR
-    #     return self.avatars_cache[jid_str]
+        @param jid_ (jid.JID): JID of the contact
+        @return: the URL to the avatar (str)
+        """
+        assert isinstance(jid_, jid.JID)
+        avatar_hash = self.contact_lists[C.PROF_KEY_NONE].getCache(jid_, 'avatar')
+        if avatar_hash is None:
+            # we have no value for avatar_hash, so we request the vcard
+            self.bridge.getCard(unicode(jid_), profile=C.PROF_KEY_NONE)
+        if not avatar_hash:
+            return C.DEFAULT_AVATAR_URL
+        ret = os.path.join(C.AVATARS_DIR, avatar_hash)
+        return ret
 
     def registerWidget(self, wid):
         log.debug("Registering %s" % wid.getDebugName())
@@ -348,8 +343,8 @@
         # for cat, name in C.CACHED_PARAMS:
         #     self.bridge.call('asyncGetParamA', param_cb(cat, name, count), name, cat)
 
-    def profilePlugged(self, profile):
-        #we fill the panels already here
+    def profilePlugged(self, dummy):
+        # we fill the panels already here
         for widget in self.widgets.getWidgets(blog.MicroblogPanel):
             if widget.accept_all():
                 self.bridge.getMassiveLastMblogs('ALL', [], 10, profile=C.PROF_KEY_NONE, callback=widget.massiveInsert)
@@ -424,7 +419,7 @@
         """
         if data is None:
             data = {}
-        self.bridge.call('launchAction', (self._actionCb, self._actionEb), callback_id, data)
+        self.bridge.launchAction(callback_id, data, profile=C.PROF_KEY_NONE, callback=self._actionCb, errback=self._actionEb)
 
     def _getContactsCB(self, contacts_data):
         for contact_ in contacts_data:
@@ -800,6 +795,7 @@
         self.contact_panel.updateContact(contact_jid, attributes, groups)
 
     def _entityDataUpdatedCb(self, entity_jid_s, key, value):
+        raise Exception # FIXME should not be here
         if key == "avatar":
             avatar = '/' + C.AVATARS_DIR + value
             self.avatars_cache[entity_jid_s] = avatar
--- a/src/browser/sat_browser/base_widget.py	Sat Feb 07 20:35:45 2015 +0100
+++ b/src/browser/sat_browser/base_widget.py	Mon Feb 09 21:58:49 2015 +0100
@@ -215,13 +215,12 @@
             menu_styles.update(styles)
         base_menu.GenericMenuBar.__init__(self, host, vertical=vertical, styles=menu_styles)
 
-        # FIXME
-        # if hasattr(parent, 'addMenus'):
-        #     # regroup all the dynamic menu categories in a sub-menu
-        #     sub_menu = WidgetSubMenuBar(host, vertical=True)
-        #     parent.addMenus(sub_menu)
-        #     if len(sub_menu.getCategories()) > 0:
-        #         self.addCategory('', '', 'plugins', sub_menu)
+        if hasattr(parent, 'addMenus'):
+            # regroup all the dynamic menu categories in a sub-menu
+            sub_menu = WidgetSubMenuBar(host, vertical=True)
+            parent.addMenus(sub_menu)
+            if len(sub_menu.getCategories()) > 0:
+                self.addCategory('', '', 'plugins', sub_menu)
 
     @classmethod
     def getCategoryHTML(cls, menu_name_i18n, type_):
--- a/src/browser/sat_browser/blog.py	Sat Feb 07 20:35:45 2015 +0100
+++ b/src/browser/sat_browser/blog.py	Mon Feb 09 21:58:49 2015 +0100
@@ -47,11 +47,14 @@
 import richtext
 from constants import Const as C
 from sat_frontends.quick_frontend import quick_widgets
+from sat_frontends.tools import jid
 
 # TODO: at some point we should decide which behaviors to keep and remove these two constants
 TOGGLE_EDITION_USE_ICON = False  # set to True to use an icon inside the "toggle syntax" button
 NEW_MESSAGE_USE_BUTTON = False  # set to True to display the "New message" button instead of an empty entry
 
+unicode = str # XXX: pyjamas doesn't manage unicode
+
 
 class MicroblogItem():
     # XXX: should be moved in a separated module
@@ -64,7 +67,7 @@
         self.title_xhtml = data.get('title_xhtml', '')
         self.content = data.get('content', '')
         self.content_xhtml = data.get('content_xhtml', '')
-        self.author = data['author']
+        self.author = jid.JID(data['author'])
         self.updated = float(data.get('updated', 0))  # XXX: int doesn't work here
         self.published = float(data.get('published', self.updated))  # XXX: int doesn't work here
         self.service = data.get('service', '')
@@ -104,8 +107,8 @@
 
         entry_avatar = SimplePanel()
         entry_avatar.setStyleName('mb_entry_avatar')
-        # FIXME
-        self.avatar = Image(C.DEFAULT_AVATAR) # self._blog_panel.host.getAvatar(self.author))
+        assert isinstance(self.author, jid.JID) # FIXME: temporary
+        self.avatar = Image(self._blog_panel.host.getAvatarURL(self.author)) # FIXME: self.author should be initially a jid.JID
         entry_avatar.add(self.avatar)
         self.panel.add(entry_avatar)
 
@@ -139,7 +142,7 @@
         self.header.setHTML("""<div class='mb_entry_header'>
                                    <span class='mb_entry_author'>%(author)s</span> on
                                    <span class='mb_entry_timestamp'>%(published)s</span>%(updated)s
-                               </div>""" % {'author': html_tools.html_sanitize(self.author),
+                               </div>""" % {'author': html_tools.html_sanitize(unicode(self.author)),
                                             'published': datetime.fromtimestamp(self.published),
                                             'updated': update_text if self.published != self.updated else ''
                                             }
@@ -275,7 +278,7 @@
         data = {'id': str(time()),
                 'new': True,
                 'type': 'comment',
-                'author': self._blog_panel.host.whoami.bare,
+                'author': unicode(self._blog_panel.host.whoami.bare),
                 'service': self.comments_service,
                 'node': self.comments_node
                 }
@@ -371,6 +374,7 @@
         self.vpanel = VerticalPanel()
         self.vpanel.setStyleName('microblogPanel')
         self.setWidget(self.vpanel)
+        host.addListerner('avatar', self.onAvatarUpdate)
 
     def __str__(self):
         return u"Blog Widget [target: {}, profile: {}]".format(self.target, self.profile)
@@ -379,6 +383,21 @@
     def target(self):
         return tuple(self.accepted_groups)
 
+    def onDelete(self):
+        quick_widgets.QuickWidget.onDelete(self)
+        self.host.removeListener('avatar', self.onAvatarUpdate)
+
+    def onAvatarUpdate(self, jid_, hash_, profile):
+        """Called on avatar update events
+
+        @param jid_: jid of the entity with updated avatar
+        @param hash_: hash of the avatar
+        @param profile: should be C.PROF_KEY_NONE
+        """
+        whoami = self.host.profiles[self.profile].whoami.bare
+        if self.isJidAccepted(jid_) or jid_.bare  == whoami.bare:
+            self.updateValue('avatar', jid_, hash_)
+
     def refresh(self):
         """Refresh the display of this widget. If the unibox is disabled,
         display the 'New message' button or an empty bubble on top of the panel"""
@@ -391,7 +410,7 @@
                     self.new_button.setVisible(False)
                 data = {'id': str(time()),
                         'new': True,
-                        'author': self.host.whoami.bare,
+                        'author': unicode(self.host.whoami.bare),
                         }
                 entry = self.addEntry(data)
                 entry.edit(True)
@@ -541,10 +560,11 @@
     def addEntryIfAccepted(self, sender, groups, mblog_entry):
         """Check if an entry can go in MicroblogPanel and add to it
 
-        @param sender: jid of the entry sender
+        @param sender(jid.JID): jid of the entry sender
         @param groups: groups which can receive this entry
         @param mblog_entry: panels.MicroblogItem instance
         """
+        assert isinstance(sender, jid.JID) # FIXME temporary
         if (mblog_entry.type == "comment"
             or self.isJidAccepted(sender)
             or (groups == None and sender == self.host.profiles[self.profile].whoami.bare)
@@ -660,19 +680,22 @@
         if self.selected_entry:
             self.selected_entry.removeStyleName('selected_entry')
         if entry:
-            log.debug("microblog entry selected (author=%s)" % entry.author)
+            log.debug("microblog entry selected (author=%s)" % unicode(entry.author))
             entry.addStyleName('selected_entry')
         self.selected_entry = entry
 
-    def updateValue(self, type_, jid, value):
+    def updateValue(self, type_, jid_, value):
         """Update a jid value in entries
+
         @param type_: one of 'avatar', 'nick'
-        @param jid: jid concerned
+        @param jid_(jid.JID): jid concerned
         @param value: new value"""
+        assert isinstance(jid_, jid.JID) # FIXME: temporary
         def updateVPanel(vpanel):
+            avatar_url = self.host.getAvatarURL(jid_)
             for child in vpanel.children:
-                if isinstance(child, MicroblogEntry) and child.author == jid:
-                    child.updateAvatar(value)
+                if isinstance(child, MicroblogEntry) and child.author == jid_:
+                    child.updateAvatar(avatar_url)
                 elif isinstance(child, VerticalPanel):
                     updateVPanel(child)
         if type_ == 'avatar':
@@ -700,6 +723,7 @@
         @param jid_(jid.JID): jid to check
         @return: True if the jid is accepted
         """
+        assert isinstance(jid_, jid.JID) # FIXME temporary
         if self.accept_all():
             return True
         for group in self._accepted_groups:
--- a/src/browser/sat_browser/constants.py	Sat Feb 07 20:35:45 2015 +0100
+++ b/src/browser/sat_browser/constants.py	Mon Feb 09 21:58:49 2015 +0100
@@ -35,6 +35,4 @@
                      ('General', C.SHOW_EMPTY_GROUPS),
                      ]
 
-    # Empty avatar
-    EMPTY_AVATAR = "/media/misc/empty_avatar"
     WEB_PANEL_DEFAULT_URL = "http://salut-a-toi.org"
--- a/src/browser/sat_browser/contact_list.py	Sat Feb 07 20:35:45 2015 +0100
+++ b/src/browser/sat_browser/contact_list.py	Mon Feb 09 21:58:49 2015 +0100
@@ -113,7 +113,7 @@
 
     @classmethod
     def getCategoryHTML(cls, menu_name_i18n, type_):
-        return '<img src="%s"/>' % C.DEFAULT_AVATAR
+        return '<img src="%s"/>' % C.DEFAULT_AVATAR_URL
 
     def setUrl(self, url):
         """Set the URL of the contact avatar."""
@@ -128,7 +128,7 @@
         self.jid = jid_
         self.label = ContactLabel(jid_, name)
         self.avatar = ContactMenuBar(self, host) if handle_menu else Image()
-        # self.updateAvatar(host.getAvatar(jid_)) # FIXME
+        self.updateAvatar(host.getAvatarURL(jid_))
         self.add(self.avatar)
         self.add(self.label)
         if click_listener:
@@ -335,11 +335,16 @@
         self.add(self.scroll_panel)
         self.setStyleName('contactList')
         Window.addWindowResizeListener(self)
+        host.addListerner('avatar', self.onAvatarUpdate)
 
     @property
     def profile(self):
         return C.PROF_KEY_NONE
 
+    def onDelete(self):
+        QuickContactList.onDelete(self)
+        self.host.removeListener('avatar', self.onAvatarUpdate)
+
     def update(self):
         ### GROUPS ###
         _keys = self._groups.keys()
@@ -535,6 +540,15 @@
         """
         self._contacts_panel.updateAvatar(jid_s, url)
 
+    def onAvatarUpdate(self, jid_, hash_, profile):
+        """Called on avatar update events
+
+        @param jid_: jid of the entity with updated avatar
+        @param hash_: hash of the avatar
+        @param profile: should be C.PROF_KEY_NONE
+        """
+        self._contacts_panel.updateAvatar(jid_, self.host.getAvatarURL(jid_))
+
     def hasVisibleMembers(self, group):
         """Tell if the given group actually has visible members
 
--- a/src/browser/sat_browser/json.py	Sat Feb 07 20:35:45 2015 +0100
+++ b/src/browser/sat_browser/json.py	Mon Feb 09 21:58:49 2015 +0100
@@ -185,7 +185,7 @@
                          "getWaitingSub", "subscription", "delContact", "updateContact", "getCard",
                          "getEntityData", "getParamsUI", "asyncGetParamA", "setParam", "launchAction",
                          "disconnect", "chatStateComposing", "getNewAccountDomain", "confirmationAnswer",
-                         "syntaxConvert", "getAccountDialogUI", "getLastResource", "getWaitingConf",
+                         "syntaxConvert", "getAccountDialogUI", "getLastResource", "getWaitingConf", "getEntitiesData",
                         ])
     def __call__(self, *args, **kwargs):
         return LiberviaJsonProxy.__call__(self, *args, **kwargs)
--- a/src/common/constants.py	Sat Feb 07 20:35:45 2015 +0100
+++ b/src/common/constants.py	Mon Feb 09 21:58:49 2015 +0100
@@ -19,6 +19,7 @@
 
 from sat.core.i18n import D_
 from sat_frontends import constants
+import os.path
 
 
 class Const(constants.Const):
@@ -46,8 +47,12 @@
     UPLOAD_KO = 'UPLOAD KO'
     UNKNOWN_ERROR = 'UNMANAGED FAULT STRING (%s)'
 
-    # PATHS
+    # directories
     AVATARS_DIR = "avatars/"
+    MEDIA_DIR = "media/"
 
-    # Default avatar
-    DEFAULT_AVATAR = "/media/misc/default_avatar.png"
+    # avatars
+    DEFAULT_AVATAR_FILE = "default_avatar.png"
+    DEFAULT_AVATAR_URL = os.path.join(MEDIA_DIR, "misc", DEFAULT_AVATAR_FILE)
+    EMPTY_AVATAR_FILE = "empty_avatar"
+    EMPTY_AVATAR_URL = os.path.join(MEDIA_DIR, "misc", EMPTY_AVATAR_FILE)
--- a/src/server/blog.py	Sat Feb 07 20:35:45 2015 +0100
+++ b/src/server/blog.py	Mon Feb 09 21:58:49 2015 +0100
@@ -96,14 +96,14 @@
         except IndexError:  # impossible to guess the entity
             return
         log.debug(_("Using default avatar for entity %s") % entity_jid_s)
-        self.avatars_cache[entity_jid_s] = C.DEFAULT_AVATAR
-        self.waiting_deferreds[entity_jid_s][1].callback(C.DEFAULT_AVATAR)
+        self.avatars_cache[entity_jid_s] = C.DEFAULT_AVATAR_URL
+        self.waiting_deferreds[entity_jid_s][1].callback(C.DEFAULT_AVATAR_URL)
         del self.waiting_deferreds[entity_jid_s]
 
     def getAvatar(self, profile):
         """Get the avatar of the given profile
 
-        @param profile (str):
+        @param profile(unicode): %(doc_profile)s
         @return: deferred avatar path, relative to the server's root
         """
         jid_s = (profile + '@' + self.host.bridge.getNewAccountDomain()).lower()
--- a/src/server/constants.py	Sat Feb 07 20:35:45 2015 +0100
+++ b/src/server/constants.py	Mon Feb 09 21:58:49 2015 +0100
@@ -44,3 +44,6 @@
 
     # Security limit for Libervia server_side
     SERVER_SECURITY_LIMIT = constants.Const.NO_SECURITY_LIMIT
+
+    # keys for cache values we can get from browser
+    ALLOWED_ENTITY_DATA = {'avatar', 'nick'}
--- a/src/server/server.py	Sat Feb 07 20:35:45 2015 +0100
+++ b/src/server/server.py	Mon Feb 09 21:58:49 2015 +0100
@@ -34,6 +34,7 @@
 log = getLogger(__name__)
 from sat_frontends.bridge.DBus import DBusBridgeFrontend, BridgeExceptionNoService, const_TIMEOUT as BRIDGE_TIMEOUT
 from sat.core.i18n import _, D_
+from sat.core import exceptions
 from sat.tools.xml_tools import paramsXML2XMLUI
 
 import re
@@ -463,23 +464,40 @@
             return
         self.sat_host.bridge.radiocolLaunch(invited, room_jid, profile)
 
+    def jsonrpc_getEntitiesData(self, jids, keys):
+        """Get cached data for several entities at once
+
+        @param jids: list jids from who we wants data, or empty list for all jids in cache
+        @param keys: name of data we want (list)
+        @return: requested data"""
+        if not C.ALLOWED_ENTITY_DATA.issuperset(keys):
+            raise exceptions.PermissionError("Trying to access unallowed data (hack attempt ?)")
+        profile = ISATSession(self.session).profile
+        try:
+            return self.sat_host.bridge.getEntitiesData(jids, keys, profile)
+        except Exception as e:
+            raise Failure(jsonrpclib.Fault(C.ERRNUM_BRIDGE_ERRBACK, unicode(e)))
+
     def jsonrpc_getEntityData(self, jid, keys):
-        """Get cached data for an entit
+        """Get cached data for an entity
+
         @param jid: jid of contact from who we want data
         @param keys: name of data we want (list)
         @return: requested data"""
+        if not C.ALLOWED_ENTITY_DATA.issuperset(keys):
+            raise exceptions.PermissionError("Trying to access unallowed data (hack attempt ?)")
         profile = ISATSession(self.session).profile
         try:
             return self.sat_host.bridge.getEntityData(jid, keys, profile)
         except Exception as e:
             raise Failure(jsonrpclib.Fault(C.ERRNUM_BRIDGE_ERRBACK, unicode(e)))
 
-    def jsonrpc_getCard(self, jid):
+    def jsonrpc_getCard(self, jid_):
         """Get VCard for entiry
-        @param jid: jid of contact from who we want data
+        @param jid_: jid of contact from who we want data
         @return: id to retrieve the profile"""
         profile = ISATSession(self.session).profile
-        return self.sat_host.bridge.getCard(jid, profile)
+        return self.sat_host.bridge.getCard(jid_, profile)
 
     def jsonrpc_getAccountDialogUI(self):
         """Get the dialog for managing user account