Mercurial > libervia-backend
view frontends/src/quick_frontend/quick_app.py @ 1422:be1fccf4854d
tmp (wokkel): licenses fixes:
the licenses headers were wrong, it was fixed: original work from Adrien Cossa is directly under AGPL v3 (with his agreement), work derivated from Wokkel is sublicensed to AGPL v3 as allowed by the original license, to stay consistent with the rest of the code base.
Theses files (and only these ones) can be relicensed again to fill Wokkel license if Ralph plan to merge them upstream...
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 23 Apr 2015 10:57:40 +0200 |
parents | 6adf1b0be609 |
children | 798e5e38516b |
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, 2015 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.log import getLogger log = getLogger(__name__) from sat.core.i18n import _ from sat.core import exceptions from sat.tools import trigger from sat_frontends.tools import jid from sat_frontends.quick_frontend import quick_widgets from sat_frontends.quick_frontend import quick_menus from sat_frontends.quick_frontend import quick_chat, quick_games from sat_frontends.quick_frontend.constants import Const as C import sys from collections import OrderedDict try: # FIXME: to be removed when an acceptable solution is here unicode('') # XXX: unicode doesn't exist in pyjamas except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options unicode = str class ProfileManager(object): """Class managing all data relative to one profile, and plugging in mechanism""" host = None bridge = None cache_keys_to_get = ['avatar'] 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 # we get cached data self.host.bridge.getEntitiesData([], ProfileManager.cache_keys_to_get, profile=self.profile, callback=self._plug_profile_gotCachedValues, errback=self._plug_profile_failedCachedValues) def _plug_profile_failedCachedValues(self, failure): log.error(u"Couldn't get cached values: {}".format(failure)) self._plug_profile_gotCachedValues({}) def _plug_profile_gotCachedValues(self, cached_values): # TODO: watched plugin # add the contact list and its listener contact_list = self.host.addContactList(self.profile) self.host.contact_lists[self.profile] = contact_list for entity, data in cached_values.iteritems(): for key, value in data.iteritems(): contact_list.setCache(jid.JID(entity), key, value) if not self.bridge.isConnected(self.profile): self.host.setPresenceStatus(C.PRESENCE_UNAVAILABLE, '', profile=self.profile) else: self.host.setPresenceStatus(profile=self.profile) #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(u"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)) # remove the contact list and its listener host = self._profiles[profile].host host.contact_lists[profile].onDelete() del host.contact_lists[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) """ self.menus = quick_menus.QuickMenusManager(self) ProfileManager.host = self self.profiles = ProfilesManager() self.ready_profiles = set() # profiles which are connected and ready self.signals_cache = {} # used to keep signal received between start of plug_profile and when the profile is actualy ready self.contact_lists = {} self.widgets = quick_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) # listeners self._listeners = {} # key: listener type ("avatar", "selected", etc), value: list of callbacks # triggers self.trigger = trigger.TriggerManager() # trigger are used to change the default behaviour ## 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.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("chatStateReceived", iface="plugin") self.registerSignal("personalEvent", iface="plugin") # FIXME: do it dynamically quick_games.Tarot.registerSignals(self) quick_games.Quiz.registerSignals(self) quick_games.Radiocol.registerSignals(self) 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, function_name, handler=None, iface="core", with_profile=True): """Register a handler for a signal @param function_name (str): name of the signal to handle @param handler (instancemethod): method to call when the signal arrive, None for calling an automatically named handler (function_name + '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(function_name, 'Handler')) if not with_profile: self.bridge.register(function_name, 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: if not self.check_profile(profile): if profile in self.profiles: # profile is not ready but is in self.profiles, that's mean that it's being connecting and we need to cache the signal self.signals_cache.setdefault(profile, []).append((function_name, handler, args, kwargs)) return # we ignore signal for profiles we don't manage handler(*args, **kwargs) self.bridge.register(function_name, signalReceived, iface) def addListener(self, type_, callback, profiles_filter=None): """Add a listener for an event /!\ don't forget to remove listener when not used anymore (e.g. if you delete a widget) @param type_: type of event, can be: - avatar: called when avatar data is updated args: (entity, avatar file, profile) - nick: called when nick data is updated args: (entity, new_nick, profile) - presence: called when a presence is received args: (entity, show, priority, statuses, profile) - menu: called when a menu item is added or removed args: (type_, path, path_i18n, item) were values are: type_: same as in [sat.core.sat_main.SAT.importMenu] path: same as in [sat.core.sat_main.SAT.importMenu] path_i18n: translated path (or None if the item is removed) item: instance of quick_menus.MenuItemBase or None if the item is removed - gotMenus: called only once when menu are available (no arg) @param callback: method to call on event @param profiles_filter (set[unicode]): if set and not empty, the listener will be callable only by one of the given profiles. """ assert type_ in C.LISTENERS self._listeners.setdefault(type_, OrderedDict())[callback] = profiles_filter def removeListener(self, type_, callback): """Remove a callback from listeners @param type_: same as for [addListener] @param callback: callback to remove """ assert type_ in C.LISTENERS self._listeners[type_].pop(callback) def callListeners(self, type_, *args, **kwargs): """Call the methods which listen type_ event. If a profiles filter has been register with a listener and profile argument is not None, the listener will be called only if profile is in the profiles filter list. @param type_: same as for [addListener] @param *args: arguments sent to callback @param **kwargs: keywords argument, mainly used to pass "profile" when needed """ assert type_ in C.LISTENERS try: listeners = self._listeners[type_] except KeyError: pass else: profile = kwargs.get("profile") for listener, profiles_filter in listeners.iteritems(): if profile is None or not profiles_filter or profile in profiles_filter: listener(*args, **kwargs) def check_profile(self, profile): """Tell if the profile is currently followed by the application, and ready""" return profile in self.ready_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 /!\ if you override the method and don't call the parent, be sure to add the profile to ready_profiles ! if you don't, all signals will stay in cache @param profile(unicode): %(doc_profile)s """ self.ready_profiles.add(profile) # profile is ready, we can call send signals that where is cache cached_signals = self.signals_cache.pop(profile, []) for function_name, handler, args, kwargs in cached_signals: log.debug(u"Calling cached signal [%s] with args %s and kwargs %s" % (function_name, args, kwargs)) handler(*args, **kwargs) self.callListeners('profilePlugged', profile=profile) 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.unplug(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.setPresenceStatus(profile=profile) self.contact_lists[profile].fill() def disconnectedHandler(self, profile): """called when the connection is closed""" log.debug(_("Disconnected")) self.contact_lists[profile].clearContacts() self.setPresenceStatus(C.PRESENCE_UNAVAILABLE, '', 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 newMessageHandler(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) if not self.trigger.point("newMessageTrigger", from_jid, msg, type_, to_jid, extra, profile=profile): return 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 contact_list = self.contact_lists[profile] chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, target, type_=chat_type, on_new_widget=None, profile=profile) self.current_action_ids = set() # FIXME: to be removed self.current_action_ids_cb = {} # FIXME: to be removed if not from_jid in contact_list and from_jid.bare != self.profiles[profile].whoami.bare: #XXX: needed to show entities which haven't sent any # presence information and which are not in roster contact_list.setContact(from_jid) # we display the message in the widget chat_widget.newMessage(from_jid, target, msg, type_, extra, profile) # ContactList alert visible = False for widget in self.visible_widgets: if isinstance(widget, quick_chat.QuickChat) and widget.manageMessage(from_jid, type_): visible = True break if not visible: contact_list.setAlert(from_jid.bare if type_ == C.MESS_TYPE_GROUPCHAT else from_jid) 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") if not self.trigger.point("sendMessageTrigger", to_jid, message, subject, mess_type, extra, callback, errback, profile_key=profile_key): return self.bridge.sendMessage(unicode(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 setPresenceStatus(self, show='', status=None, profile=C.PROF_KEY_NONE): raise NotImplementedError def presenceUpdateHandler(self, entity_s, show, priority, statuses, profile): log.debug(_(u"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 == C.PRESENCE_UNAVAILABLE: self.setPresenceStatus(C.PRESENCE_UNAVAILABLE, '', profile=profile) else: # FIXME: try to retrieve user language status before fallback to default status = statuses.get(C.PRESENCE_STATUSES_DEFAULT, None) self.setPresenceStatus(show, status, 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.callListeners('presence', entity, show, priority, statuses, profile=profile) def roomJoinedHandler(self, room_jid_s, room_nicks, user_nick, profile): """Called when a MUC room is joined""" log.debug(u"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_=C.CHAT_GROUP, profile=profile) chat_widget.setUserNick(unicode(user_nick)) self.contact_lists[profile].setSpecial(room_jid, C.CONTACT_SPECIAL_GROUP) chat_widget.update() def roomLeftHandler(self, room_jid_s, profile): """Called when a MUC room is left""" log.debug(u"Room [%(room_jid)s] left by %(profile)s" % {'room_jid': room_jid_s, 'profile': profile}) room_jid = jid.JID(room_jid_s) chat_widget = self.widgets.getWidget(quick_chat.QuickChat, room_jid, profile) if chat_widget: self.widgets.deleteWidget(chat_widget) self.contact_lists[profile].removeContact(room_jid) 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_=C.CHAT_GROUP, profile=profile) chat_widget.addUser(user_nick) log.debug(u"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_=C.CHAT_GROUP, profile=profile) chat_widget.removeUser(user_nick) log.debug(u"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_=C.CHAT_GROUP, profile=profile) chat_widget.changeUserNick(old_nick, new_nick) log.debug(u"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_=C.CHAT_GROUP, profile=profile) chat_widget.setSubject(subject) log.debug(u"new subject for room [%(room_jid)s]: %(subject)s" % {'room_jid': room_jid, "subject": subject}) def chatStateReceivedHandler(self, from_jid_s, state, profile): """Called when a new chat state (XEP-0085) is received. @param from_jid_s (unicode): JID of a contact or C.ENTITY_ALL @param state (unicode): new state @param profile (unicode): current profile """ log.debug(_(u"Received new chat state {} from {} [{}]").format(state, from_jid_s, profile)) from_jid = jid.JID(from_jid_s) if from_jid_s != C.ENTITY_ALL else C.ENTITY_ALL contact_list = self.contact_lists[profile] for widget in self.widgets.getWidgets(quick_chat.QuickChat): if profile != widget.profile: continue to_display = C.USER_CHAT_STATES[state] if (state and widget.type == C.CHAT_GROUP) else state if widget.type == C.CHAT_GROUP and from_jid_s == C.ENTITY_ALL: for occupant in [jid.newResource(widget.target, nick) for nick in widget.occupants]: contact_list.setCache(occupant, 'chat_state', to_display) widget.update(occupant) elif from_jid.bare == widget.target.bare: # roster contact or MUC occupant contact_list.setCache(from_jid, 'chat_state', to_display) widget.update(from_jid) def personalEventHandler(self, sender, event_type, data, profile): """Called when a PEP event is received. @param sender (jid.JID): event sender @param event_type (unicode): event type, e.g. 'MICROBLOG' or 'MICROBLOG_DELETE' @param data (dict): event data """ # FIXME move some code from Libervia to here and put the magic strings to constants pass def _subscribe_cb(self, answer, data): entity, profile = data type_ = "subscribed" if answer else "unsubscribed" self.bridge.subscription(type_, unicode(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, answer_data=None): raise NotImplementedError def showAlert(self, message): pass #FIXME def paramUpdateHandler(self, name, value, namespace, profile): log.debug(_(u"param update: [%(namespace)s] %(name)s = %(value)s") % {'namespace': namespace, 'name': name, 'value': value}) if (namespace, name) == ("Connection", "JabberID"): log.debug(_(u"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_s, profile): target = jid.JID(jid_s) self.contact_lists[profile].removeContact(target, in_roster=True) def entityDataUpdatedHandler(self, entity_s, key, value, profile): entity = jid.JID(entity_s) if key == "nick": # this is the roster nick, not the MUC nick if entity in self.contact_lists[profile]: self.contact_lists[profile].setCache(entity, 'nick', value) self.callListeners('nick', entity, value, profile=profile) elif key == "avatar": if entity in self.contact_lists[profile]: def gotFilename(filename): self.contact_lists[profile].setCache(entity, 'avatar', filename) self.callListeners('avatar', entity, filename, profile=profile) 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=C.PROF_KEY_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 disconnect(self, profile): log.info("disconnecting") self.callListeners('disconnect', profile=profile) self.bridge.disconnect(profile) def onExit(self): """Must be called when the frontend is terminating""" to_unplug = [] 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.disconnect(profile) to_unplug.append(profile) for profile in to_unplug: self.unplug_profile(profile)