changeset 914:0c0551967bdf

server, browser: partial Libervia fix Libervia was broken following the refactorings. This commit partially fixes it : Libervia is starting, avatar, blog and message are working again, but not everything is restablished yet. following things have been fixed/changed: - new dependency: shortuuid - D-Bus bridge is working again - fixed naming in several bridge methods - register method changed to register_signal - fixed Chat widget, which was not working anymore since the refactoring - avatar now use avatarGet. Cache dir is accessible using a session specific uuid, to avoid cache leak (i.e. accessing cache of other profiles) - server: new uuid attribute in session data Browser code is not fully working yet, notably OTR and contact list are not fully fixed.
author Goffi <goffi@goffi.org>
date Sun, 26 Feb 2017 18:32:47 +0100
parents 58f611481e6d
children e9e9d9d893a8
files setup.py src/browser/libervia_main.py src/browser/sat_browser/chat.py src/browser/sat_browser/game_radiocol.py src/browser/sat_browser/json.py src/browser/sat_browser/plugin_sec_otr.py src/common/constants.py src/server/blog.py src/server/server.py
diffstat 9 files changed, 208 insertions(+), 165 deletions(-) [+]
line wrap: on
line diff
--- a/setup.py	Sun Aug 28 19:25:52 2016 +0200
+++ b/setup.py	Sun Feb 26 18:32:47 2017 +0100
@@ -302,6 +302,6 @@
                   for root, dirs, files in os.walk(C.THEMES_DIR)],
       scripts=[],
       zip_safe=False,
-      install_requires=['sat', 'twisted', 'txJSON-RPC<5', 'zope.interface', 'pyopenssl', 'jinja2'],
+      install_requires=['sat', 'twisted', 'txJSON-RPC<5', 'zope.interface', 'pyopenssl', 'jinja2', 'shortuuid'],
       cmdclass={'install': CustomInstall},
       )
--- a/src/browser/libervia_main.py	Sun Aug 28 19:25:52 2016 +0200
+++ b/src/browser/libervia_main.py	Sun Feb 26 18:32:47 2017 +0100
@@ -58,7 +58,6 @@
 assert web_widget # XXX: just here to avoid pyflakes warning
 
 from sat_browser.constants import Const as C
-import os.path
 
 
 try:
@@ -79,7 +78,8 @@
     def onModuleLoad(self):
         log.info("============ onModuleLoad ==============")
         self.bridge_signals = json.BridgeSignals(self)
-        QuickApp.__init__(self, json.BridgeCall, xmlui=xmlui)
+        QuickApp.__init__(self, json.BridgeCall, xmlui=xmlui, connect_bridge=False)
+        self.connectBridge()
         self._profile_plugged = False
         self.signals_cache[C.PROF_KEY_NONE] = []
         self.panel = main_panel.MainPanel(self)
@@ -93,7 +93,7 @@
         DOM.addEventPreview(self)
         self.importPlugins()
         self._register = json.RegisterCall()
-        self._register.call('getMenus', self.gotMenus)
+        self._register.call('menusGet', self.gotMenus)
         self._register.call('registerParams', None)
         self._register.call('getSessionMetadata', self._getSessionMetadataCB)
         self.initialised = False
@@ -170,7 +170,7 @@
         else:
             callback = handler
 
-        self.bridge_signals.register(functionName, callback, with_profile=with_profile)
+        self.bridge_signals.register_signal(functionName, callback, with_profile=with_profile)
 
     def importPlugins(self):
         self.plugins = {}
@@ -226,26 +226,16 @@
             event.preventDefault()
         return True
 
-    # FIXME: must not call _entityDataUpdatedCb by itself
-    #        should not get VCard, backend plugin must be fixed too
     def getAvatarURL(self, jid_):
         """Return avatar of a jid if in cache, else ask for it.
 
         @param jid_ (jid.JID): JID of the contact
         @return: the URL to the avatar (unicode)
         """
-        assert isinstance(jid_, jid.JID)
-        contact_list = self.contact_list  # pyjamas issue: need a temporary variable to call a property's method
-        avatar_hash = contact_list.getCache(jid_, 'avatar')
-        if avatar_hash is None:
-            # we set to empty string to avoid requesting vcard several times
-            contact_list.setCache(jid_, 'avatar', '')
-            # 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
+        return self.getAvatar(jid_) or self.getDefaultAvatar()
+
+    def getDefaultAvatar(self):
+        return C.DEFAULT_AVATAR_URL
 
     def registerWidget(self, wid):
         log.debug(u"Registering %s" % wid.getDebugName())
@@ -336,7 +326,7 @@
 
     def _isConnectedCB(self, connected):
         if not connected:
-            self._register.call('asyncConnect', lambda x: self.logged())
+            self._register.call('connect', lambda x: self.logged())
         else:
             self.logged()
 
--- a/src/browser/sat_browser/chat.py	Sun Aug 28 19:25:52 2016 +0200
+++ b/src/browser/sat_browser/chat.py	Sun Feb 26 18:32:47 2017 +0100
@@ -29,15 +29,12 @@
 from pyjamas.ui.AbsolutePanel import AbsolutePanel
 from pyjamas.ui.VerticalPanel import VerticalPanel
 from pyjamas.ui.HorizontalPanel import HorizontalPanel
