diff src/browser/sat_browser/json.py @ 589:a5019e62c3e9 frontends_multi_profiles

browser side: big refactoring to base Libervia on QuickFrontend, first draft: /!\ not finished, partially working and highly instable - add collections module with an OrderedDict like class - SatWebFrontend inherit from QuickApp - general sat_frontends tools.jid module is used - bridge/json methods have moved to json module - UniBox is partially removed (should be totally removed before merge to trunk) - Signals are now register with the generic registerSignal method (which is called mainly in QuickFrontend) - the generic getOrCreateWidget method from QuickWidgetsManager is used instead of Libervia's specific methods - all Widget are now based more or less directly on QuickWidget - with the new QuickWidgetsManager.getWidgets method, it's no more necessary to check all widgets which are instance of a particular class - ChatPanel and related moved to chat module - MicroblogPanel and related moved to blog module - global and overcomplicated send method has been disabled: each class should manage its own sending - for consistency with other frontends, former ContactPanel has been renamed to ContactList and vice versa - for the same reason, ChatPanel has been renamed to Chat - for compatibility with QuickFrontend, a fake profile is used in several places, it is set to C.PROF_KEY_NONE (real profile is managed server side for obvious security reasons) - changed default url for web panel to SàT website, and contact address to generic SàT contact address - ContactList is based on QuickContactList, UI changes are done in update method - bride call (now json module) have been greatly improved, in particular call can be done in the same way as for other frontends (bridge.method_name(arg1, arg2, ..., callback=cb, errback=eb). Blocking method must be called like async methods due to javascript architecture - in bridge calls, a callback can now exists without errback - hard reload on BridgeSignals remote error has been disabled, a better option should be implemented - use of constants where that make sens, some style improvments - avatars are temporarily disabled - lot of code disabled, will be fixed or removed before merge - various other changes, check diff for more details server side: manage remote exception on getEntityData, removed getProfileJid call, added getWaitingConf, added getRoomsSubjects
author Goffi <goffi@goffi.org>
date Sat, 24 Jan 2015 01:45:39 +0100
parents src/browser/libervia_main.py@0a06cf833f5a
children 917e271975d9
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/browser/sat_browser/json.py	Sat Jan 24 01:45:39 2015 +0100
@@ -0,0 +1,268 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Libervia: a Salut à Toi frontend
+# Copyright (C) 2011, 2012, 2013, 2014 Jérôme Poisson <goffi@goffi.org>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+### logging configuration ###
+from sat.core.log import getLogger
+log = getLogger(__name__)
+###
+
+from pyjamas.Timer import Timer
+from pyjamas import Window
+from pyjamas import JSONService
+
+from sat_browser.constants import Const as C
+
+
+class LiberviaMethodProxy(object):
+    """This class manage calling for one method"""
+
+    def __init__(self, parent, method):
+        self._parent = parent
+        self._method = method
+
+    def call(self, *args, **kwargs):
+        """Method called when self._method attribue is used in JSON_PROXY_PARENT
+
+        This method manage callback/errback in kwargs, and profile(_key) removing
+        @param *args: positional arguments of self._method
+        @param **kwargs: keyword arguments of self._method
+        """
+        callback=kwargs.pop('callback', None)
+        errback=kwargs.pop('errback', None)
+
+        # as profile is linked to browser session and managed server side, we remove them
+        profile_removed = False
+        try:
+            kwargs['profile'] # FIXME: workaround for pyjamas bug: KeyError is not raised iwith del
+            del kwargs['profile']
+            profile_removed = True
+        except KeyError:
+            pass
+
+        try:
+            kwargs['profile_key'] # FIXME: workaround for pyjamas bug: KeyError is not raised iwith del
+            del kwargs['profile_key']
+            profile_removed = True
+        except KeyError:
+            pass
+
+        if not profile_removed and args:
+            # if profile was not in kwargs, there is most probably one in args
+            args = list(args)
+            assert isinstance(args[-1], basestring) # Detect when we want to remove a callback (or something else) instead of the profile
+            del args[-1]
+
+        if kwargs:
+            # kwargs should be empty here, we don't manage keyword arguments on bridge calls
+            log.error("kwargs is not empty after treatment on method call: kwargs={}".format(kwargs))
+
+        id_ = self._parent.callMethod(self._method, args)
+
+        # callback or errback are managed in parent LiberviaJsonProxy with call id
+        if callback is not None:
+            self._parent.cb[id_] = callback
+        if errback is not None:
+            self._parent.eb[id_] = errback
+
+
+class LiberviaJsonProxy(JSONService.JSONService):
+
+    def __init__(self, url, methods):
+        self._serviceURL = url
+        self.methods = methods
+        JSONService.JSONService.__init__(self, url, self)
+        self.cb = {}
+        self.eb = {}
+        self._registerMethods(methods)
+
+    def _registerMethods(self, methods):
+        if methods:
+            for method in methods:
+                log.debug("Registering JSON method call [{}]".format(method))
+                setattr(self,
+                        method,
+                        getattr(LiberviaMethodProxy(self, method), 'call')
+                       )
+
+    def callMethod(self, method, params, handler = None):
+        ret = super(LiberviaJsonProxy, self).callMethod(method, params, handler)
+        return ret
+
+    def call(self, method, cb, *args):
+        # FIXME: deprecated call method, must be removed once it's not used anymore
+        id_ = self.callMethod(method, args)
+        log.debug("call: method={} [id={}], args={}".format(method, id_, args))
+        if cb:
+            if isinstance(cb, tuple):
+                if len(cb) != 2:
+                    log.error("tuple syntax for bridge.call is (callback, errback), aborting")
+                    return
+                if cb[0] is not None:
+                    self.cb[id_] = cb[0]
+                self.eb[id_] = cb[1]
+            else:
+                self.cb[id_] = cb
+
+    def onRemoteResponse(self, response, request_info):
+        try:
+            _cb = self.cb[request_info.id]
+        except KeyError:
+            pass
+        else:
+            # if isinstance(_cb, tuple):
+            #     #we have arguments attached to the callback
+            #     #we send them after the answer
+            #     callback, args = _cb
+            #     callback(response, *args)
+            # else:
+            #     #No additional argument, we call directly the callback
+            _cb(response)
+            del self.cb[request_info.id]
+
+        try:
+            del self.eb[request_info.id]
+        except KeyError:
+            pass
+
+    def onRemoteError(self, code, errobj, request_info):
+        """def dump(obj):
+            print "\n\nDUMPING %s\n\n" % obj
+            for i in dir(obj):
+                print "%s: %s" % (i, getattr(obj,i))"""
+        try:
+            _eb = self.eb[request_info.id]
+        except KeyError:
+            if code != 0:
+                log.error("Internal server error")
+                """for o in code, error, request_info:
+                    dump(o)"""
+            else:
+                if isinstance(errobj['message'], dict):
+                    log.error("Error %s: %s" % (errobj['message']['faultCode'], errobj['message']['faultString']))
+                else:
+                    log.error("%s" % errobj['message'])
+        else:
+            _eb((code, errobj))
+            del self.eb[request_info.id]
+
+        try:
+            del self.cb[request_info.id]
+        except KeyError:
+            pass
+
+
+class RegisterCall(LiberviaJsonProxy):
+    def __init__(self):
+        LiberviaJsonProxy.__init__(self, "/register_api",
+                        ["isRegistered", "isConnected", "asyncConnect", "registerParams", "getMenus"])
+
+
+class BridgeCall(LiberviaJsonProxy):
+    def __init__(self):
+        LiberviaJsonProxy.__init__(self, "/json_api",
+                        ["getContacts", "addContact", "sendMessage", "sendMblog", "sendMblogComment",
+                         "getLastMblogs", "getMassiveLastMblogs", "getMblogComments",
+                         "getHistory", "getPresenceStatuses", "joinMUC", "mucLeave", "getRoomsJoined",
+                         "getRoomsSubjects", "inviteMUC", "launchTarotGame", "getTarotCardsPaths", "tarotGameReady",
+                         "tarotGamePlayCards", "launchRadioCollective", "getMblogs", "getMblogsWithComments",
+                         "getWaitingSub", "subscription", "delContact", "updateContact", "getCard",
+                         "getEntityData", "getParamsUI", "asyncGetParamA", "setParam", "launchAction",
+                         "disconnect", "chatStateComposing", "getNewAccountDomain", "confirmationAnswer",
+                         "syntaxConvert", "getAccountDialogUI", "getLastResource", "getWaitingConf",
+                        ])
+    def __call__(self, *args, **kwargs):
+        return LiberviaJsonProxy.__call__(self, *args, **kwargs)
+
+    def getConfig(self, dummy1, dummy2): # FIXME
+        log.warning("getConfig is not implemeted in Libervia yet")
+        return ''
+
+    def isConnected(self, dummy): # FIXME
+        log.warning("isConnected is not implemeted in Libervia as for now profile is connected if session is opened")
+        return True
+
+    def getAvatarFile(self, hash_, callback=None):
+        log.warning("getAvatarFile only return hash in Libervia")
+        if callback is not None:
+            callback(hash_)
+        return hash_
+
+
+class BridgeSignals(LiberviaJsonProxy):
+    RETRY_BASE_DELAY = 1000
+
+    def __init__(self, host):
+        self.host = host
+        self.retry_delay = self.RETRY_BASE_DELAY
+        LiberviaJsonProxy.__init__(self, "/json_signal_api",
+                        ["getSignals"])
+        self._signals = {} # key: signal name, value: callback
+
+    def onRemoteResponse(self, response, request_info):
+        if self.retry_delay != self.RETRY_BASE_DELAY:
+            log.info("Connection with server restablished")
+        self.retry_delay = self.RETRY_BASE_DELAY
+        LiberviaJsonProxy.onRemoteResponse(self, response, request_info)
+
+    def onRemoteError(self, code, errobj, request_info):
+        if errobj['message'] == 'Empty Response':
+            Window.alert (u"Empty reponse bridgeSignal\ncode={}\nrequest_info: id={} method={} handler={}".format(code, request_info.id, request_info.method, request_info.handler))
+            # FIXME: to check/replace by a proper session end on disconnected signal
+            # Window.getLocation().reload()  # XXX: reset page in case of session ended.
+                                           # FIXME: Should be done more properly without hard reload
+        LiberviaJsonProxy.onRemoteError(self, code, errobj, request_info)
+        #we now try to reconnect
+        if isinstance(errobj['message'], dict) and errobj['message']['faultCode'] == 0:
+            Window.alert('You are not allowed to connect to server')
+        else:
+            def _timerCb(timer):
+                log.info("Trying to reconnect to server...")
+                self.getSignals(callback=self.signalHandler)
+            log.warning("Lost connection, trying to reconnect in {} s".format(self.retry_delay/1000))
+            Timer(notify=_timerCb).schedule(self.retry_delay)
+            self.retry_delay *= 2
+
+    def register(self, name, callback, with_profile=True):
+        """Register a signal
+
+        @param: name of the signal to register
+        @param callback: method to call
+        @param with_profile: True if the original bridge method need a profile
+        """
+        log.debug("Registering signal {}".format(name))
+        if name in self._signals:
+            log.error("Trying to register and already registered signal ({})".format(name))
+        else:
+            self._signals[name] = (callback, with_profile)
+
+    def signalHandler(self,  signal_data):
+        self.getSignals(callback=self.signalHandler)
+        if len(signal_data) == 1:
+            signal_data.append([])
+        log.debug("Got signal ==> name: %s, params: %s" % (signal_data[0], signal_data[1]))
+        name, args = signal_data
+        try:
+            callback, with_profile = self._signals[name]
+        except KeyError:
+            log.warning("Ignoring {} signal: no handler registered !".format(name))
+            return
+        if with_profile:
+            args.append(C.PROF_KEY_NONE)
+        callback(*args)