# HG changeset patch # User Goffi # Date 1423515529 -3600 # Node ID ea27925ef2a8ab4e4e1e44af4fa79971d76c74a3 # Parent 537649f6a2d09260489eb8dfd90d8262de0da000# Parent c22b47d63fe29e02df60f1a7d21e0174e52b0fe9 merges souliane changes diff -r c22b47d63fe2 -r ea27925ef2a8 src/browser/libervia_main.py --- 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 diff -r c22b47d63fe2 -r ea27925ef2a8 src/browser/sat_browser/base_widget.py --- 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_): diff -r c22b47d63fe2 -r ea27925ef2a8 src/browser/sat_browser/blog.py --- 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("""
on %(updated)s -
""" % {'author': html_tools.html_sanitize(self.author), + """ % {'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: diff -r c22b47d63fe2 -r ea27925ef2a8 src/browser/sat_browser/constants.py --- 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" diff -r c22b47d63fe2 -r ea27925ef2a8 src/browser/sat_browser/contact_list.py --- 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 '' % C.DEFAULT_AVATAR + return '' % 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 diff -r c22b47d63fe2 -r ea27925ef2a8 src/browser/sat_browser/json.py --- 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) diff -r c22b47d63fe2 -r ea27925ef2a8 src/common/constants.py --- 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) diff -r c22b47d63fe2 -r ea27925ef2a8 src/server/blog.py --- 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() diff -r c22b47d63fe2 -r ea27925ef2a8 src/server/constants.py --- 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'} diff -r c22b47d63fe2 -r ea27925ef2a8 src/server/server.py --- 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