view frontends/src/quick_frontend/quick_app.py @ 1304:1a61b18703c4 frontends_multi_profiles

quick frontend (quick widgets): class' __name__ method is used for classes_map hash because the use of class directly was causing bugs with pyjamas (difficult to find, several MicroblogPanel instances were added only once in Libervia's TabPanel, hash method seemed buggy)
author Goffi <goffi@goffi.org>
date Fri, 06 Feb 2015 19:05:51 +0100
parents faa1129559b8
children 6c7d89843f1b
line wrap: on
line source

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

# helper class for making a SAT frontend
# Copyright (C) 2009, 2010, 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/>.

from sat.core.i18n import _
import sys
from sat.core.log import getLogger
log = getLogger(__name__)
from sat.core import exceptions
from sat_frontends.tools import jid
from sat_frontends.quick_frontend.quick_widgets import QuickWidgetsManager
from sat_frontends.quick_frontend import quick_chat

from sat_frontends.quick_frontend.constants import Const as C


class ProfileManager(object):
    """Class managing all data relative to one profile, and plugging in mechanism"""
    host = None
    bridge = None

    def __init__(self, profile):
        self.profile = profile
        self.whoami = None
        self.data = {}

    def __getitem__(self, key):
        return self.data[key]

    def __setitem__(self, key, value):
        self.data[key] = value

    def plug(self):
        """Plug the profile to the host"""
        # we get the essential params
        self.bridge.asyncGetParamA("JabberID", "Connection", profile_key=self.profile,
                                   callback=self._plug_profile_jid, errback=self._getParamError)

    def _plug_profile_jid(self, _jid):
        self.whoami = jid.JID(_jid)
        self.bridge.asyncGetParamA("autoconnect", "Connection", profile_key=self.profile,
                                   callback=self._plug_profile_autoconnect, errback=self._getParamError)

    def _plug_profile_autoconnect(self, value_str):
        autoconnect = C.bool(value_str)
        if autoconnect and not self.bridge.isConnected(self.profile):
            self.host.asyncConnect(self.profile, callback=lambda dummy: self._plug_profile_afterconnect())
        else:
            self._plug_profile_afterconnect()

    def _plug_profile_afterconnect(self):
        # Profile can be connected or not
        # TODO: watched plugin
        contact_list = self.host.addContactList(self.profile)

        if not self.bridge.isConnected(self.profile):
            self.host.setStatusOnline(False, profile=self.profile)
        else:
            self.host.setStatusOnline(True, profile=self.profile)

            contact_list.fill()

            #The waiting subscription requests
            self.bridge.getWaitingSub(self.profile, callback=self._plug_profile_gotWaitingSub)

    def _plug_profile_gotWaitingSub(self, waiting_sub):
        for sub in waiting_sub:
            self.host.subscribeHandler(waiting_sub[sub], sub, self.profile)

        self.bridge.getRoomsJoined(self.profile, callback=self._plug_profile_gotRoomsJoined)

    def _plug_profile_gotRoomsJoined(self, rooms_args):
        #Now we open the MUC window where we already are:
        for room_args in rooms_args:
            self.host.roomJoinedHandler(*room_args, profile=self.profile)

        self.bridge.getRoomsSubjects(self.profile, callback=self._plug_profile_gotRoomsSubjects)

    def _plug_profile_gotRoomsSubjects(self, subjects_args):
        for subject_args in subjects_args:
            self.host.roomNewSubjectHandler(*subject_args, profile=self.profile)

        #Presence must be requested after rooms are filled
        self.host.bridge.getPresenceStatuses(self.profile, callback=self._plug_profile_gotPresences)


    def _plug_profile_gotPresences(self, presences):
        def gotEntityData(data, contact):
            for key in ('avatar', 'nick'):
                if key in data:
                    self.host.entityDataUpdatedHandler(contact, key, data[key], self.profile)

        for contact in presences:
            for res in presences[contact]:
                jabber_id = ('%s/%s' % (jid.JID(contact).bare, res)) if res else contact
                show = presences[contact][res][0]
                priority = presences[contact][res][1]
                statuses = presences[contact][res][2]
                self.host.presenceUpdateHandler(jabber_id, show, priority, statuses, self.profile)
            self.host.bridge.getEntityData(contact, ['avatar', 'nick'], self.profile, callback=lambda data, contact=contact: gotEntityData(data, contact), errback=lambda failure, contact=contact: log.debug("No cache data for {}".format(contact)))

        #Finaly, we get the waiting confirmation requests
        self.bridge.getWaitingConf(self.profile, callback=self._plug_profile_gotWaitingConf)

    def _plug_profile_gotWaitingConf(self, waiting_confs):
        for confirm_id, confirm_type, data in waiting_confs:
            self.host.askConfirmationHandler(confirm_id, confirm_type, data, self.profile)

        # At this point, profile should be fully plugged
        # and we launch frontend specific method
        self.host.profilePlugged(self.profile)

    def _getParamError(self, ignore):
        log.error(_("Can't get profile parameter"))


