view frontends/src/quick_frontend/quick_app.py @ 1265:e3a9ea76de35 frontends_multi_profiles

quick_frontend, primitivus: multi-profiles refactoring part 1 (big commit, sorry :p): This refactoring allow primitivus to manage correctly several profiles at once, with various other improvments: - profile_manager can now plug several profiles at once, requesting password when needed. No more profile plug specific method is used anymore in backend, instead a "validated" key is used in actions - Primitivus widget are now based on a common "PrimitivusWidget" classe which mainly manage the decoration so far - all widgets are treated in the same way (contactList, Chat, Progress, etc), no more chat_wins specific behaviour - widgets are created in a dedicated manager, with facilities to react on new widget creation or other events - quick_frontend introduce a new QuickWidget class, which aims to be as generic and flexible as possible. It can manage several targets (jids or something else), and several profiles - each widget class return a Hash according to its target. For example if given a target jid and a profile, a widget class return a hash like (target.bare, profile), the same widget will be used for all resources of the same jid - better management of CHAT_GROUP mode for Chat widgets - some code moved from Primitivus to QuickFrontend, the final goal is to have most non backend code in QuickFrontend, and just graphic code in subclasses - no more (un)escapePrivate/PRIVATE_PREFIX - contactList improved a lot: entities not in roster and special entities (private MUC conversations) are better managed - resources can be displayed in Primitivus, and their status messages - profiles are managed in QuickFrontend with dedicated managers This is work in progress, other frontends are broken. Urwid SàText need to be updated. Most of features of Primitivus should work as before (or in a better way ;))
author Goffi <goffi@goffi.org>
date Wed, 10 Dec 2014 19:00:09 +0100
parents e56dfe0378a1
children faa1129559b8
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.bridge.DBus import DBusBridgeFrontend
from sat_frontends.tools import jid
from sat_frontends.quick_frontend.quick_widgets import QuickWidgetsManager
from sat_frontends.quick_frontend import quick_chat
from optparse import OptionParser

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
            waitingSub = self.bridge.getWaitingSub(self.profile)
            for sub in waitingSub:
                self.host.subscribeHandler(waitingSub[sub], sub, self.profile)

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

            for subject_args in self.bridge.getRoomsSubjects(self.profile):
                self.host.roomNewSubjectHandler(*subject_args, profile=self.profile)

            #Finaly, we get the waiting confirmation requests
            for confirm_id, confirm_type, data in self.bridge.getWaitingConf(self.profile):
                self.host.askConfirmationHandler(confirm_id, confirm_type, data, 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):
        ProfileManager.host = self
        self.profiles = ProfilesManager()
        self.contact_lists = {}
        self.widgets = QuickWidgetsManager(self)
        self.check_options()

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

        ## bridge ##
        try:
            self.bridge = DBusBridgeFrontend()
        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()

    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, "%s%s" % (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.profile:
            profile_manager.autoconnect([self.options.profile])

    def check_options(self):
        """Check command line options"""
        usage = _("""
        %prog [options]

        %prog --help for options list
        """)
        parser = OptionParser(usage=usage) # TODO: use argparse

        parser.add_option("-p", "--profile", help=_("Select the profile to use"))

        (self.options, args) = parser.parse_args()
        if self.options.profile:
            self.options.profile = self.options.profile.decode('utf-8')
        return args

    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
        """
        raise NotImplementedError

    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 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: None
        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.visible_widgets:
            if isinstance(widget, 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]:
                filename = self.bridge.getAvatarFile(value)
                self.contact_lists[profile].setCache(entity, 'avatar', filename)

    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)