Mercurial > libervia-backend
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)