-from pyjamas.ui.Label import Label
-from pyjamas.ui.HTML import HTML
 from pyjamas.ui.KeyboardListener import KEY_ENTER, KeyboardHandler
 from pyjamas.ui.HTMLPanel import HTMLPanel
 from pyjamas import DOM
 from pyjamas import Window
 
 from datetime import datetime
-from time import time
 
 import html_tools
 import libervia_widget
@@ -53,21 +50,46 @@
 unicode = str  # FIXME: pyjamas workaround
 
 
-class ChatText(HTMLPanel):
+class MessageWidget(HTMLPanel):
+
+    def __init__(self, mess_data):
+        """
+        @param mess_data(quick_chat.Message, None): message data
+            None: used only for non text widgets (e.g.: focus separator)
+        """
+        self.mess_data = mess_data
+        mess_data.widgets.add(self)
+        _msg_class = []
+        if mess_data.type == C.MESS_TYPE_INFO:
+            markup = "<span class='{msg_class}'>{msg}</span>"
 
-    def __init__(self, timestamp, nick, mymess, msg, extra):
-        xhtml = extra.get('xhtml')
-        _date = datetime.fromtimestamp(float(timestamp or time()))
-        _msg_class = ["chat_text_msg"]
-        if mymess:
-            _msg_class.append("chat_text_mymess")
-        HTMLPanel.__init__(self, "<span class='chat_text_timestamp'>%(timestamp)s</span> <span class='chat_text_nick'>%(nick)s</span> <span class='%(msg_class)s'>%(msg)s</span>" %
-                           {"timestamp": _date.strftime("%H:%M"),
-                            "nick": "[%s]" % html_tools.html_sanitize(nick),
-                            "msg_class": ' '.join(_msg_class),
-                            "msg": strings.addURLToText(html_tools.html_sanitize(msg)) if not xhtml else html_tools.inlineRoot(xhtml)}  # FIXME: images and external links must be removed according to preferences
-                           )
-        self.setStyleName('chatText')
+            if mess_data.extra.get('info_type') == 'me':
+                _msg_class.append('chatTextMe')
+            else:
+                _msg_class.append('chatTextInfo')
+            # FIXME: following code was in printInfo before refactoring
+            #        seems to be used only in radiocol
+            # elif type_ == 'link':
+            #     _wid = HTML(msg)
+            #     _wid.setStyleName('chatTextInfo-link')
+            #     if link_cb:
+            #         _wid.addClickListener(link_cb)
+        else:
+            markup = "<span class='chat_text_timestamp'>{timestamp}</span> <span class='chat_text_nick'>{nick}</span> <span class='{msg_class}'>{msg}</span>"
+            _msg_class.append("chat_text_msg")
+            if mess_data.own_mess:
+                _msg_class.append("chat_text_mymess")
+
+        xhtml = mess_data.main_message_xhtml
+        _date = datetime.fromtimestamp(float(mess_data.timestamp))
+        HTMLPanel.__init__(self, markup.format(
+                               timestamp = _date.strftime("%H:%M"),
+                               nick =  "[{}]".format(html_tools.html_sanitize(mess_data.nick)),
+                               msg_class = ' '.join(_msg_class),
+                               msg = strings.addURLToText(html_tools.html_sanitize(mess_data.main_message)) if not xhtml else html_tools.inlineRoot(xhtml)  # FIXME: images and external links must be removed according to preferences
+                           ))
+        if mess_data.type != C.MESS_TYPE_INFO:
+            self.setStyleName('chatText')
 
 
 class Chat(QuickChat, libervia_widget.LiberviaWidget, KeyboardHandler):
@@ -121,6 +143,7 @@
         self.message_box.onSelectedChange(self)
         self.message_box.addKeyboardListener(self)
         self.vpanel.add(self.message_box)
+        self.postInit()
 
     def onWindowResized(self, width=None, height=None):
         if self.type == C.CHAT_GROUP:
@@ -210,40 +233,27 @@
             self.setHeaderInfo(header_info)
         QuickChat.newMessage(self, from_jid, target, msg, type_, extra, profile)
 
