view libervia.py @ 239:b911f2b43fd4

browser_side: added input history in the unibox: This functionality uses a file from the sat project: use the -I parameter of pyjsbuild to add sat library to your PYJSPATH. To ease also possible to use your sat source directory instead of the library, you just need to trick pyjsbuild with a symbolic link: SAT=~/workspace/sat if [[ ! -e $SAT/sat ]]; then ln -sf $SAT/src $SAT/sat; fi This will allow you to import like that in libervia.py: from sat.tools.frontend.misc import InputHistory And then you can build with: $PYJS/bin/pyjsbuild libervia --no-compile-inplace -m -I $SAT
author souliane <souliane@mailoo.org>
date Mon, 14 Oct 2013 20:54:13 +0200
parents b304cdf13a3b
children a25aa882e09a
line wrap: on
line source

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
Libervia: a Salut à Toi frontend
Copyright (C) 2011, 2012, 2013 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/>.
"""

import pyjd  # this is dummy in pyjs
from pyjamas.ui.RootPanel import RootPanel
from pyjamas.ui.HTML import HTML
from pyjamas.ui.KeyboardListener import KEY_ESCAPE
from pyjamas.Timer import Timer
from pyjamas import Window, DOM
from pyjamas.JSONService import JSONProxy
from browser_side.register import RegisterBox
from browser_side.contact import ContactPanel
from browser_side.base_widget import WidgetsPanel
from browser_side.panels import MicroblogItem
from browser_side import panels, dialog
from browser_side.jid import JID
from browser_side.tools import html_sanitize
from sat.tools.frontend.misc import InputHistory


MAX_MBLOG_CACHE = 500  # Max microblog entries kept in memories

# Set to true to not create a new LiberviaWidget when a similar one
# already exist (i.e. a chat panel with the same target). Instead
# the existing widget will be eventually removed from its parent
# and added to new WidgetsPanel, or replaced to the expected
# position if the previous and the new parent are the same.
REUSE_EXISTING_LIBERVIA_WIDGETS = True


class LiberviaJsonProxy(JSONProxy):
    def __init__(self, *args, **kwargs):
        JSONProxy.__init__(self, *args, **kwargs)
        self.handler = self
        self.cb = {}
        self.eb = {}

    def call(self, method, cb, *args):
        _id = self.callMethod(method, args)
        if cb:
            if isinstance(cb, tuple):
                if len(cb) != 2:
                    print ("ERROR: tuple syntax for bridge.call is (callback, errback), aborting")
                    return
                self.cb[_id] = cb[0]
                self.eb[_id] = cb[1]
            else:
                self.cb[_id] = cb

    def onRemoteResponse(self, response, request_info):
        if request_info.id in self.cb:
            _cb = self.cb[request_info.id]
            # 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]
            if request_info.id in self.eb:
                del self.eb[request_info.id]

    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))"""
        if request_info.id in self.eb:
            _eb = self.eb[request_info.id]
            _eb((code, errobj))
            del self.cb[request_info.id]
            del self.eb[request_info.id]
        else:
            if code != 0:
                print ("Internal server error")
                """for o in code, error, request_info:
                    dump(o)"""
            else:
                if isinstance(errobj['message'], dict):
                    print("Error %s: %s" % (errobj['message']['faultCode'], errobj['message']['faultString']))
                else:
                    print("Error: %s" % errobj['message'])


class RegisterCall(LiberviaJsonProxy):
    def __init__(self):
        LiberviaJsonProxy.__init__(self, "/register_api",
                        ["isRegistered", "isConnected", "connect"])


class BridgeCall(LiberviaJsonProxy):
    def __init__(self):
        LiberviaJsonProxy.__init__(self, "/json_api",
                        ["getContacts", "addContact", "sendMessage", "sendMblog", "sendMblogComment",
                         "getLastMblogs", "getMassiveLastMblogs", "getMblogComments", "getProfileJid",
                         "getHistory", "getPresenceStatus", "joinMUC", "mucLeave", "getRoomsJoined",
                         "launchTarotGame", "getTarotCardsPaths", "tarotGameReady",
                         "tarotGameContratChoosed", "tarotGamePlayCards", "launchRadioCollective",
                         "getWaitingSub", "subscription", "delContact", "updateContact", "getCard",
                         "getEntityData", "getParamsUI", "asyncGetParamA", "setParam", "launchAction",
                         "disconnect", "chatStateComposing"
                        ])

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"])

    def onRemoteResponse(self, response, request_info):
        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.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():
                self.host.bridge_signals.call('getSignals', self.host._getSignalsCB)
            Timer(notify=_timerCb).schedule(self.retry_delay)
            self.retry_delay *= 2


