# HG changeset patch # User Goffi # Date 1488130367 -3600 # Node ID 0c0551967bdf85aea855de6745455a701db49dd7 # Parent 58f611481e6da4cc17146a609e981638cf1a7a07 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. diff -r 58f611481e6d -r 0c0551967bdf setup.py --- 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}, ) diff -r 58f611481e6d -r 0c0551967bdf src/browser/libervia_main.py --- 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() diff -r 58f611481e6d -r 0c0551967bdf src/browser/sat_browser/chat.py --- 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 = "{msg}" - 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, "%(timestamp)s %(nick)s %(msg)s" % - {"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 = "{timestamp} {nick} {msg}" + _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 diff -r 58f611481e6d -r 0c0551967bdf src/browser/sat_browser/game_radiocol.py --- 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.
Click here 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 diff -r 58f611481e6d -r 0c0551967bdf src/browser/sat_browser/json.py --- 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 diff -r 58f611481e6d -r 0c0551967bdf src/browser/sat_browser/plugin_sec_otr.py --- 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() diff -r 58f611481e6d -r 0c0551967bdf src/common/constants.py --- 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" diff -r 58f611481e6d -r 0c0551967bdf src/server/blog.py --- 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 = {} diff -r 58f611481e6d -r 0c0551967bdf src/server/server.py --- 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 = """...""" # 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):