-    def printInfo(self, msg, type_='normal', extra=None, link_cb=None):
-        """Print general info
-        @param msg: message to print
-        @param type_: one of:
-            "normal": general info like "toto has joined the room" (will be sanitized)
-            "link": general info that is clickable like "click here to join the main room" (no sanitize done)
-            "me": "/me" information like "/me clenches his fist" ==> "toto clenches his fist" (will stay on one line)
-        @param extra (dict): message data
-        @param link_cb: method to call when the info is clicked, ignored if type_ is not 'link'
+    def _onHistoryPrinted(self):
+        """Refresh or scroll down the focus after the history is printed"""
+        self.printMessages(clear=False)
+        super(Chat, self)._onHistoryPrinted()
+
+    def printMessages(self, clear=True):
+        """generate message widgets
+
+        @param clear(bool): clear message before printing if true
         """
-        QuickChat.printInfo(self, msg, type_, extra)
-        if extra is None:
-            extra = {}
-        if type_ == 'normal':
-            _wid = HTML(strings.addURLToText(html_tools.XHTML2Text(msg)))
-            _wid.setStyleName('chatTextInfo')
-        elif type_ == 'link':
-            _wid = HTML(msg)
-            _wid.setStyleName('chatTextInfo-link')
-            if link_cb:
-                _wid.addClickListener(link_cb)
-        elif type_ == 'me':
-            _wid = Label(msg)
-            _wid.setStyleName('chatTextMe')
-        else:
-            raise ValueError("Unknown printInfo type %s" % type_)
-        self.content.add(_wid)
-        self.content_scroll.scrollToBottom()
+        if clear:
+            # FIXME: clear is not handler
+            pass
+        for message in self.messages.itervalues():
+            self.appendMessage(message)
 
-    def printMessage(self, nick, my_message, message, timestamp, extra=None, profile=C.PROF_KEY_NONE):
-        QuickChat.printMessage(self, nick, my_message, message, timestamp, extra, profile)
-        if extra is None:
-            extra = {}
-        self.content.add(ChatText(timestamp, nick, my_message, message, extra))
+    def createMessage(self, message):
+        self.appendMessage(message)
+
+    def appendMessage(self, message):
+        self.content.add(MessageWidget(message))
         self.content_scroll.scrollToBottom()
 
     def notify(self, contact="somebody", msg=""):
@@ -254,12 +264,12 @@
         """
         self.host.notification.notify(contact, msg)
 
-    def printDayChange(self, day):
-        """Display the day on a new line.
+    # def printDayChange(self, day):
+    #     """Display the day on a new line.
 
-        @param day(unicode): day to display (or not if this method is not overwritten)
-        """
-        self.printInfo("* " + day)
+    #     @param day(unicode): day to display (or not if this method is not overwritten)
+    #     """
+    #     self.printInfo("* " + day)
 
     def setTitle(self, title=None, extra=None):
         """Refresh the title of this Chat dialog
--- a/src/browser/sat_browser/game_radiocol.py	Sun Aug 28 19:25:52 2016 +0200
+++ b/src/browser/sat_browser/game_radiocol.py	Sun Feb 26 18:32:47 2017 +0100
@@ -258,7 +258,8 @@
         Please do not submit files that are protected by copyright.<br />
         Click <a style="color: red;">here</a> if you need some support :)"""
         link_cb = lambda: self._parent.host.bridge.joinMUC(self._parent.host.default_muc, self._parent.nick, profile=C.PROF_KEY_NONE, callback=lambda dummy: None, errback=self._parent.host.onJoinMUCFailure)
-        self._parent.printInfo(help_msg, type_='link', link_cb=link_cb)
+        # FIXME: printInfo disabled after refactoring
+        # self._parent.printInfo(help_msg, type_='link', link_cb=link_cb)
 
     def pushNextSong(self, title):
         """Add a song to the left panel's next songs queue"""
@@ -301,7 +302,8 @@
             log.warning("Can't preload song, we are getting too many songs to preload, we shouldn't have more than %d at once" % self.queue_data[1])
         else:
             self.pushNextSong(title)
-            self._parent.printInfo(_('%(user)s uploaded %(artist)s - %(title)s') % {'user': sender, 'artist': artist, 'title': title})
+            # FIXME: printInfo disabled after refactoring
+            # self._parent.printInfo(_('%(user)s uploaded %(artist)s - %(title)s') % {'user': sender, 'artist': artist, 'title': title})
 
     def radiocolPlayHandler(self, filename):
         found = False
--- a/src/browser/sat_browser/json.py	Sun Aug 28 19:25:52 2016 +0200
+++ b/src/browser/sat_browser/json.py	Sun Feb 26 18:32:47 2017 +0100
@@ -167,7 +167,7 @@
 class RegisterCall(LiberviaJsonProxy):
     def __init__(self):
         LiberviaJsonProxy.__init__(self, "/register_api",
-                        ["getSessionMetadata", "isConnected", "asyncConnect", "registerParams", "getMenus"])
+                        ["getSessionMetadata", "isConnected", "connect", "registerParams", "menusGet"])
 
 
 class BridgeCall(LiberviaJsonProxy):
@@ -177,10 +177,10 @@
                          "psDeleteNode", "psRetractItem", "psRetractItems",
                          "mbSend", "mbRetract", "mbGet", "mbGetFromMany", "mbGetFromManyRTResult",
                          "mbGetFromManyWithComments", "mbGetFromManyWithCommentsRTResult",
-                         "getHistory", "getPresenceStatuses", "joinMUC", "mucLeave", "mucGetRoomsJoined",
+                         "historyGet", "getPresenceStatuses", "joinMUC", "mucLeave", "mucGetRoomsJoined",
                          "inviteMUC", "launchTarotGame", "getTarotCardsPaths", "tarotGameReady",
                          "tarotGamePlayCards", "launchRadioCollective",
-                         "getWaitingSub", "subscription", "delContact", "updateContact", "getCard",
+                         "getWaitingSub", "subscription", "delContact", "updateContact", "avatarGet",
                          "getEntityData", "getParamsUI", "asyncGetParamA", "setParam", "launchAction",
                          "disconnect", "chatStateComposing", "getNewAccountDomain",
                          "syntaxConvert", "getAccountDialogUI", "getMainResource", "getEntitiesData",