class SatWebFrontend(InputHistory):
    def onModuleLoad(self):
        print "============ onModuleLoad =============="
        panels.ChatPanel.registerClass()
        panels.MicroblogPanel.registerClass()
        self.whoami = None
        self._selected_listeners = set()
        self.bridge = BridgeCall()
        self.bridge_signals = BridgeSignals(self)
        self.uni_box = None
        self.status_panel = panels.StatusPanel(self)
        self.contact_panel = ContactPanel(self)
        self.panel = panels.MainPanel(self)
        self.discuss_panel = self.panel.discuss_panel
        self.tab_panel = self.panel.tab_panel
        self.tab_panel.addTabListener(self)
        self.libervia_widgets = set() #keep track of all actives LiberviaWidgets
        self.room_list = set() #set of rooms 
        self.mblog_cache = [] #used to keep our own blog entries in memory, to show them in new mblog panel
        self.avatars_cache = {} #keep track of jid's avatar hash (key=jid, value=file)
        self.current_action_ids = set()
        #self.discuss_panel.addWidget(panels.EmptyPanel(self))
        self.discuss_panel.addWidget(panels.MicroblogPanel(self, []))
        #self.discuss_panel.addWidget(panels.EmptyPanel(self))
        self._register_box = None
        RootPanel().add(self.panel)
        DOM.addEventPreview(self)
        self.resize()
        self._register = RegisterCall()
        self._register.call('isRegistered', self._isRegisteredCB)
        self.initialised = False
        self.init_cache = []  # used to cache events until initialisation is done

    def addSelectedListener(self, callback):
        self._selected_listeners.add(callback)

    def getSelected(self):
        wid = self.tab_panel.getCurrentPanel()
        if not isinstance(wid, WidgetsPanel):
            print "ERROR: Tab widget is not a WidgetsPanel, can't get selected widget"
            return None
        return wid.selected

    def setSelected(self, widget):
        """Define the selected widget"""
        widgets_panel = self.tab_panel.getCurrentPanel()
        if not isinstance(widgets_panel, WidgetsPanel):
            return

        selected = widgets_panel.selected

        if selected == widget:
            return

        if selected:
            selected.removeStyleName('selected_widget')

        widgets_panel.selected = widget

        if widget:
            widgets_panel.selected.addStyleName('selected_widget')

        for callback in self._selected_listeners:
            callback(widget)

    def resize(self):
        """Resize elements"""
        Window.onResize()

    def onBeforeTabSelected(self, sender, tab_index):
        return True

    def onTabSelected(self, sender, tab_index):
        selected = self.getSelected()
        for callback in self._selected_listeners:
            callback(selected)

    def onEventPreview(self, event):
        if event.type in ["keydown", "keypress", "keyup"] and event.keyCode == KEY_ESCAPE:
            #needed to prevent request cancellation in Firefox
            event.preventDefault()
        return True

    def getAvatar(self, jid_str):
        """Return avatar of a jid if in cache, else ask for it"""
        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] = "/media/misc/empty_avatar"
        return self.avatars_cache[jid_str]

    def registerWidget(self, wid):
        print "Registering", wid.getDebugName()
        self.libervia_widgets.add(wid)

    def unregisterWidget(self, wid):
        try:
            self.libervia_widgets.remove(wid)
        except KeyError:
            print ('WARNING: trying to remove a non registered Widget:', wid.getDebugName())

    def setUniBox(self, unibox):
        """register the unibox widget"""
        self.uni_box = unibox
        self.uni_box.addKey("@@: ")

    def addTab(self, wid, label):
        """Create a new tab and add a widget in
        @param wid: LiberviaWidget to add
        @param label: label of the tab"""
        _widgets_panel = WidgetsPanel(self)
        _widgets_panel.addWidget(wid)
        self.tab_panel.add(_widgets_panel, label)
        self.tab_panel.selectTab(self.tab_panel.getWidgetCount() - 1)

    def addWidget(self, wid):
        """ Add a widget at the bottom of the current tab
        @param wid: LiberviaWidget to add """
        panel = self.tab_panel.getCurrentPanel()
        panel.addWidget(wid)

    def _isRegisteredCB(self, registered):
        if not registered:
            self._register_box = RegisterBox(self.logged)
            self._register_box.centerBox()
            self._register_box.show()
        else:
            self._register.call('isConnected', self._isConnectedCB)

    def _isConnectedCB(self, connected):
        if not connected:
            self._register.call('connect', lambda x:self.logged())
        else:
            self.logged()

    def logged(self):
        if self._register_box:
            self._register_box.hide()
            del self._register_box  # don't work if self._register_box is None

        #it's time to fill the page
        self.bridge.call('getContacts', self._getContactsCB)
        self.bridge.call('getParamsUI', self._getParamsUICB)
        self.bridge_signals.call('getSignals', self._getSignalsCB)
        #We want to know our own jid
        self.bridge.call('getProfileJid', self._getProfileJidCB)

    def _getContactsCB(self, contacts_data):
        for contact in contacts_data:
            jid, attributes, groups = contact
            self._newContactCb(jid, attributes, groups)

    def _getSignalsCB(self, signal_data):
        self.bridge_signals.call('getSignals', self._getSignalsCB)
        print('Got signal ==> name: %s, params: %s' % (signal_data[0], signal_data[1]))
        name, args = signal_data
        if name == 'personalEvent':
            self._personalEventCb(*args)
        elif name == 'newMessage':
            self._newMessageCb(*args)
        elif name == 'presenceUpdate':
            self._presenceUpdateCb(*args)
        elif name == 'roomJoined':
            self._roomJoinedCb(*args)
        elif name == 'roomUserJoined':
            self._roomUserJoinedCb(*args)
        elif name == 'roomUserLeft':
            self._roomUserLeftCb(*args)
        elif name == 'tarotGameStarted':
            self._tarotGameStartedCb(*args)
        elif name == 'tarotGameNew' or \
             name == 'tarotGameChooseContrat' or \
             name == 'tarotGameShowCards' or \
             name == 'tarotGameInvalidCards' or \
             name == 'tarotGameCardsPlayed' or \
             name == 'tarotGameYourTurn' or \
             name == 'tarotGameScore':
            self._tarotGameGenericCb(name, args[0], args[1:])
        elif name == 'radiocolStarted':
            self._radioColStartedCb(*args)
        elif name == 'radiocolPreload':
            self._radioColGenericCb(name, args[0], args[1:])
        elif name == 'radiocolPlay':
            self._radioColGenericCb(name, args[0], args[1:])
        elif name == 'radiocolNoUpload':
            self._radioColGenericCb(name, args[0], args[1:])
        elif name == 'radiocolUploadOk':
            self._radioColGenericCb(name, args[0], args[1:])
        elif name == 'radiocolSongRejected':
            self._radioColGenericCb(name, args[0], args[1:])
        elif name == 'subscribe':
            self._subscribeCb(*args)
        elif name == 'contactDeleted':
            self._contactDeletedCb(*args)
        elif name == 'newContact':
            self._newContactCb(*args)
        elif name == 'entityDataUpdated':
            self._entityDataUpdatedCb(*args)
        elif name == 'chatStateReceived':
            self._chatStateReceivedCb(*args)

    def _getParamsUICB(self, xmlui):
        """Hide the parameters item if there's nothing to display"""
        if not xmlui:
            self.panel.menu.removeItemParams()

    def _ownBlogsFills(self, mblogs):
        #put our own microblogs in cache, then fill all panels with them
        for publisher in mblogs:
            for mblog in mblogs[publisher]:
                if not mblog.has_key('content'):
                    print ("WARNING: No content found in microblog [%s]", mblog)
                    continue
                if mblog.has_key('groups'):
                    _groups = set(mblog['groups'].split() if mblog['groups'] else [])
                else:
                    _groups = None
                mblog_entry = MicroblogItem(mblog)
                self.mblog_cache.append((_groups, mblog_entry))

        if len(self.mblog_cache) > MAX_MBLOG_CACHE:
            del self.mblog_cache[0:len(self.mblog_cache - MAX_MBLOG_CACHE)]
        for lib_wid in self.libervia_widgets:
            if isinstance(lib_wid, panels.MicroblogPanel):
                self.FillMicroblogPanel(lib_wid)
        self.initialised = True # initialisation phase is finished here
        for event_data in self.init_cache: # so we have to send all the cached events
            self._personalEventCb(*event_data)
        del self.init_cache

    def _getProfileJidCB(self, jid):
        self.whoami = JID(jid)
        #we can now ask our status
        self.bridge.call('getPresenceStatus', self._getPresenceStatusCb)
        #the rooms where we are
        self.bridge.call('getRoomsJoined', self._getRoomsJoinedCb)
        #and if there is any subscription request waiting for us
        self.bridge.call('getWaitingSub', self._getWaitingSubCb)
        #we fill the panels already here
        for lib_wid in self.libervia_widgets:
            if isinstance(lib_wid, panels.MicroblogPanel):
                if lib_wid.accept_all():
                    self.bridge.call('getMassiveLastMblogs', lib_wid.massiveInsert, 'ALL', [], 10)
                else:
                    self.bridge.call('getMassiveLastMblogs', lib_wid.massiveInsert, 'GROUP', lib_wid.accepted_groups, 10)

        #we ask for our own microblogs:
        self.bridge.call('getMassiveLastMblogs', self._ownBlogsFills, 'JID', [self.whoami.bare], 10)

    ## Signals callbacks ##

    def _personalEventCb(self, sender, event_type, data):
        if not self.initialised:
            self.init_cache.append((sender, event_type, data))
            return
        if event_type == "MICROBLOG":
            if not 'content' in data:
                print ("WARNING: No content found in microblog data")
                return
            if 'groups' in data:
                _groups = set(data['groups'].split() if data['groups'] else [])
            else:
                _groups = None
            mblog_entry = MicroblogItem(data)

            for lib_wid in self.libervia_widgets:
                if isinstance(lib_wid, panels.MicroblogPanel):
                    self.addBlogEntry(lib_wid, sender, _groups, mblog_entry)

            if sender == self.whoami.bare:
                self.mblog_cache.append((_groups, mblog_entry))
                if len(self.mblog_cache) > MAX_MBLOG_CACHE:
                    del self.mblog_cache[0:len(self.mblog_cache - MAX_MBLOG_CACHE)]

    def addBlogEntry(self, mblog_panel, sender, _groups, mblog_entry):
        """Check if an entry can go in MicroblogPanel and add to it
        @param mblog_panel: MicroblogPanel instance
        @param sender: jid of the entry sender
        @param _groups: groups which can receive this entry
        @param mblog_entry: MicroblogItem instance"""
        if mblog_entry.type == "comment" or mblog_panel.isJidAccepted(sender) or (_groups == None and self.whoami and sender == self.whoami.bare) \
           or (_groups and _groups.intersection(mblog_panel.accepted_groups)):
            mblog_panel.addEntry(mblog_entry)

    def FillMicroblogPanel(self, mblog_panel):
        """Fill a microblog panel with entries in cache
        @param mblog_panel: MicroblogPanel instance
        """
        #XXX: only our own entries are cached
        for cache_entry in self.mblog_cache:
            _groups, mblog_entry = cache_entry
            self.addBlogEntry(mblog_panel, self.whoami.bare, *cache_entry)

    def getEntityMBlog(self, entity):
        print "geting mblog for entity [%s]" % (entity,)
        for lib_wid in self.libervia_widgets:
            if isinstance(lib_wid, panels.MicroblogPanel):
                if lib_wid.isJidAccepted(entity):
                    self.bridge.call('getMassiveLastMblogs', lib_wid.massiveInsert, 'JID', [entity], 10)

    def getLiberviaWidget(self, class_, entity, ignoreOtherTabs=True):
        """Get the corresponding panel if it exists.
        @param class_: class of the panel (ChatPanel, MicroblogPanel...)
        @param entity: polymorphic parameter, see class_.matchEntity.
        @param ignoreOtherTabs: if True, the widgets that are not
        contained by the currently selected tab will be ignored
        @return: the existing widget that has been found or None."""
        selected_tab = self.tab_panel.getCurrentPanel()
        for lib_wid in self.libervia_widgets:
            parent = lib_wid.getWidgetsPanel(verbose=False)
            if parent is None or (ignoreOtherTabs and parent != selected_tab):
                # do not return a widget that is not in the currently selected tab
                continue
            if isinstance(lib_wid, class_):
                try:
                    if lib_wid.matchEntity(entity):
                        print "existing widget found: %s" % lib_wid.getDebugName()
                        return lib_wid
                except AttributeError as e:
                    e.stack_list()
                    return None
        return None

    def getOrCreateLiberviaWidget(self, class_, entity, add=True, refresh=True, add=True):
        """Get the matching LiberviaWidget if it exists, or create a new one.
        @param class_: class of the panel (ChatPanel, MicroblogPanel...)
        @param entity: polymorphic parameter, see class_.matchEntity.
        @param refresh: if True, refresh the display of a widget that is found
                        (ignored if no widget is found and a new one is created)
        @param add: if True, add a widget that is created to the selected tab
                    (ignored if a widget is found and no new one is created)
        @return: the newly created wigdet if REUSE_EXISTING_LIBERVIA_WIDGETS
         is set to False or if the widget has not been found, the existing
         widget that has been found otherwise."""
        lib_wid = None
        if REUSE_EXISTING_LIBERVIA_WIDGETS:
            lib_wid = self.getLiberviaWidget(class_, entity)
        if lib_wid is None:
            lib_wid = class_.createPanel(self, entity)
        elif refresh:
            # remove the widget from its previous panel
            panel = lib_wid.getWidgetsPanel(verbose=False)
            if panel is not None:
                panel.removeWidget(lib_wid)
        if add or refresh:
            self.addWidget(lib_wid)
        if refresh:
            # must be done after the widget is added,
            # for example to scroll to the bottom
            self.setSelected(lib_wid)
            lib_wid.refresh()
        return lib_wid

    def _newMessageCb(self, from_jid, msg, msg_type, to_jid, extra):
        _from = JID(from_jid)
        _to = JID(to_jid)
        other = _to if _from.bare == self.whoami.bare else _from
        lib_wid = self.getLiberviaWidget(panels.ChatPanel, other, ignoreOtherTabs=False)
        if lib_wid is not None:
            lib_wid.printMessage(_from, msg, extra)
        else:
            # The message has not been showed, we must indicate it
            self.contact_panel.setContactMessageWaiting(other.bare, True)

    def _presenceUpdateCb(self, entity, show, priority, statuses):
        _entity = JID(entity)
        #XXX: QnD way to get our status
        if self.whoami and self.whoami.bare == _entity.bare and statuses:
            self.status_panel.changeStatus(statuses.values()[0])
        if (not self.whoami or self.whoami.bare != _entity.bare):
            self.contact_panel.setConnected(_entity.bare, _entity.resource, show, priority, statuses)

    def _roomJoinedCb(self, room_jid, room_nicks, user_nick):
        _target = JID(room_jid)
        self.room_list.add(_target)
        chat_panel = panels.ChatPanel(self, _target, type_='group')
        chat_panel.setUserNick(user_nick)
        if _target.node.startswith('sat_tarot_'): #XXX: it's not really beautiful, but it works :)
            self.addTab(chat_panel, "Tarot")
        elif _target.node.startswith('sat_radiocol_'):
            self.addTab(chat_panel, "Radio collective")
        else:
            self.addTab(chat_panel, _target.node)
        chat_panel.setPresents(room_nicks)
        chat_panel.historyPrint()

    def _roomUserJoinedCb(self, room_jid_s, user_nick, user_data):
        for lib_wid in self.libervia_widgets:
            if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
                lib_wid.userJoined(user_nick, user_data)

    def _roomUserLeftCb(self, room_jid_s, user_nick, user_data):
        for lib_wid in self.libervia_widgets:
            if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
                lib_wid.userLeft(user_nick, user_data)

    def _tarotGameStartedCb(self, room_jid_s, referee, players):
        print ("Tarot Game Started \o/")
        for lib_wid in self.libervia_widgets:
            if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
                lib_wid.startGame("Tarot", referee, players)

    def _tarotGameGenericCb(self, event_name, room_jid_s, args):
        for lib_wid in self.libervia_widgets:
            if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
                getattr(lib_wid.getGame("Tarot"), event_name)(*args)

    def _radioColStartedCb(self, room_jid_s, referee):
        for lib_wid in self.libervia_widgets:
            if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
                lib_wid.startGame("RadioCol", referee)

    def _radioColGenericCb(self, event_name, room_jid_s, args):
        for lib_wid in self.libervia_widgets:
            if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
                getattr(lib_wid.getGame("RadioCol"), event_name)(*args)


    def _getPresenceStatusCb(self, presence_data):
        for entity in presence_data:
            for resource in presence_data[entity]:
                args = presence_data[entity][resource]
                self._presenceUpdateCb("%s/%s" % (entity, resource), *args)

    def _getRoomsJoinedCb(self, room_data):
        for room in room_data:
            self._roomJoinedCb(*room)

    def _getWaitingSubCb(self, waiting_sub):
        for sub in waiting_sub:
            self._subscribeCb(waiting_sub[sub], sub)

    def _subscribeCb(self, sub_type, entity):
        if sub_type == 'subscribed':
            dialog.InfoDialog('Subscription confirmation', 'The contact <b>%s</b> has added you to his/her contact list' % html_sanitize(entity)).show()
            self.getEntityMBlog(entity)

        elif sub_type == 'unsubscribed':
            dialog.InfoDialog('Subscription refusal', 'The contact <b>%s</b> has refused to add you in his/her contact list' % html_sanitize(entity)).show()
            #TODO: remove microblogs from panels

        elif sub_type == 'subscribe':
            #The user want to subscribe to our presence
            _dialog = None
            msg = HTML('The contact <b>%s</b> want to add you in his/her contact list, do you accept ?' % html_sanitize(entity))

            def ok_cb(ignore):
                self.bridge.call('subscription', None, "subscribed", entity, '', _dialog.getSelectedGroups())
            def cancel_cb(ignore):
                self.bridge.call('subscription', None, "unsubscribed", entity, '', '')

            _dialog = dialog.GroupSelector([msg], self.contact_panel.getGroups(), [], "Add", ok_cb, cancel_cb)
            _dialog.setHTML('<b>Add contact request</b>')
            _dialog.show()

    def _contactDeletedCb(self, entity):
        self.contact_panel.removeContact(entity)

    def _newContactCb(self, contact, attributes, groups):
        self.contact_panel.updateContact(contact, attributes, groups)

    def _entityDataUpdatedCb(self, entity_jid_s, key, value):
        if key == "avatar":
            avatar = '/avatars/%s' % value

            self.avatars_cache[entity_jid_s] = avatar

            for lib_wid in self.libervia_widgets:
                if isinstance(lib_wid, panels.MicroblogPanel):
                    if lib_wid.isJidAccepted(entity_jid_s) or (self.whoami and entity_jid_s == self.whoami.bare):
                        lib_wid.updateValue('avatar', entity_jid_s, avatar)

    def _chatStateReceivedCb(self, from_jid_s, state, profile):
        """Callback when a new chat state is received.
        @param from_jid_s: JID from the contact who sent his state
        @param state: new state
        @profile: current profile
        """
        _from = JID(from_jid_s).bare if from_jid_s != "@ALL@" else from_jid_s
        for lib_wid in self.libervia_widgets:
            if isinstance(lib_wid, panels.ChatPanel):
                win_from = lib_wid.target.bare
                good_win = win_from == _from or _from == "@ALL@"
                if (good_win and lib_wid.type == 'one2one'):
                    if state:
                        lib_wid.setTitle(win_from + " (" + state + ")")
                    else:
                        lib_wid.setTitle(win_from)
                    # start to send "composing" state from now
                    lib_wid.state_machine.started = True
                elif (lib_wid.type == 'group'):
                    # TODO: chat state notification for groupchat
                    pass


if __name__ == '__main__':
    pyjd.setup("http://localhost:8080/libervia.html")
    app = SatWebFrontend()
    app.onModuleLoad()
    pyjd.run()