class ProfilesManager(object):
    """Class managing collection of profiles"""

    def __init__(self):
        self._profiles = {}

    def __contains__(self, profile):
        return profile in self._profiles

    def __iter__(self):
        return self._profiles.iterkeys()

    def __getitem__(self, profile):
        return self._profiles[profile]

    def __len__(self):
        return len(self._profiles)

    def plug(self, profile):
        if profile in self._profiles:
            raise exceptions.ConflictError('A profile of the name [{}] is already plugged'.format(profile))
        self._profiles[profile] = ProfileManager(profile)
        self._profiles[profile].plug()

    def unplug(self, profile):
        if profile not in self._profiles:
            raise ValueError('The profile [{}] is not plugged'.format(profile))
        del self._profiles[profile]

    def chooseOneProfile(self):
        return self._profiles.keys()[0]

class QuickApp(object):
    """This class contain the main methods needed for the frontend"""

    def __init__(self, create_bridge, check_options=None):
        """Create a frontend application

        @param create_bridge: method to use to create the Bridge
        @param check_options: method to call to check options (usually command line arguments)
        """
        ProfileManager.host = self
        self.profiles = ProfilesManager()
        self.contact_lists = {}
        self.widgets = QuickWidgetsManager(self)
        if check_options is not None:
            self.options = check_options()
        else:
            self.options = None

        # widgets
        self.selected_widget = None # widget currently selected (must be filled by frontend)

        ## bridge ##
        try:
            self.bridge = create_bridge()
        except exceptions.BridgeExceptionNoService:
            print(_(u"Can't connect to SàT backend, are you sure it's launched ?"))
            sys.exit(1)
        except exceptions.BridgeInitError:
            print(_(u"Can't init bridge"))
            sys.exit(1)
        ProfileManager.bridge = self.bridge
        self.registerSignal("connected")
        self.registerSignal("disconnected")
        self.registerSignal("newContact")
        self.registerSignal("newMessage", self._newMessage)
        self.registerSignal("newAlert")
        self.registerSignal("presenceUpdate")
        self.registerSignal("subscribe")
        self.registerSignal("paramUpdate")
        self.registerSignal("contactDeleted")
        self.registerSignal("entityDataUpdated")
        self.registerSignal("askConfirmation")
        self.registerSignal("actionResult")
        self.registerSignal("actionResultExt", self.actionResultHandler)
        self.registerSignal("roomJoined", iface="plugin")
        self.registerSignal("roomLeft", iface="plugin")
        self.registerSignal("roomUserJoined", iface="plugin")
        self.registerSignal("roomUserLeft", iface="plugin")
        self.registerSignal("roomUserChangedNick", iface="plugin")
        self.registerSignal("roomNewSubject", iface="plugin")
        self.registerSignal("tarotGameStarted", iface="plugin")
        self.registerSignal("tarotGameNew", iface="plugin")
        self.registerSignal("tarotGameChooseContrat", iface="plugin")
        self.registerSignal("tarotGameShowCards", iface="plugin")
        self.registerSignal("tarotGameYourTurn", iface="plugin")
        self.registerSignal("tarotGameScore", iface="plugin")
        self.registerSignal("tarotGameCardsPlayed", iface="plugin")
        self.registerSignal("tarotGameInvalidCards", iface="plugin")
        self.registerSignal("quizGameStarted", iface="plugin")
        self.registerSignal("quizGameNew", iface="plugin")
        self.registerSignal("quizGameQuestion", iface="plugin")
        self.registerSignal("quizGamePlayerBuzzed", iface="plugin")
        self.registerSignal("quizGamePlayerSays", iface="plugin")
        self.registerSignal("quizGameAnswerResult", iface="plugin")
        self.registerSignal("quizGameTimerExpired", iface="plugin")
        self.registerSignal("quizGameTimerRestarted", iface="plugin")
        self.registerSignal("chatStateReceived", iface="plugin")

        self.current_action_ids = set() # FIXME: to be removed
        self.current_action_ids_cb = {} # FIXME: to be removed
        self.media_dir = self.bridge.getConfig('', 'media_dir')

    @property
    def current_profile(self):
        """Profile that a user would expect to use"""
        try:
            return self.selected_widget.profile
        except (TypeError, AttributeError):
            return self.profiles.chooseOneProfile()

    @property
    def visible_widgets(self):
        """widgets currently visible (must be implemented by frontend)"""
        raise NotImplementedError

    def registerSignal(self, functionName, handler=None, iface="core", with_profile=True):
        """Register a handler for a signal

        @param functionName (str): name of the signal to handle
        @param handler (instancemethod): method to call when the signal arrive, None for calling an automatically named handler (functionName + 'Handler')
        @param iface (str): interface of the bridge to use ('core' or 'plugin')
        @param with_profile (boolean): True if the signal concerns a specific profile, in that case the profile name has to be passed by the caller
        """
        if handler is None:
            handler = getattr(self, "{}{}".format(functionName, 'Handler'))
        if not with_profile:
            self.bridge.register(functionName, handler, iface)
            return

        def signalReceived(*args, **kwargs):
            profile = kwargs.get('profile')
            if profile is None:
                if not args:
                    raise exceptions.ProfileNotSetError
                profile = args[-1]
            if profile is not None and not self.check_profile(profile):
                return  # we ignore signal for profiles we don't manage
            handler(*args, **kwargs)
        self.bridge.register(functionName, signalReceived, iface)

    def check_profile(self, profile):
        """Tell if the profile is currently followed by the application"""
        return profile in self.profiles

    def postInit(self, profile_manager):
        """Must be called after initialization is done, do all automatic task (auto plug profile)

        @param profile_manager: instance of a subclass of Quick_frontend.QuickProfileManager
        """
        if self.options and self.options.profile:
            profile_manager.autoconnect([self.options.profile])

    def profilePlugged(self, profile):
        """Method called when the profile is fully plugged, to launch frontend specific workflow

        @param profile(unicode): %(doc_profile)s
        """
        pass

    def asyncConnect(self, profile, callback=None, errback=None):
        if not callback:
            callback = lambda dummy: None
        if not errback:
            def errback(failure):
                log.error(_(u"Can't connect profile [%s]") % failure)
                if failure.module.startswith('twisted.words.protocols.jabber') and failure.condition == "not-authorized":
                    self.launchAction(C.CHANGE_XMPP_PASSWD_ID, {}, profile=profile)
                else:
                    self.showDialog(failure.message, failure.fullname, 'error')
        self.bridge.asyncConnect(profile, callback=callback, errback=errback)

    def plug_profiles(self, profiles):
        """Tell application which profiles must be used

        @param profiles: list of valid profile names
        """
        self.plugging_profiles()
        for profile in profiles:
            self.profiles.plug(profile)

    def plugging_profiles(self):
        """Method to subclass to manage frontend specific things to do

        will be called when profiles are choosen and are to be plugged soon
        """
        pass

    def unplug_profile(self, profile):
        """Tell the application to not follow anymore the profile"""
        if not profile in self.profiles:
            raise ValueError("The profile [{}] is not plugged".format(profile))
        self.profiles.remove(profile)

    def clear_profile(self):
        self.profiles.clear()

    def addContactList(self, profile):
        """Method to subclass to add a contact list widget

        will be called on each profile session build
        @return: a ContactList widget
        """
        return NotImplementedError

    def newWidget(self, widget):
        raise NotImplementedError

    def connectedHandler(self, profile):
        """called when the connection is made"""
        log.debug(_("Connected"))
        self.setStatusOnline(True, profile=profile)

    def disconnectedHandler(self, profile):
        """called when the connection is closed"""
        log.debug(_("Disconnected"))
        self.contact_lists[profile].clearContacts()
        self.setStatusOnline(False, profile=profile)

    def newContactHandler(self, JabberId, attributes, groups, profile):
        entity = jid.JID(JabberId)
        _groups = list(groups)
        self.contact_lists[profile].setContact(entity, _groups, attributes, in_roster=True)

    def _newMessage(self, from_jid_s, msg, type_, to_jid_s, extra, profile):
        from_jid = jid.JID(from_jid_s)
        to_jid = jid.JID(to_jid_s)
        self.newMessageHandler(from_jid, to_jid, msg, type_, extra, profile)

    def newMessageHandler(self, from_jid, to_jid, msg, type_, extra, profile):
        from_me = from_jid.bare == self.profiles[profile].whoami.bare
        target = to_jid if from_me else from_jid

        chat_type = C.CHAT_GROUP if type_ == C.MESS_TYPE_GROUPCHAT else C.CHAT_ONE2ONE

        chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, target, type_=chat_type, profile=profile)

        self.current_action_ids = set() # FIXME: to be removed
        self.current_action_ids_cb = {} # FIXME: to be removed

        chat_widget.newMessage(from_jid, target, msg, type_, extra, profile)

    def sendMessage(self, to_jid, message, subject='', mess_type="auto", extra={}, callback=None, errback=None, profile_key=C.PROF_KEY_NONE):
        if callback is None:
            callback = lambda dummy=None: None # FIXME: optional argument is here because pyjamas doesn't support callback without arg with json proxy
        if errback is None:
            errback = lambda failure: self.showDialog(failure.fullname, failure.message, "error")
        self.bridge.sendMessage(to_jid, message, subject, mess_type, extra, profile_key, callback=callback, errback=errback)

    def newAlertHandler(self, msg, title, alert_type, profile):
        assert alert_type in ['INFO', 'ERROR']
        self.showDialog(unicode(msg), unicode(title), alert_type.lower())

    def setStatusOnline(self, online=True, show="", statuses={}, profile=C.PROF_KEY_NONE):
        raise NotImplementedError

    def presenceUpdateHandler(self, entity_s, show, priority, statuses, profile):

        log.debug(_("presence update for %(entity)s (show=%(show)s, priority=%(priority)s, statuses=%(statuses)s) [profile:%(profile)s]")
              % {'entity': entity_s, C.PRESENCE_SHOW: show, C.PRESENCE_PRIORITY: priority, C.PRESENCE_STATUSES: statuses, 'profile': profile})
        entity = jid.JID(entity_s)

        if entity == self.profiles[profile].whoami:
            if show == "unavailable":
                self.setStatusOnline(False, profile=profile)
            else:
                self.setStatusOnline(True, show, statuses, profile=profile)
            return

        # #FIXME: must be moved in a plugin
        # if entity.bare in self.profiles[profile].data.get('watched',[]) and not entity.bare in self.profiles[profile]['onlineContact']:
        #     self.showAlert(_("Watched jid [%s] is connected !") % entity.bare)

        self.contact_lists[profile].updatePresence(entity, show, priority, statuses)

    def roomJoinedHandler(self, room_jid_s, room_nicks, user_nick, profile):
        """Called when a MUC room is joined"""
        log.debug("Room [%(room_jid)s] joined by %(profile)s, users presents:%(users)s" % {'room_jid': room_jid_s, 'profile': profile, 'users': room_nicks})
        room_jid = jid.JID(room_jid_s)
        chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_='group', profile=profile)
        chat_widget.setUserNick(user_nick)
        chat_widget.id = room_jid # FIXME: to be removed
        chat_widget.setPresents(list(set([user_nick] + room_nicks)))
        self.contact_lists[profile].setSpecial(room_jid, C.CONTACT_SPECIAL_GROUP)

    def roomLeftHandler(self, room_jid_s, profile):
        """Called when a MUC room is left"""
        log.debug("Room [%(room_jid)s] left by %(profile)s" % {'room_jid': room_jid_s, 'profile': profile})
        del self.chat_wins[room_jid_s]
        self.contact_lists[profile].remove(jid.JID(room_jid_s))

    def roomUserJoinedHandler(self, room_jid_s, user_nick, user_data, profile):
        """Called when an user joined a MUC room"""
        room_jid = jid.JID(room_jid_s)
        chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_='group', profile=profile)
        chat_widget.replaceUser(user_nick)
        log.debug("user [%(user_nick)s] joined room [%(room_jid)s]" % {'user_nick': user_nick, 'room_jid': room_jid})

    def roomUserLeftHandler(self, room_jid_s, user_nick, user_data, profile):
        """Called when an user joined a MUC room"""
        room_jid = jid.JID(room_jid_s)
        chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_='group', profile=profile)
        chat_widget.removeUser(user_nick)
        log.debug("user [%(user_nick)s] left room [%(room_jid)s]" % {'user_nick': user_nick, 'room_jid': room_jid})

    def roomUserChangedNickHandler(self, room_jid_s, old_nick, new_nick, profile):
        """Called when an user joined a MUC room"""
        room_jid = jid.JID(room_jid_s)
        chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_='group', profile=profile)
        chat_widget.changeUserNick(old_nick, new_nick)
        log.debug("user [%(old_nick)s] is now known as [%(new_nick)s] in room [%(room_jid)s]" % {'old_nick': old_nick, 'new_nick': new_nick, 'room_jid': room_jid})

    def roomNewSubjectHandler(self, room_jid_s, subject, profile):
        """Called when subject of MUC room change"""
        room_jid = jid.JID(room_jid_s)
        chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_='group', profile=profile)
        chat_widget.setSubject(subject)
        log.debug("new subject for room [%(room_jid)s]: %(subject)s" % {'room_jid': room_jid, "subject": subject})

    def tarotGameStartedHandler(self, room_jid_s, referee, players, profile):
        log.debug(_("Tarot Game Started \o/"))
        room_jid = jid.JID(room_jid_s)
        chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_='group', profile=profile)
        chat_widget.startGame("Tarot", referee, players)
        log.debug("new Tarot game started by [%(referee)s] in room [%(room_jid)s] with %(players)s" % {'referee': referee, 'room_jid': room_jid, 'players': [str(player) for player in players]})

    def tarotGameNewHandler(self, room_jid, hand, profile):
        log.debug(_("New Tarot Game"))
        if room_jid in self.chat_wins:
            self.chat_wins[room_jid].getGame("Tarot").newGame(hand)

    def tarotGameChooseContratHandler(self, room_jid, xml_data, profile):
        """Called when the player has to select his contrat"""
        log.debug(_("Tarot: need to select a contrat"))
        if room_jid in self.chat_wins:
            self.chat_wins[room_jid].getGame("Tarot").chooseContrat(xml_data)

    def tarotGameShowCardsHandler(self, room_jid, game_stage, cards, data, profile):
        log.debug(_("Show cards"))
        if room_jid in self.chat_wins:
            self.chat_wins[room_jid].getGame("Tarot").showCards(game_stage, cards, data)

    def tarotGameYourTurnHandler(self, room_jid, profile):
        log.debug(_("My turn to play"))
        if room_jid in self.chat_wins:
            self.chat_wins[room_jid].getGame("Tarot").myTurn()

    def tarotGameScoreHandler(self, room_jid, xml_data, winners, loosers, profile):
        """Called when the game is finished and the score are updated"""
        log.debug(_("Tarot: score received"))
        if room_jid in self.chat_wins:
            self.chat_wins[room_jid].getGame("Tarot").showScores(xml_data, winners, loosers)

    def tarotGameCardsPlayedHandler(self, room_jid, player, cards, profile):
        log.debug(_("Card(s) played (%(player)s): %(cards)s") % {"player": player, "cards": cards})
        if room_jid in self.chat_wins:
            self.chat_wins[room_jid].getGame("Tarot").cardsPlayed(player, cards)

    def tarotGameInvalidCardsHandler(self, room_jid, phase, played_cards, invalid_cards, profile):
        log.debug(_("Cards played are not valid: %s") % invalid_cards)
        if room_jid in self.chat_wins:
            self.chat_wins[room_jid].getGame("Tarot").invalidCards(phase, played_cards, invalid_cards)

    def quizGameStartedHandler(self, room_jid, referee, players, profile):
        log.debug(_("Quiz Game Started \o/"))
        if room_jid in self.chat_wins:
            self.chat_wins[room_jid].startGame("Quiz", referee, players)
            log.debug(_("new Quiz game started by [%(referee)s] in room [%(room_jid)s] with %(players)s") % {'referee': referee, 'room_jid': room_jid, 'players': [str(player) for player in players]})

    def quizGameNewHandler(self, room_jid, data, profile):
        log.debug(_("New Quiz Game"))
        if room_jid in self.chat_wins:
            self.chat_wins[room_jid].getGame("Quiz").quizGameNewHandler(data)

    def quizGameQuestionHandler(self, room_jid, question_id, question, timer, profile):
        """Called when a new question is asked"""
        log.debug(_(u"Quiz: new question: %s") % question)
        if room_jid in self.chat_wins:
            self.chat_wins[room_jid].getGame("Quiz").quizGameQuestionHandler(question_id, question, timer)

    def quizGamePlayerBuzzedHandler(self, room_jid, player, pause, profile):
        """Called when a player pushed the buzzer"""
        if room_jid in self.chat_wins:
            self.chat_wins[room_jid].getGame("Quiz").quizGamePlayerBuzzedHandler(player, pause)

    def quizGamePlayerSaysHandler(self, room_jid, player, text, delay, profile):
        """Called when a player say something"""
        if room_jid in self.chat_wins:
            self.chat_wins[room_jid].getGame("Quiz").quizGamePlayerSaysHandler(player, text, delay)

    def quizGameAnswerResultHandler(self, room_jid, player, good_answer, score, profile):
        """Called when a player say something"""
        if room_jid in self.chat_wins:
            self.chat_wins[room_jid].getGame("Quiz").quizGameAnswerResultHandler(player, good_answer, score)

    def quizGameTimerExpiredHandler(self, room_jid, profile):
        """Called when nobody answered the question in time"""
        if room_jid in self.chat_wins:
            self.chat_wins[room_jid].getGame("Quiz").quizGameTimerExpiredHandler()

    def quizGameTimerRestartedHandler(self, room_jid, time_left, profile):
        """Called when the question is not answered, and we still have time"""
        if room_jid in self.chat_wins:
            self.chat_wins[room_jid].getGame("Quiz").quizGameTimerRestartedHandler(time_left)

    def chatStateReceivedHandler(self, from_jid_s, state, profile):
        """Called when a new chat state is received.

        @param from_jid_s: JID of the contact who sent his state, or '@ALL@'
        @param state: new state (string)
        @profile: current profile
        """
        from_jid = jid.JID(from_jid_s) if from_jid_s != C.ENTITY_ALL else C.ENTITY_ALL
        for widget in self.widgets.getWidgets(quick_chat.QuickChat):
            if from_jid == C.ENTITY_ALL or from_jid.bare == widget.target.bare:
                widget.updateChatState(from_jid, state)

    def _subscribe_cb(self, answer, data):
        entity, profile = data
        if answer:
            self.bridge.subscription("subscribed", entity.bare, profile_key=profile)
        else:
            self.bridge.subscription("unsubscribed", entity.bare, profile_key=profile)

    def subscribeHandler(self, type, raw_jid, profile):
        """Called when a subsciption management signal is received"""
        entity = jid.JID(raw_jid)
        if type == "subscribed":
            # this is a subscription confirmation, we just have to inform user
            self.showDialog(_("The contact %s has accepted your subscription") % entity.bare, _('Subscription confirmation'))
        elif type == "unsubscribed":
            # this is a subscription refusal, we just have to inform user
            self.showDialog(_("The contact %s has refused your subscription") % entity.bare, _('Subscription refusal'), 'error')
        elif type == "subscribe":
            # this is a subscriptionn request, we have to ask for user confirmation
            self.showDialog(_("The contact %s wants to subscribe to your presence.\nDo you accept ?") % entity.bare, _('Subscription confirmation'), 'yes/no', answer_cb=self._subscribe_cb, answer_data=(entity, profile))

    def showDialog(self, message, title, type="info", answer_cb=None):
        raise NotImplementedError

    def showAlert(self, message):
        pass  #FIXME

    def paramUpdateHandler(self, name, value, namespace, profile):
        log.debug(_("param update: [%(namespace)s] %(name)s = %(value)s") % {'namespace': namespace, 'name': name, 'value': value})
        if (namespace, name) == ("Connection", "JabberID"):
            log.debug(_("Changing JID to %s") % value)
            self.profiles[profile].whoami = jid.JID(value)
        elif (namespace, name) == ("Misc", "Watched"):
            self.profiles[profile]['watched'] = value.split()
        elif (namespace, name) == ('General', C.SHOW_OFFLINE_CONTACTS):
            self.contact_lists[profile].showOfflineContacts(C.bool(value))
        elif (namespace, name) == ('General', C.SHOW_EMPTY_GROUPS):
            self.contact_lists[profile].showEmptyGroups(C.bool(value))

    def contactDeletedHandler(self, jid, profile):
        target = jid.JID(jid)
        self.contact_lists[profile].remove(target)

    def entityDataUpdatedHandler(self, entity_s, key, value, profile):
        entity = jid.JID(entity_s)
        if key == "nick":
            if entity in self.contact_lists[profile]:
                self.contact_lists[profile].setCache(entity, 'nick', value)
        elif key == "avatar":
            if entity in self.contact_lists[profile]:
                def gotFilename(filename):
                    self.contact_lists[profile].setCache(entity, 'avatar', filename)
                self.bridge.getAvatarFile(value, callback=gotFilename)

    def askConfirmationHandler(self, confirm_id, confirm_type, data, profile):
        raise NotImplementedError

    def actionResultHandler(self, type, id, data, profile):
        raise NotImplementedError

    def launchAction(self, callback_id, data=None, callback=None, profile="@NONE@"):
        """Launch a dynamic action
        @param callback_id: id of the action to launch
        @param data: data needed only for certain actions
        @param callback: if not None and 'validated' key is present, it will be called with the following parameters:
            - callback_id
            - data
            - profile_key
        @param profile_key: %(doc_profile)s

        """
        raise NotImplementedError

    def onExit(self):
        """Must be called when the frontend is terminating"""
        for profile in self.profiles:
            if self.bridge.isConnected(profile):
                if C.bool(self.bridge.getParamA("autodisconnect", "Connection", profile_key=profile)):
                    #The user wants autodisconnection
                    self.bridge.disconnect(profile)