@@ -194,15 +194,12 @@
         log.warning("getConfig is not implemeted in Libervia yet")
         return ''
 
-    def isConnected(self, dummy): # FIXME
+    def isConnected(self, dummy, callback): # FIXME
         log.warning("isConnected is not implemeted in Libervia as for now profile is connected if session is opened")
-        return True
+        callback(True)
 
-    def getAvatarFile(self, hash_, callback=None):
-        log.warning("getAvatarFile only return hash in Libervia")
-        if callback is not None:
-            callback(hash_)
-        return hash_
+    def bridgeConnect(self, callback, errback):
+        callback()
 
 
 class BridgeSignals(LiberviaJsonProxy):
@@ -263,7 +260,7 @@
             self.retry_timer.scheduleRepeating(1000)
             _timerCb(None)
 
-    def register(self, name, callback, with_profile=True):
+    def register_signal(self, name, callback, with_profile=True):
         """Register a signal
 
         @param: name of the signal to register
--- a/src/browser/sat_browser/plugin_sec_otr.py	Sun Aug 28 19:25:52 2016 +0200
+++ b/src/browser/sat_browser/plugin_sec_otr.py	Sun Feb 26 18:32:47 2017 +0100
@@ -35,6 +35,8 @@
 import otrjs_wrapper as otr
 import dialog
 import chat
+import uuid
+import time
 
 
 NS_OTR = "otr_plugin"
@@ -162,11 +164,12 @@
         if not encrypted:
             log.warning(u"A plain-text message has been handled by otr.js")
         log.debug(u"message received (was %s): %s" % ('encrypted' if encrypted else 'plain', msg))
+        uuid_ = str(uuid.uuid4())  # FIXME
         if not encrypted:
             if self.state == otr.context.STATE_ENCRYPTED:
                 log.warning(u"Received unencrypted message in an encrypted context (from %(jid)s)" % {'jid': self.peer})
-                self.host.newMessageHandler(unicode(self.peer), RECEIVE_PLAIN_IN_ENCRYPTED_CONTEXT, C.MESS_TYPE_INFO, unicode(self.host.whoami), {})
-        self.host.newMessageHandler(unicode(self.peer), msg, C.MESS_TYPE_CHAT, unicode(self.host.whoami), {})
+                self.host.newMessageHandler(uuid_, time.time(), unicode(self.peer), unicode(self.host.whoami), {'': RECEIVE_PLAIN_IN_ENCRYPTED_CONTEXT}, {}, C.MESS_TYPE_INFO, {})
+        self.host.newMessageHandler(uuid_, time.time(), unicode(self.peer), unicode(self.host.whoami), {'': msg}, {}, C.MESS_TYPE_CHAT, {})
 
     def sendMessageCb(self, msg, meta=None):
         assert isinstance(self.peer, jid.JID)
@@ -199,7 +202,8 @@
             elif msg_state == otr.context.STATE_FINISHED:
                 feedback = END_FINISHED
 
-        self.host.newMessageHandler(unicode(self.peer), feedback.format(jid=other_jid_s), C.MESS_TYPE_INFO, unicode(self.host.whoami), {'header_info': OTR.getInfoText(msg_state, trust)})
+        uuid_ = str(uuid.uuid4())  # FIXME
+        self.host.newMessageHandler(uuid_, time.time(), unicode(self.peer), unicode(self.host.whoami), {'': feedback.format(jid=other_jid_s)}, {}, C.MESS_TYPE_INFO, {'header_info': OTR.getInfoText(msg_state, trust)})
 
     def setCurrentTrust(self, new_trust='', act='asked', type_='trust'):
         log.debug(u"setCurrentTrust: trust={trust}, act={act}, type={type}".format(type=type_, trust=new_trust, act=act))
@@ -222,7 +226,8 @@
         otr.context.Context.setCurrentTrust(self, new_trust)
         if old_trust != new_trust:
             feedback = AUTH_STATUS.format(state=(AUTH_TRUSTED if new_trust else AUTH_UNTRUSTED).lower())
-            self.host.newMessageHandler(unicode(self.peer), feedback, C.MESS_TYPE_INFO, unicode(self.host.whoami), {'header_info': OTR.getInfoText(self.state, new_trust)})
+            uuid_ = str(uuid.uuid4())  # FIXME
+            self.host.newMessageHandler(uuid_, time.time(), unicode(self.peer), unicode(self.host.whoami), {'': feedback}, {}, C.MESS_TYPE_INFO, {'header_info': OTR.getInfoText(self.state, new_trust)})
 
     def fingerprintAuthCb(self):
         """OTR v2 authentication using manual fingerprint comparison"""
@@ -388,8 +393,8 @@
         self.host = host
         self.context_manager = None
         self.host.bridge._registerMethods(["skipOTR"])
-        self.host.trigger.add("messageNewTrigger", self.newMessageTg, priority=trigger.TriggerManager.MAX_PRIORITY) # FIXME: need to be fixed after message refactoring
-        self.host.trigger.add("sendMessageTrigger", self.sendMessageTg, priority=trigger.TriggerManager.MAX_PRIORITY)
+        self.host.trigger.add("messageNewTrigger", self.newMessageTg, priority=trigger.TriggerManager.MAX_PRIORITY)
+        self.host.trigger.add("messageSendTrigger", self.sendMessageTg, priority=trigger.TriggerManager.MAX_PRIORITY)
 
         # FIXME: workaround for a pyjamas issue: calling hash on a class method always return a different value if that method is defined directly within the class (with the "def" keyword)
         self._profilePluggedListener = self.profilePluggedListener
@@ -453,10 +458,14 @@
         if show == C.PRESENCE_UNAVAILABLE:
             self.endSession(entity, disconnect=False)
 
-    def newMessageTg(self, from_jid, msg, msg_type, to_jid, extra, profile):
+    def newMessageTg(self, uid, timestamp, from_jid, to_jid, msg, subject, msg_type, extra, profile):
         if msg_type != C.MESS_TYPE_CHAT:
             return True
 
+        try:
+            msg = msg.values()[0]  # FIXME: Q&D fix for message refactoring, message is now a dict
+        except IndexError:
+            return True
         tag = otr.proto.checkForOTR(msg)
         if tag is None or (tag == otr.context.WHITESPACE_TAG and not DEFAULT_POLICY_FLAGS['WHITESPACE_START_AKE']):
             return True
@@ -488,7 +497,8 @@
             if otrctx.state == otr.context.STATE_ENCRYPTED:
                 log.debug(u"encrypting message")
                 otrctx.sendMessage(message)
-                self.host.newMessageHandler(unicode(self.host.whoami), message, mess_type, unicode(to_jid), extra)
+                uuid_ = str(uuid.uuid4())  # FIXME
+                self.host.newMessageHandler(uuid_, time.time(), unicode(self.host.whoami), unicode(to_jid), {'': message}, {}, mess_type,  extra)
             else:
                 feedback = SEND_PLAIN_IN_FINISHED_CONTEXT
                 dialog.InfoDialog(FINISHED_CONTEXT_TITLE.format(jid=to_jid), feedback, AddStyleName="maxWidthLimit").show()
@@ -511,7 +521,8 @@
         for otrctx in contexts:
             if otrctx is None or otrctx.state == otr.context.STATE_PLAINTEXT:
                 if disconnect:
-                    self.host.newMessageHandler(unicode(other_jid), END_PLAIN_HAS_NOT.format(jid=other_jid), C.MESS_TYPE_INFO, unicode(self.host.whoami), {})
+                    uuid_ = str(uuid.uuid4())  # FIXME
+                    self.host.newMessageHandler(uuid_, time.time(), unicode(other_jid), unicode(self.host.whoami), {'': END_PLAIN_HAS_NOT.format(jid=other_jid)}, {}, C.MESS_TYPE_INFO, {})
                 return
             if disconnect:
                 otrctx.disconnect()
--- a/src/common/constants.py	Sun Aug 28 19:25:52 2016 +0200
+++ b/src/common/constants.py	Sun Feb 26 18:32:47 2017 +0100
@@ -48,8 +48,8 @@
     UPLOAD_KO = 'UPLOAD KO'
 
     # directories
-    AVATARS_DIR = "avatars/"
     MEDIA_DIR = "media/"
+    CACHE_DIR = "cache"
 
     # avatars
     DEFAULT_AVATAR_FILE = "default_avatar.png"
--- a/src/server/blog.py	Sun Aug 28 19:25:52 2016 +0200
+++ b/src/server/blog.py	Sun Feb 26 18:32:47 2017 +0100
@@ -122,7 +122,7 @@
         self.host = host
         Resource.__init__(self)
         TemplateProcessor.__init__(self, host)
-        self.host.bridge.register('entityDataUpdated', self.entityDataUpdatedHandler)
+        self.host.bridge.register_signal('entityDataUpdated', self.entityDataUpdatedHandler)
         self.avatars_cache = {}
         self.waiting_deferreds = {}
 
--- a/src/server/server.py	Sun Aug 28 19:25:52 2016 +0200
+++ b/src/server/server.py	Sun Feb 26 18:32:47 2017 +0100
@@ -33,10 +33,11 @@
 
 from sat.core.log import getLogger
 log = getLogger(__name__)
-from sat_frontends.bridge.DBus import DBusBridgeFrontend, BridgeExceptionNoService, const_TIMEOUT as BRIDGE_TIMEOUT
+from sat_frontends.bridge.dbus_bridge import Bridge, BridgeExceptionNoService, const_TIMEOUT as BRIDGE_TIMEOUT
 from sat.core.i18n import _, D_
 from sat.core import exceptions
 from sat.tools import utils
+from sat.tools.common import regex
 
 import re
 import glob
@@ -47,6 +48,7 @@
 import uuid
 import urlparse
 import urllib
+import shortuuid
 from zope.interface import Interface, Attribute, implements
 from httplib import HTTPS_PORT
 import libervia
@@ -68,6 +70,7 @@
 class ISATSession(Interface):
     profile = Attribute("Sat profile")
     jid = Attribute("JID associated with the profile")
+    uuid = Attribute("uuid associated with the profile session")
 
 
 class SATSession(object):
@@ -76,6 +79,7 @@
     def __init__(self, session):
         self.profile = None
         self.jid = None
+        self.uuid = unicode(shortuuid.uuid())
 
 
 class LiberviaSession(server.Session):
@@ -650,7 +654,7 @@
         profile = ISATSession(self.session).profile
         return self.sat_host.bridge.getPresenceStatuses(profile)
 
-    def jsonrpc_getHistory(self, from_jid, to_jid, size, between, search=''):
+    def jsonrpc_historyGet(self, from_jid, to_jid, size, between, search=''):
         """Return history for the from_jid/to_jid couple"""
         sat_session = ISATSession(self.session)
         profile = sat_session.profile
@@ -661,15 +665,15 @@
         if jid.JID(from_jid).userhost() != sat_jid.userhost() and jid.JID(to_jid).userhost() != sat_jid.userhost():
             log.error(u"Trying to get history from a different jid (given (browser): {}, real (backend): {}), maybe a hack attempt ?".format(from_jid, sat_jid))
             return {}
-        d = self.asyncBridgeCall("getHistory", from_jid, to_jid, size, between, search, profile)
+        d = self.asyncBridgeCall("historyGet", from_jid, to_jid, size, between, search, profile)
 
         def show(result_dbus):
             result = []
             for line in result_dbus:
                 #XXX: we have to do this stupid thing because Python D-Bus use its own types instead of standard types
                 #     and txJsonRPC doesn't accept D-Bus types, resulting in a empty query
-                timestamp, from_jid, to_jid, message, mess_type, extra = line
-                result.append((float(timestamp), unicode(from_jid), unicode(to_jid), unicode(message), unicode(mess_type), dict(extra)))
+                uuid, timestamp, from_jid, to_jid, message, subject, mess_type, extra = line
+                result.append((unicode(uuid), float(timestamp), unicode(from_jid), unicode(to_jid), dict(message), dict(subject), unicode(mess_type), dict(extra)))
             return result
         d.addCallback(show)
         return d
@@ -784,6 +788,19 @@
         profile = ISATSession(self.session).profile
         return self.sat_host.bridge.getCard(jid_, profile)
 
+    @defer.inlineCallbacks
+    def jsonrpc_avatarGet(self, entity, cache_only, hash_only):
+        session_data = ISATSession(self.session)
+        profile = session_data.profile
+        # profile_uuid = session_data.uuid
+        avatar = yield self.asyncBridgeCall("avatarGet", entity, cache_only, hash_only, profile)
+        if hash_only:
+            defer.returnValue(avatar)
+        else:
+            filename = os.path.basename(avatar)
+            avatar_url = os.path.join(C.CACHE_DIR, session_data.uuid, filename)
+            defer.returnValue(avatar_url)
+
     def jsonrpc_getAccountDialogUI(self):
         """Get the dialog for managing user account
         @return: XML string of the XMLUI"""
@@ -920,7 +937,7 @@
         _session = request.getSession()
         parsed = jsonrpclib.loads(request.content.read())
         method = parsed.get("method")  # pylint: disable=E1103
-        if  method not in ['getSessionMetadata', 'registerParams', 'getMenus']:
+        if  method not in ['getSessionMetadata', 'registerParams', 'menusGet']:
             #if we don't call these methods, we need to be identified
             profile = ISATSession(_session).profile
             if not profile:
@@ -1020,7 +1037,7 @@
                 return
             register_with_ext_jid = False
 
-            connect_method = "asyncConnect"
+            connect_method = "connect"
 
         if self.waiting_profiles.getRequest(profile):
             request.write(C.ALREADY_WAITING)
@@ -1105,16 +1122,22 @@
         _session = request.getSession()
         sat_session = ISATSession(_session)
         if sat_session.profile:
-            log.error(('/!\\ Session has already a profile, this should NEVER happen!'))
+            log.error(_(u'/!\\ Session has already a profile, this should NEVER happen!'))
             request.write(C.SESSION_ACTIVE)
             request.finish()
             return
         # we manage profile server side to avoid profile spoofing
         sat_session.profile = profile
         self.sat_host.prof_connected.add(profile)
+        cache_dir = os.path.join(self.sat_host.cache_root_dir, regex.pathEscape(profile))
+        # FIXME: would be better to have a global /cache URL which redirect to profile's cache directory, without uuid
+        self.sat_host.cache_resource.putChild(sat_session.uuid, ProtectedFile(cache_dir))
+        log.debug(_(u"profile cache resource added from {uuid} to {path}").format(uuid=sat_session.uuid, path=cache_dir))
 
         def onExpire():
             log.info(u"Session expired (profile=%s)" % (profile,))
+            self.sat_host.cache_resource.delEntity(sat_session.uuid)
+            log.debug(_(u"profile cache resource {uuid} deleted").format(uuid = sat_session.uuid))
             try:
                 #We purge the queue
                 del self.sat_host.signal_handler.queue[profile]
@@ -1133,13 +1156,13 @@
         profile = ISATSession(_session).profile
         return self.sat_host.bridge.isConnected(profile)
 
-    def jsonrpc_asyncConnect(self):
+    def jsonrpc_connect(self):
         _session = self.request.getSession()
         profile = ISATSession(_session).profile
         if self.waiting_profiles.getRequest(profile):
             raise jsonrpclib.Fault(1, C.ALREADY_WAITING)  # FIXME: define some standard error codes for libervia
         self.waiting_profiles.setRequest(self.request, profile)
-        self.sat_host.bridge.asyncConnect(profile)
+        self.sat_host.bridge.connect(profile)
         return server.NOT_DONE_YET
 
     def jsonrpc_getSessionMetadata(self):
@@ -1171,10 +1194,10 @@
         # params = """<params><individual>...</category></individual>"""
         # self.sat_host.bridge.paramsRegisterApp(params, C.SECURITY_LIMIT, C.APP_NAME)
 
-    def jsonrpc_getMenus(self):
+    def jsonrpc_menusGet(self):
         """Return the parameters XML for profile"""
         # XXX: we put this method in Register because we get menus before being logged
-        return self.sat_host.bridge.getMenus('', C.SECURITY_LIMIT)
+        return self.sat_host.bridge.menusGet('', C.SECURITY_LIMIT)
 
     def _getSecurityWarning(self):
         """@return: a security warning message, or None if the connection is secure"""
@@ -1437,80 +1460,84 @@
 
         self._cleanup = []
 
-        root = LiberviaRootResource(self.options, self.html_dir)
-
         self.signal_handler = SignalHandler(self)
-        _register = Register(self)
-        _upload_radiocol = UploadManagerRadioCol(self)
-        _upload_avatar = UploadManagerAvatar(self)
-        self.signal_handler.plugRegister(_register)
         self.sessions = {}  # key = session value = user
         self.prof_connected = set()  # Profiles connected
 
         ## bridge ##
         try:
-            self.bridge = DBusBridgeFrontend()
+            self.bridge = Bridge()
         except BridgeExceptionNoService:
             print(u"Can't connect to SàT backend, are you sure it's launched ?")
             sys.exit(1)
-
-        def backendReady(dummy):
-            self.bridge.register("connected", self.signal_handler.connected)
-            self.bridge.register("disconnected", self.signal_handler.disconnected)
-            #core
-            for signal_name in ['presenceUpdate', 'messageNew', 'subscribe', 'contactDeleted',
-                                'newContact', 'entityDataUpdated', 'paramUpdate']:
-                self.bridge.register(signal_name, self.signal_handler.getGenericCb(signal_name))
-            # XXX: actionNew is handled separately because the handler must manage security_limit
-            self.bridge.register('actionNew', self.signal_handler.actionNewHandler)
-            #plugins
-            for signal_name in ['psEvent', 'mucRoomJoined', 'tarotGameStarted', 'tarotGameNew', 'tarotGameChooseContrat',
-                                'tarotGameShowCards', 'tarotGameInvalidCards', 'tarotGameCardsPlayed', 'tarotGameYourTurn', 'tarotGameScore', 'tarotGamePlayers',
-                                'radiocolStarted', 'radiocolPreload', 'radiocolPlay', 'radiocolNoUpload', 'radiocolUploadOk', 'radiocolSongRejected', 'radiocolPlayers',
-                                'mucRoomLeft', 'mucRoomUserChangedNick', 'chatStateReceived']:
-                self.bridge.register(signal_name, self.signal_handler.getGenericCb(signal_name), "plugin")
-            self.media_dir = self.bridge.getConfig('', 'media_dir')
-            self.local_dir = self.bridge.getConfig('', 'local_dir')
+        self.bridge.bridgeConnect(callback=self._bridgeCb, errback=self._bridgeEb)
 
-            ## URLs ##
-            def putChild(path, resource):
-                """Add a child to the root resource"""
-                # FIXME: check that no information is leaked (c.f. https://twistedmatrix.com/documents/current/web/howto/using-twistedweb.html#request-encoders)
-                root.putChild(path, web_resource.EncodingResourceWrapper(resource, [server.GzipEncoderFactory()]))
-
-            # JSON APIs
-            putChild('json_signal_api', self.signal_handler)
-            putChild('json_api', MethodHandler(self))
-            putChild('register_api', _register)
+    def backendReady(self, dummy):
+        self.root = root = LiberviaRootResource(self.options, self.html_dir)
+        _register = Register(self)
+        _upload_radiocol = UploadManagerRadioCol(self)
+        _upload_avatar = UploadManagerAvatar(self)
+        self.signal_handler.plugRegister(_register)
+        self.bridge.register_signal("connected", self.signal_handler.connected)
+        self.bridge.register_signal("disconnected", self.signal_handler.disconnected)
+        #core
+        for signal_name in ['presenceUpdate', 'messageNew', 'subscribe', 'contactDeleted',
+                            'newContact', 'entityDataUpdated', 'paramUpdate']:
+            self.bridge.register_signal(signal_name, self.signal_handler.getGenericCb(signal_name))
+        # XXX: actionNew is handled separately because the handler must manage security_limit
+        self.bridge.register_signal('actionNew', self.signal_handler.actionNewHandler)
+        #plugins
+        for signal_name in ['psEvent', 'mucRoomJoined', 'tarotGameStarted', 'tarotGameNew', 'tarotGameChooseContrat',
+                            'tarotGameShowCards', 'tarotGameInvalidCards', 'tarotGameCardsPlayed', 'tarotGameYourTurn', 'tarotGameScore', 'tarotGamePlayers',
+                            'radiocolStarted', 'radiocolPreload', 'radiocolPlay', 'radiocolNoUpload', 'radiocolUploadOk', 'radiocolSongRejected', 'radiocolPlayers',
+                            'mucRoomLeft', 'mucRoomUserChangedNick', 'chatStateReceived']:
+            self.bridge.register_signal(signal_name, self.signal_handler.getGenericCb(signal_name), "plugin")
+        self.media_dir = self.bridge.getConfig('', 'media_dir')
+        self.local_dir = self.bridge.getConfig('', 'local_dir')
+        self.cache_root_dir = os.path.join(
+            self.local_dir,
+            C.CACHE_DIR)
 
-            # files upload
-            putChild('upload_radiocol', _upload_radiocol)
-            putChild('upload_avatar', _upload_avatar)
+        # JSON APIs
+        self.putChild('json_signal_api', self.signal_handler)
+        self.putChild('json_api', MethodHandler(self))
+        self.putChild('register_api', _register)
 
-            # static pages
-            putChild('blog', MicroBlog(self))
-            putChild(C.THEMES_URL, ProtectedFile(self.themes_dir))
+        # files upload
+        self.putChild('upload_radiocol', _upload_radiocol)
+        self.putChild('upload_avatar', _upload_avatar)
 
-            # media dirs
-            putChild(os.path.dirname(C.MEDIA_DIR), ProtectedFile(self.media_dir))
-            putChild(os.path.dirname(C.AVATARS_DIR), ProtectedFile(os.path.join(self.local_dir, C.AVATARS_DIR)))
+        # static pages
+        self.putChild('blog', MicroBlog(self))
+        self.putChild(C.THEMES_URL, ProtectedFile(self.themes_dir))
 
-            # special
-            putChild('radiocol', ProtectedFile(_upload_radiocol.getTmpDir(), defaultType="audio/ogg"))  # FIXME: We cheat for PoC because we know we are on the same host, so we use directly upload dir
-            # pyjamas tests, redirected only for dev versions
-            if self.version[-1] == 'D':
-                putChild('test', web_util.Redirect('/libervia_test.html'))
+        # media dirs
+        # FIXME: get rid of dirname and "/" in C.XXX_DIR
+        self.putChild(os.path.dirname(C.MEDIA_DIR), ProtectedFile(self.media_dir))
+        self.cache_resource = web_resource.NoResource()
+        self.putChild(C.CACHE_DIR, self.cache_resource)
+
+        # special
+        self.putChild('radiocol', ProtectedFile(_upload_radiocol.getTmpDir(), defaultType="audio/ogg"))  # FIXME: We cheat for PoC because we know we are on the same host, so we use directly upload dir
+        # pyjamas tests, redirected only for dev versions
+        if self.version[-1] == 'D':
+            self.putChild('test', web_util.Redirect('/libervia_test.html'))
 
 
-            wrapped = web_resource.EncodingResourceWrapper(root, [server.GzipEncoderFactory()])
-            self.site = server.Site(wrapped)
-            self.site.sessionFactory = LiberviaSession
+        wrapped = web_resource.EncodingResourceWrapper(root, [server.GzipEncoderFactory()])
+        self.site = server.Site(wrapped)
+        self.site.sessionFactory = LiberviaSession
 
+
+    def _bridgeCb(self):
         self.bridge.getReady(lambda: self.initialised.callback(None),
                              lambda failure: self.initialised.errback(Exception(failure)))
-        self.initialised.addCallback(backendReady)
+        self.initialised.addCallback(self.backendReady)
         self.initialised.addErrback(lambda failure: log.error(u"Init error: %s" % failure))
 
+    def _bridgeEb(self, failure):
+        log.error(u"Can't connect to bridge: {}".format(failure))
+
     @property
     def version(self):
         """Return the short version of Libervia"""
@@ -1555,13 +1582,19 @@
                 self.stop()
                 return
             if not connected:
-                self.bridge.asyncConnect(C.SERVICE_PROFILE, self.options['passphrase'],
-                                         callback=self._startService, errback=eb)
+                self.bridge.connect(C.SERVICE_PROFILE, self.options['passphrase'],
+                                    {}, callback=self._startService, errback=eb)
             else:
                 self._startService()
 
         self.initialised.addCallback(initOk)
 
+    ## URLs ##
+    def putChild(self, path, resource):
+        """Add a child to the root resource"""
+        # FIXME: check that no information is leaked (c.f. https://twistedmatrix.com/documents/current/web/howto/using-twistedweb.html#request-encoders)
+        self.root.putChild(path, web_resource.EncodingResourceWrapper(resource, [server.GzipEncoderFactory()]))
+
     ## TLS related methods ##
 
     def _TLSOptionsCheck(self):