libervia-backend/frontends/src/quick_frontend/quick_app.py
2015-03-19 19:44:37 +01:00

689 lines
31 KiB
Python

#!/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.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("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.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))
# 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))
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.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 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 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.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("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(user_nick)
chat_widget.id = room_jid # FIXME: to be removed
room_nicks = [unicode(nick) for nick in room_nicks] # FIXME: should be done in DBus bridge / is that still needed?!
nicks = list(set([user_nick] + room_nicks))
nicks.sort()
chat_widget.setPresents(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})
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].remove(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.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_=C.CHAT_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_=C.CHAT_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_=C.CHAT_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 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 personalEventHandler(self, sender, event_type, data):
"""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(_("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_s, profile):
target = jid.JID(jid_s)
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)
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)