comparison frontends/src/quick_frontend/quick_app.py @ 1367:f71a0fc26886

merged branch frontends_multi_profiles
author Goffi <goffi@goffi.org>
date Wed, 18 Mar 2015 10:52:28 +0100
parents ba87b940f07a
children 0befb14ecf62
comparison
equal deleted inserted replaced
1295:1e3b1f9ad6e2 1367:f71a0fc26886
15 # GNU Affero General Public License for more details. 15 # GNU Affero General Public License for more details.
16 16
17 # You should have received a copy of the GNU Affero General Public License 17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 from sat.core.i18n import _
21 import sys
22 from sat.core.log import getLogger 20 from sat.core.log import getLogger
23 log = getLogger(__name__) 21 log = getLogger(__name__)
24 from sat_frontends.tools.jid import JID 22
25 from sat_frontends.bridge.DBus import DBusBridgeFrontend 23 from sat.core.i18n import _
26 from sat.core import exceptions 24 from sat.core import exceptions
27 from sat_frontends.quick_frontend.quick_utils import escapePrivate, unescapePrivate 25 from sat.tools.misc import TriggerManager
28 from optparse import OptionParser 26
29 27 from sat_frontends.tools import jid
28 from sat_frontends.quick_frontend import quick_widgets
29 from sat_frontends.quick_frontend import quick_menus
30 from sat_frontends.quick_frontend import quick_chat, quick_games
30 from sat_frontends.quick_frontend.constants import Const as C 31 from sat_frontends.quick_frontend.constants import Const as C
32
33 import sys
34 from collections import OrderedDict
35
36 try:
37 # FIXME: to be removed when an acceptable solution is here
38 unicode('') # XXX: unicode doesn't exist in pyjamas
39 except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options
40 unicode = str
41
42
43 class ProfileManager(object):
44 """Class managing all data relative to one profile, and plugging in mechanism"""
45 host = None
46 bridge = None
47 cache_keys_to_get = ['avatar']
48
49 def __init__(self, profile):
50 self.profile = profile
51 self.whoami = None
52 self.data = {}
53
54 def __getitem__(self, key):
55 return self.data[key]
56
57 def __setitem__(self, key, value):
58 self.data[key] = value
59
60 def plug(self):
61 """Plug the profile to the host"""
62 # we get the essential params
63 self.bridge.asyncGetParamA("JabberID", "Connection", profile_key=self.profile,
64 callback=self._plug_profile_jid, errback=self._getParamError)
65
66 def _plug_profile_jid(self, _jid):
67 self.whoami = jid.JID(_jid)
68 self.bridge.asyncGetParamA("autoconnect", "Connection", profile_key=self.profile,
69 callback=self._plug_profile_autoconnect, errback=self._getParamError)
70
71 def _plug_profile_autoconnect(self, value_str):
72 autoconnect = C.bool(value_str)
73 if autoconnect and not self.bridge.isConnected(self.profile):
74 self.host.asyncConnect(self.profile, callback=lambda dummy: self._plug_profile_afterconnect())
75 else:
76 self._plug_profile_afterconnect()
77
78 def _plug_profile_afterconnect(self):
79 # Profile can be connected or not
80 # we get cached data
81 self.host.bridge.getEntitiesData([], ProfileManager.cache_keys_to_get, profile=self.profile, callback=self._plug_profile_gotCachedValues, errback=self._plug_profile_failedCachedValues)
82
83 def _plug_profile_failedCachedValues(self, failure):
84 log.error("Couldn't get cached values: {}".format(failure))
85 self._plug_profile_gotCachedValues({})
86
87 def _plug_profile_gotCachedValues(self, cached_values):
88 # TODO: watched plugin
89
90 # add the contact list and its listener
91 contact_list = self.host.addContactList(self.profile)
92 self.host.contact_lists[self.profile] = contact_list
93
94 for entity, data in cached_values.iteritems():
95 for key, value in data.iteritems():
96 contact_list.setCache(jid.JID(entity), key, value)
97
98 if not self.bridge.isConnected(self.profile):
99 self.host.setStatusOnline(False, profile=self.profile)
100 else:
101 self.host.setStatusOnline(True, profile=self.profile)
102
103 contact_list.fill()
104
105 #The waiting subscription requests
106 self.bridge.getWaitingSub(self.profile, callback=self._plug_profile_gotWaitingSub)
107
108 def _plug_profile_gotWaitingSub(self, waiting_sub):
109 for sub in waiting_sub:
110 self.host.subscribeHandler(waiting_sub[sub], sub, self.profile)
111
112 self.bridge.getRoomsJoined(self.profile, callback=self._plug_profile_gotRoomsJoined)
113
114 def _plug_profile_gotRoomsJoined(self, rooms_args):
115 #Now we open the MUC window where we already are:
116 for room_args in rooms_args:
117 self.host.roomJoinedHandler(*room_args, profile=self.profile)
118
119 self.bridge.getRoomsSubjects(self.profile, callback=self._plug_profile_gotRoomsSubjects)
120
121 def _plug_profile_gotRoomsSubjects(self, subjects_args):
122 for subject_args in subjects_args:
123 self.host.roomNewSubjectHandler(*subject_args, profile=self.profile)
124
125 #Presence must be requested after rooms are filled
126 self.host.bridge.getPresenceStatuses(self.profile, callback=self._plug_profile_gotPresences)
127
128
129 def _plug_profile_gotPresences(self, presences):
130 def gotEntityData(data, contact):
131 for key in ('avatar', 'nick'):
132 if key in data:
133 self.host.entityDataUpdatedHandler(contact, key, data[key], self.profile)
134
135 for contact in presences:
136 for res in presences[contact]:
137 jabber_id = ('%s/%s' % (jid.JID(contact).bare, res)) if res else contact
138 show = presences[contact][res][0]
139 priority = presences[contact][res][1]
140 statuses = presences[contact][res][2]
141 self.host.presenceUpdateHandler(jabber_id, show, priority, statuses, self.profile)
142 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)))
143
144 #Finaly, we get the waiting confirmation requests
145 self.bridge.getWaitingConf(self.profile, callback=self._plug_profile_gotWaitingConf)
146
147 def _plug_profile_gotWaitingConf(self, waiting_confs):
148 for confirm_id, confirm_type, data in waiting_confs:
149 self.host.askConfirmationHandler(confirm_id, confirm_type, data, self.profile)
150
151 # At this point, profile should be fully plugged
152 # and we launch frontend specific method
153 self.host.profilePlugged(self.profile)
154
155 def _getParamError(self, ignore):
156 log.error(_("Can't get profile parameter"))
157
158
159 class ProfilesManager(object):
160 """Class managing collection of profiles"""
161
162 def __init__(self):
163 self._profiles = {}
164
165 def __contains__(self, profile):
166 return profile in self._profiles
167
168 def __iter__(self):
169 return self._profiles.iterkeys()
170
171 def __getitem__(self, profile):
172 return self._profiles[profile]
173
174 def __len__(self):
175 return len(self._profiles)
176
177 def plug(self, profile):
178 if profile in self._profiles:
179 raise exceptions.ConflictError('A profile of the name [{}] is already plugged'.format(profile))
180 self._profiles[profile] = ProfileManager(profile)
181 self._profiles[profile].plug()
182
183 def unplug(self, profile):
184 if profile not in self._profiles:
185 raise ValueError('The profile [{}] is not plugged'.format(profile))
186
187 # remove the contact list and its listener
188 host = self._profiles[profile].host
189 host.contact_lists[profile].onDelete()
190 del host.contact_lists[profile]
191
192 del self._profiles[profile]
193
194 def chooseOneProfile(self):
195 return self._profiles.keys()[0]
31 196
32 197
33 class QuickApp(object): 198 class QuickApp(object):
34 """This class contain the main methods needed for the frontend""" 199 """This class contain the main methods needed for the frontend"""
35 200
36 def __init__(self, single_profile=True): 201 def __init__(self, create_bridge, check_options=None):
37 self.profiles = {} 202 """Create a frontend application
38 self.single_profile = single_profile 203
39 self.check_options() 204 @param create_bridge: method to use to create the Bridge
205 @param check_options: method to call to check options (usually command line arguments)
206 """
207 self.menus = quick_menus.QuickMenusManager(self)
208 ProfileManager.host = self
209 self.profiles = ProfilesManager()
210 self.ready_profiles = set() # profiles which are connected and ready
211 self.signals_cache = {} # used to keep signal received between start of plug_profile and when the profile is actualy ready
212 self.contact_lists = {}
213 self.widgets = quick_widgets.QuickWidgetsManager(self)
214 if check_options is not None:
215 self.options = check_options()
216 else:
217 self.options = None
218
219 # widgets
220 self.selected_widget = None # widget currently selected (must be filled by frontend)
221
222 # listeners
223 self._listeners = {} # key: listener type ("avatar", "selected", etc), value: list of callbacks
224
225 # triggers
226 self.trigger = TriggerManager() # trigger are used to change the default behaviour
40 227
41 ## bridge ## 228 ## bridge ##
42 try: 229 try:
43 self.bridge = DBusBridgeFrontend() 230 self.bridge = create_bridge()
44 except exceptions.BridgeExceptionNoService: 231 except exceptions.BridgeExceptionNoService:
45 print(_(u"Can't connect to SàT backend, are you sure it's launched ?")) 232 print(_(u"Can't connect to SàT backend, are you sure it's launched ?"))
46 sys.exit(1) 233 sys.exit(1)
47 except exceptions.BridgeInitError: 234 except exceptions.BridgeInitError:
48 print(_(u"Can't init bridge")) 235 print(_(u"Can't init bridge"))
49 sys.exit(1) 236 sys.exit(1)
237 ProfileManager.bridge = self.bridge
50 self.registerSignal("connected") 238 self.registerSignal("connected")
51 self.registerSignal("disconnected") 239 self.registerSignal("disconnected")
52 self.registerSignal("newContact") 240 self.registerSignal("newContact")
53 self.registerSignal("newMessage", self._newMessage) 241 self.registerSignal("newMessage")
54 self.registerSignal("newAlert") 242 self.registerSignal("newAlert")
55 self.registerSignal("presenceUpdate") 243 self.registerSignal("presenceUpdate")
56 self.registerSignal("subscribe") 244 self.registerSignal("subscribe")
57 self.registerSignal("paramUpdate") 245 self.registerSignal("paramUpdate")
58 self.registerSignal("contactDeleted") 246 self.registerSignal("contactDeleted")
64 self.registerSignal("roomLeft", iface="plugin") 252 self.registerSignal("roomLeft", iface="plugin")
65 self.registerSignal("roomUserJoined", iface="plugin") 253 self.registerSignal("roomUserJoined", iface="plugin")
66 self.registerSignal("roomUserLeft", iface="plugin") 254 self.registerSignal("roomUserLeft", iface="plugin")
67 self.registerSignal("roomUserChangedNick", iface="plugin") 255 self.registerSignal("roomUserChangedNick", iface="plugin")
68 self.registerSignal("roomNewSubject", iface="plugin") 256 self.registerSignal("roomNewSubject", iface="plugin")
69 self.registerSignal("tarotGameStarted", iface="plugin")
70 self.registerSignal("tarotGameNew", iface="plugin")
71 self.registerSignal("tarotGameChooseContrat", iface="plugin")
72 self.registerSignal("tarotGameShowCards", iface="plugin")
73 self.registerSignal("tarotGameYourTurn", iface="plugin")
74 self.registerSignal("tarotGameScore", iface="plugin")
75 self.registerSignal("tarotGameCardsPlayed", iface="plugin")
76 self.registerSignal("tarotGameInvalidCards", iface="plugin")
77 self.registerSignal("quizGameStarted", iface="plugin")
78 self.registerSignal("quizGameNew", iface="plugin")
79 self.registerSignal("quizGameQuestion", iface="plugin")
80 self.registerSignal("quizGamePlayerBuzzed", iface="plugin")
81 self.registerSignal("quizGamePlayerSays", iface="plugin")
82 self.registerSignal("quizGameAnswerResult", iface="plugin")
83 self.registerSignal("quizGameTimerExpired", iface="plugin")
84 self.registerSignal("quizGameTimerRestarted", iface="plugin")
85 self.registerSignal("chatStateReceived", iface="plugin") 257 self.registerSignal("chatStateReceived", iface="plugin")
86 258 self.registerSignal("personalEvent", iface="plugin")
87 self.current_action_ids = set() 259
88 self.current_action_ids_cb = {} 260 # FIXME: do it dynamically
261 quick_games.Tarot.registerSignals(self)
262 quick_games.Quiz.registerSignals(self)
263 quick_games.Radiocol.registerSignals(self)
264
265 self.current_action_ids = set() # FIXME: to be removed
266 self.current_action_ids_cb = {} # FIXME: to be removed
89 self.media_dir = self.bridge.getConfig('', 'media_dir') 267 self.media_dir = self.bridge.getConfig('', 'media_dir')
90 268
91 def registerSignal(self, functionName, handler=None, iface="core", with_profile=True): 269 @property
270 def current_profile(self):
271 """Profile that a user would expect to use"""
272 try:
273 return self.selected_widget.profile
274 except (TypeError, AttributeError):
275 return self.profiles.chooseOneProfile()
276
277 @property
278 def visible_widgets(self):
279 """widgets currently visible (must be implemented by frontend)"""
280 raise NotImplementedError
281
282 def registerSignal(self, function_name, handler=None, iface="core", with_profile=True):
92 """Register a handler for a signal 283 """Register a handler for a signal
93 284
94 @param functionName (str): name of the signal to handle 285 @param function_name (str): name of the signal to handle
95 @param handler (instancemethod): method to call when the signal arrive, None for calling an automatically named handler (functionName + 'Handler') 286 @param handler (instancemethod): method to call when the signal arrive, None for calling an automatically named handler (function_name + 'Handler')
96 @param iface (str): interface of the bridge to use ('core' or 'plugin') 287 @param iface (str): interface of the bridge to use ('core' or 'plugin')
97 @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 288 @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
98 """ 289 """
99 if handler is None: 290 if handler is None:
100 handler = getattr(self, "%s%s" % (functionName, 'Handler')) 291 handler = getattr(self, "{}{}".format(function_name, 'Handler'))
101 if not with_profile: 292 if not with_profile:
102 self.bridge.register(functionName, handler, iface) 293 self.bridge.register(function_name, handler, iface)
103 return 294 return
104 295
105 def signalReceived(*args, **kwargs): 296 def signalReceived(*args, **kwargs):
106 profile = kwargs.get('profile') 297 profile = kwargs.get('profile')
107 if profile is None: 298 if profile is None:
108 if not args: 299 if not args:
109 raise exceptions.ProfileNotSetError 300 raise exceptions.ProfileNotSetError
110 profile = args[-1] 301 profile = args[-1]
111 if profile is not None and not self.check_profile(profile): 302 if profile is not None:
112 return # we ignore signal for profiles we don't manage 303 if not self.check_profile(profile):
304 if profile in self.profiles:
305 # profile is not ready but is in self.profiles, that's mean that it's being connecting and we need to cache the signal
306 self.signals_cache.setdefault(profile, []).append((function_name, handler, args, kwargs))
307 return # we ignore signal for profiles we don't manage
113 handler(*args, **kwargs) 308 handler(*args, **kwargs)
114 self.bridge.register(functionName, signalReceived, iface) 309 self.bridge.register(function_name, signalReceived, iface)
310
311 def addListener(self, type_, callback, profiles_filter=None):
312 """Add a listener for an event
313
314 /!\ don't forget to remove listener when not used anymore (e.g. if you delete a widget)
315 @param type_: type of event, can be:
316 - avatar: called when avatar data is updated
317 args: (entity, avatar file, profile)
318 - nick: called when nick data is updated
319 args: (entity, new_nick, profile)
320 - presence: called when a presence is received
321 args: (entity, show, priority, statuses, profile)
322 - menu: called when a menu item is added or removed
323 args: (type_, path, path_i18n, item) were values are:
324 type_: same as in [sat.core.sat_main.SAT.importMenu]
325 path: same as in [sat.core.sat_main.SAT.importMenu]
326 path_i18n: translated path (or None if the item is removed)
327 item: instance of quick_menus.MenuItemBase or None if the item is removed
328 - gotMenus: called only once when menu are available (no arg)
329 @param callback: method to call on event
330 @param profiles_filter (set[unicode]): if set and not empty, the
331 listener will be callable only by one of the given profiles.
332 """
333 assert type_ in C.LISTENERS
334 self._listeners.setdefault(type_, OrderedDict())[callback] = profiles_filter
335
336 def removeListener(self, type_, callback):
337 """Remove a callback from listeners
338
339 @param type_: same as for [addListener]
340 @param callback: callback to remove
341 """
342 assert type_ in C.LISTENERS
343 self._listeners[type_].pop(callback)
344
345 def callListeners(self, type_, *args, **kwargs):
346 """Call the methods which listen type_ event. If a profiles filter has
347 been register with a listener and profile argument is not None, the
348 listener will be called only if profile is in the profiles filter list.
349
350 @param type_: same as for [addListener]
351 @param *args: arguments sent to callback
352 @param **kwargs: keywords argument, mainly used to pass "profile" when needed
353 """
354 assert type_ in C.LISTENERS
355 try:
356 listeners = self._listeners[type_]
357 except KeyError:
358 pass
359 else:
360 profile = kwargs.get("profile")
361 for listener, profiles_filter in listeners.iteritems():
362 if profile is None or not profiles_filter or profile in profiles_filter:
363 listener(*args, **kwargs)
115 364
116 def check_profile(self, profile): 365 def check_profile(self, profile):
117 """Tell if the profile is currently followed by the application""" 366 """Tell if the profile is currently followed by the application, and ready"""
118 return profile in self.profiles.keys() 367 return profile in self.ready_profiles
119 368
120 def postInit(self): 369 def postInit(self, profile_manager):
121 """Must be called after initialization is done, do all automatic task (auto plug profile)""" 370 """Must be called after initialization is done, do all automatic task (auto plug profile)
122 if self.options.profile: 371
123 if not self.bridge.getProfileName(self.options.profile): 372 @param profile_manager: instance of a subclass of Quick_frontend.QuickProfileManager
124 log.error(_("Trying to plug an unknown profile (%s)" % self.options.profile)) 373 """
125 else: 374 if self.options and self.options.profile:
126 self.plug_profile(self.options.profile) 375 profile_manager.autoconnect([self.options.profile])
127 376
128 def check_options(self): 377 def profilePlugged(self, profile):
129 """Check command line options""" 378 """Method called when the profile is fully plugged, to launch frontend specific workflow
130 usage = _(""" 379
131 %prog [options] 380 /!\ if you override the method and don't call the parent, be sure to add the profile to ready_profiles !
132 381 if you don't, all signals will stay in cache
133 %prog --help for options list 382
134 """) 383 @param profile(unicode): %(doc_profile)s
135 parser = OptionParser(usage=usage) 384 """
136 385 self.ready_profiles.add(profile)
137 parser.add_option("-p", "--profile", help=_("Select the profile to use")) 386
138 387 # profile is ready, we can call send signals that where is cache
139 (self.options, args) = parser.parse_args() 388 cached_signals = self.signals_cache.pop(profile, [])
140 if self.options.profile: 389 for function_name, handler, args, kwargs in cached_signals:
141 self.options.profile = self.options.profile.decode('utf-8') 390 log.debug(u"Calling cached signal [%s] with args %s and kwargs %s" % (function_name, args, kwargs))
142 return args 391
143 392 self.callListeners('profilePlugged', profile=profile)
144 def _getParamError(self, ignore):
145 log.error(_("Can't get profile parameter"))
146
147 def plug_profile(self, profile_key='@DEFAULT@'):
148 """Tell application which profile must be used"""
149 if self.single_profile and self.profiles:
150 log.error(_('There is already one profile plugged (we are in single profile mode) !'))
151 return
152 profile = self.bridge.getProfileName(profile_key)
153 if not profile:
154 log.error(_("The profile asked doesn't exist"))
155 return
156 if profile in self.profiles:
157 log.warning(_("The profile is already plugged"))
158 return
159 self.profiles[profile] = {}
160 if self.single_profile:
161 self.profile = profile # FIXME: must be refactored (multi profiles are not managed correclty)
162 raw_menus = self.bridge.getMenus("", C.NO_SECURITY_LIMIT )
163 menus = self.profiles[profile]['menus'] = {}
164 for raw_menu in raw_menus:
165 id_, type_, path, path_i18n = raw_menu
166 menus_data = menus.setdefault(type_, [])
167 menus_data.append((id_, path, path_i18n))
168 self.launchAction(C.AUTHENTICATE_PROFILE_ID, {'caller': 'plug_profile'}, profile_key=profile)
169
170 def plug_profile_1(self, profile):
171 ###now we get the essential params###
172 self.bridge.asyncGetParamA("JabberID", "Connection", profile_key=profile,
173 callback=lambda _jid: self.plug_profile_2(_jid, profile), errback=self._getParamError)
174
175 def plug_profile_2(self, _jid, profile):
176 self.profiles[profile]['whoami'] = JID(_jid)
177 self.bridge.asyncGetParamA("autoconnect", "Connection", profile_key=profile,
178 callback=lambda value: self.plug_profile_3(value == "true", profile), errback=self._getParamError)
179
180 def plug_profile_3(self, autoconnect, profile):
181 self.bridge.asyncGetParamA("Watched", "Misc", profile_key=profile,
182 callback=lambda watched: self.plug_profile_4(watched, autoconnect, profile), errback=self._getParamError)
183 393
184 def asyncConnect(self, profile, callback=None, errback=None): 394 def asyncConnect(self, profile, callback=None, errback=None):
185 if not callback: 395 if not callback:
186 callback = lambda dummy: None 396 callback = lambda dummy: None
187 if not errback: 397 if not errback:
188 def errback(failure): 398 def errback(failure):
189 log.error(_(u"Can't connect profile [%s]") % failure) 399 log.error(_(u"Can't connect profile [%s]") % failure)
190 if failure.module.startswith('twisted.words.protocols.jabber') and failure.condition == "not-authorized": 400 if failure.module.startswith('twisted.words.protocols.jabber') and failure.condition == "not-authorized":
191 self.launchAction(C.CHANGE_XMPP_PASSWD_ID, {}, profile_key=profile) 401 self.launchAction(C.CHANGE_XMPP_PASSWD_ID, {}, profile=profile)
192 else: 402 else:
193 self.showDialog(failure.message, failure.fullname, 'error') 403 self.showDialog(failure.message, failure.fullname, 'error')
194 self.bridge.asyncConnect(profile, callback=callback, errback=errback) 404 self.bridge.asyncConnect(profile, callback=callback, errback=errback)
195 405
196 def plug_profile_4(self, watched, autoconnect, profile): 406 def plug_profiles(self, profiles):
197 if autoconnect and not self.bridge.isConnected(profile): 407 """Tell application which profiles must be used
198 #Does the user want autoconnection ? 408
199 self.asyncConnect(profile, callback=lambda dummy: self.plug_profile_5(watched, autoconnect, profile)) 409 @param profiles: list of valid profile names
200 else: 410 """
201 self.plug_profile_5(watched, autoconnect, profile) 411 self.plugging_profiles()
202 412 for profile in profiles:
203 def plug_profile_5(self, watched, autoconnect, profile): 413 self.profiles.plug(profile)
204 self.profiles[profile]['watched'] = watched.split() # TODO: put this in a plugin 414
205 415 def plugging_profiles(self):
206 ## misc ## 416 """Method to subclass to manage frontend specific things to do
207 self.profiles[profile]['onlineContact'] = set() # FIXME: temporary 417
208 418 will be called when profiles are choosen and are to be plugged soon
209 #TODO: manage multi-profiles here 419 """
210 if not self.bridge.isConnected(profile): 420 pass
211 self.setStatusOnline(False)
212 else:
213 self.setStatusOnline(True)
214
215 ### now we fill the contact list ###
216 for contact in self.bridge.getContacts(profile):
217 self.newContactHandler(*contact, profile=profile)
218
219 presences = self.bridge.getPresenceStatuses(profile)
220 for contact in presences:
221 for res in presences[contact]:
222 jabber_id = ('%s/%s' % (JID(contact).bare, res)) if res else contact
223 show = presences[contact][res][0]
224 priority = presences[contact][res][1]
225 statuses = presences[contact][res][2]
226 self.presenceUpdateHandler(jabber_id, show, priority, statuses, profile)
227 data = self.bridge.getEntityData(contact, ['avatar', 'nick'], profile)
228 for key in ('avatar', 'nick'):
229 if key in data:
230 self.entityDataUpdatedHandler(contact, key, data[key], profile)
231
232 #The waiting subscription requests
233 waitingSub = self.bridge.getWaitingSub(profile)
234 for sub in waitingSub:
235 self.subscribeHandler(waitingSub[sub], sub, profile)
236
237 #Now we open the MUC window where we already are:
238 for room_args in self.bridge.getRoomsJoined(profile):
239 self.roomJoinedHandler(*room_args, profile=profile)
240
241 for subject_args in self.bridge.getRoomsSubjects(profile):
242 self.roomNewSubjectHandler(*subject_args, profile=profile)
243
244 #Finaly, we get the waiting confirmation requests
245 for confirm_id, confirm_type, data in self.bridge.getWaitingConf(profile):
246 self.askConfirmationHandler(confirm_id, confirm_type, data, profile)
247 421
248 def unplug_profile(self, profile): 422 def unplug_profile(self, profile):
249 """Tell the application to not follow anymore the profile""" 423 """Tell the application to not follow anymore the profile"""
250 if not profile in self.profiles: 424 if not profile in self.profiles:
251 log.warning(_("This profile is not plugged")) 425 raise ValueError("The profile [{}] is not plugged".format(profile))
252 return 426 self.profiles.unplug(profile)
253 self.profiles.remove(profile)
254 427
255 def clear_profile(self): 428 def clear_profile(self):
256 self.profiles.clear() 429 self.profiles.clear()
430
431 def addContactList(self, profile):
432 """Method to subclass to add a contact list widget
433
434 will be called on each profile session build
435 @return: a ContactList widget
436 """
437 return NotImplementedError
438
439 def newWidget(self, widget):
440 raise NotImplementedError
257 441
258 def connectedHandler(self, profile): 442 def connectedHandler(self, profile):
259 """called when the connection is made""" 443 """called when the connection is made"""
260 log.debug(_("Connected")) 444 log.debug(_("Connected"))
261 self.setStatusOnline(True) 445 self.setStatusOnline(True, profile=profile)
262 446
263 def disconnectedHandler(self, profile): 447 def disconnectedHandler(self, profile):
264 """called when the connection is closed""" 448 """called when the connection is closed"""
265 log.debug(_("Disconnected")) 449 log.debug(_("Disconnected"))
266 self.contact_list.clearContacts() 450 self.contact_lists[profile].clearContacts()
267 self.setStatusOnline(False) 451 self.setStatusOnline(False, profile=profile)
268 452
269 def newContactHandler(self, JabberId, attributes, groups, profile): 453 def newContactHandler(self, JabberId, attributes, groups, profile):
270 entity = JID(JabberId) 454 entity = jid.JID(JabberId)
271 _groups = list(groups) 455 _groups = list(groups)
272 self.contact_list.replace(entity, _groups, attributes) 456 self.contact_lists[profile].setContact(entity, _groups, attributes, in_roster=True)
273 457
274 def _newMessage(self, from_jid_s, msg, type_, to_jid_s, extra, profile): 458 def newMessageHandler(self, from_jid_s, msg, type_, to_jid_s, extra, profile):
275 """newMessage premanagement: a dirty hack to manage private messages 459 from_jid = jid.JID(from_jid_s)
276 460 to_jid = jid.JID(to_jid_s)
277 if a private MUC message is detected, from_jid or to_jid is prefixed and resource is escaped 461
278 """ 462 if not self.trigger.point("newMessageTrigger", from_jid, msg, type_, to_jid, extra, profile=profile):
279 # FIXME: must be refactored for 0.6 463 return
280 from_jid = JID(from_jid_s) 464
281 to_jid = JID(to_jid_s) 465 from_me = from_jid.bare == self.profiles[profile].whoami.bare
282 466 target = to_jid if from_me else from_jid
283 from_me = from_jid.bare == self.profiles[profile]['whoami'].bare 467
284 win = to_jid if from_me else from_jid 468 chat_type = C.CHAT_GROUP if type_ == C.MESS_TYPE_GROUPCHAT else C.CHAT_ONE2ONE
285 469 contact_list = self.contact_lists[profile]
286 if ((type_ != "groupchat" and self.contact_list.getSpecial(win) == "MUC") and 470
287 (type_ != C.MESS_TYPE_INFO or (type_ == C.MESS_TYPE_INFO and win.resource))): 471 chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, target, type_=chat_type, on_new_widget=None, profile=profile)
288 #we have a private message in a MUC room 472
289 #XXX: normaly we use bare jid as key, here we need the full jid 473 self.current_action_ids = set() # FIXME: to be removed
290 # so we cheat by replacing the "/" before the resource by 474 self.current_action_ids_cb = {} # FIXME: to be removed
291 # a "@", so the jid is invalid, 475
292 new_jid = escapePrivate(win) 476 if not from_jid in contact_list and from_jid.bare != self.profiles[profile].whoami.bare:
293 if from_me: 477 #XXX: needed to show entities which haven't sent any
294 to_jid = new_jid 478 # presence information and which are not in roster
295 else: 479 contact_list.setContact(from_jid)
296 from_jid = new_jid 480
297 if new_jid not in self.contact_list: 481 # we display the message in the widget
298 self.contact_list.add(new_jid, [C.GROUP_NOT_IN_ROSTER]) 482 chat_widget.newMessage(from_jid, target, msg, type_, extra, profile)
299 483
300 self.newMessageHandler(from_jid, to_jid, msg, type_, extra, profile) 484 # ContactList alert
301 485 visible = False
302 def newMessageHandler(self, from_jid, to_jid, msg, type_, extra, profile): 486 for widget in self.visible_widgets:
303 from_me = from_jid.bare == self.profiles[profile]['whoami'].bare 487 if isinstance(widget, quick_chat.QuickChat) and widget.manageMessage(from_jid, type_):
304 win = to_jid if from_me else from_jid 488 visible = True
305 489 break
306 self.current_action_ids = set() 490 if not visible:
307 self.current_action_ids_cb = {} 491 contact_list.setAlert(from_jid.bare if type_ == C.MESS_TYPE_GROUPCHAT else from_jid)
308 492
309 timestamp = extra.get('archive') 493 def sendMessage(self, to_jid, message, subject='', mess_type="auto", extra={}, callback=None, errback=None, profile_key=C.PROF_KEY_NONE):
310 if type_ == C.MESS_TYPE_INFO:
311 self.chat_wins[win.bare].printInfo(msg, timestamp=float(timestamp) if timestamp else None)
312 else:
313 self.chat_wins[win.bare].printMessage(from_jid, msg, profile, float(timestamp) if timestamp else None)
314
315 def sendMessage(self, to_jid, message, subject='', mess_type="auto", extra={}, callback=None, errback=None, profile_key="@NONE@"):
316 if to_jid.startswith(C.PRIVATE_PREFIX):
317 to_jid = unescapePrivate(to_jid)
318 mess_type = "chat"
319 if callback is None: 494 if callback is None:
320 callback = lambda: None 495 callback = lambda dummy=None: None # FIXME: optional argument is here because pyjamas doesn't support callback without arg with json proxy
321 if errback is None: 496 if errback is None:
322 errback = lambda failure: self.showDialog(failure.fullname, failure.message, "error") 497 errback = lambda failure: self.showDialog(failure.fullname, failure.message, "error")
323 self.bridge.sendMessage(to_jid, message, subject, mess_type, extra, profile_key, callback=callback, errback=errback) 498
499 if not self.trigger.point("sendMessageTrigger", to_jid, message, subject, mess_type, extra, callback, errback, profile_key=profile_key):
500 return
501
502 self.bridge.sendMessage(unicode(to_jid), message, subject, mess_type, extra, profile_key, callback=callback, errback=errback)
324 503
325 def newAlertHandler(self, msg, title, alert_type, profile): 504 def newAlertHandler(self, msg, title, alert_type, profile):
326 assert alert_type in ['INFO', 'ERROR'] 505 assert alert_type in ['INFO', 'ERROR']
327 self.showDialog(unicode(msg), unicode(title), alert_type.lower()) 506 self.showDialog(unicode(msg), unicode(title), alert_type.lower())
328 507
329 def setStatusOnline(self, online=True, show="", statuses={}): 508 def setStatusOnline(self, online=True, show="", statuses={}, profile=C.PROF_KEY_NONE):
330 raise NotImplementedError 509 raise NotImplementedError
331 510
332 def presenceUpdateHandler(self, jabber_id, show, priority, statuses, profile): 511 def presenceUpdateHandler(self, entity_s, show, priority, statuses, profile):
333 512
334 log.debug(_("presence update for %(jid)s (show=%(show)s, priority=%(priority)s, statuses=%(statuses)s) [profile:%(profile)s]") 513 log.debug(_("presence update for %(entity)s (show=%(show)s, priority=%(priority)s, statuses=%(statuses)s) [profile:%(profile)s]")
335 % {'jid': jabber_id, 'show': show, 'priority': priority, 'statuses': statuses, 'profile': profile}) 514 % {'entity': entity_s, C.PRESENCE_SHOW: show, C.PRESENCE_PRIORITY: priority, C.PRESENCE_STATUSES: statuses, 'profile': profile})
336 from_jid = JID(jabber_id) 515 entity = jid.JID(entity_s)
337 516
338 if from_jid == self.profiles[profile]['whoami']: 517 if entity == self.profiles[profile].whoami:
339 if show == "unavailable": 518 if show == "unavailable":
340 self.setStatusOnline(False) 519 self.setStatusOnline(False, profile=profile)
341 else: 520 else:
342 self.setStatusOnline(True, show, statuses) 521 self.setStatusOnline(True, show, statuses, profile=profile)
343 return 522 return
344 523
345 presences = self.profiles[profile].setdefault('presences', {}) 524 # #FIXME: must be moved in a plugin
346 525 # if entity.bare in self.profiles[profile].data.get('watched',[]) and not entity.bare in self.profiles[profile]['onlineContact']:
347 if show != 'unavailable': 526 # self.showAlert(_("Watched jid [%s] is connected !") % entity.bare)
348 527
349 #FIXME: must be moved in a plugin 528 self.callListeners('presence', entity, show, priority, statuses, profile=profile)
350 if from_jid.bare in self.profiles[profile].get('watched',[]) and not from_jid.bare in self.profiles[profile]['onlineContact']: 529
351 self.showAlert(_("Watched jid [%s] is connected !") % from_jid.bare) 530 def roomJoinedHandler(self, room_jid_s, room_nicks, user_nick, profile):
352
353 presences[jabber_id] = {'show': show, 'priority': priority, 'statuses': statuses}
354 self.profiles[profile].setdefault('onlineContact',set()).add(from_jid) # FIXME onlineContact is useless with CM, must be removed
355
356 #TODO: vcard data (avatar)
357
358 if show == "unavailable" and from_jid in self.profiles[profile].get('onlineContact',set()):
359 try:
360 del presences[jabber_id]
361 except KeyError:
362 pass
363 self.profiles[profile]['onlineContact'].remove(from_jid)
364
365 # check if the contact is connected with another resource, use the one with highest priority
366 jids = [jid for jid in presences if JID(jid).bare == from_jid.bare]
367 if jids:
368 max_jid = max(jids, key=lambda jid: presences[jid]['priority'])
369 data = presences[max_jid]
370 max_priority = data['priority']
371 if show == "unavailable": # do not check the priority here, because 'unavailable' has a dummy one
372 from_jid = JID(max_jid)
373 show, priority, statuses = data['show'], data['priority'], data['statuses']
374 if not jids or priority >= max_priority:
375 # case 1: not jids means all resources are disconnected, send the 'unavailable' presence
376 # case 2: update (or confirm) with the values of the resource which takes precedence
377 self.contact_list.updatePresence(from_jid, show, priority, statuses)
378
379 def roomJoinedHandler(self, room_jid, room_nicks, user_nick, profile):
380 """Called when a MUC room is joined""" 531 """Called when a MUC room is joined"""
381 log.debug(_("Room [%(room_jid)s] joined by %(profile)s, users presents:%(users)s") % {'room_jid': room_jid, 'profile': profile, 'users': room_nicks}) 532 log.debug("Room [%(room_jid)s] joined by %(profile)s, users presents:%(users)s" % {'room_jid': room_jid_s, 'profile': profile, 'users': room_nicks})
382 self.chat_wins[room_jid].setUserNick(user_nick) 533 room_jid = jid.JID(room_jid_s)
383 self.chat_wins[room_jid].setType("group") 534 chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile)
384 self.chat_wins[room_jid].id = room_jid 535 chat_widget.setUserNick(user_nick)
385 self.chat_wins[room_jid].setPresents(list(set([user_nick] + room_nicks))) 536 chat_widget.id = room_jid # FIXME: to be removed
386 self.contact_list.setSpecial(JID(room_jid), "MUC", show=True) 537 room_nicks = [unicode(nick) for nick in room_nicks] # FIXME: should be done in DBus bridge / is that still needed?!
538 nicks = list(set([user_nick] + room_nicks))
539 nicks.sort()
540 chat_widget.setPresents(nicks)
541 self.contact_lists[profile].setSpecial(room_jid, C.CONTACT_SPECIAL_GROUP)
387 542
388 def roomLeftHandler(self, room_jid_s, profile): 543 def roomLeftHandler(self, room_jid_s, profile):
389 """Called when a MUC room is left""" 544 """Called when a MUC room is left"""
390 log.debug(_("Room [%(room_jid)s] left by %(profile)s") % {'room_jid': room_jid_s, 'profile': profile}) 545 log.debug("Room [%(room_jid)s] left by %(profile)s" % {'room_jid': room_jid_s, 'profile': profile})
391 del self.chat_wins[room_jid_s] 546 room_jid = jid.JID(room_jid_s)
392 self.contact_list.remove(JID(room_jid_s)) 547 chat_widget = self.widgets.getWidget(quick_chat.QuickChat, room_jid, profile)
393 548 if chat_widget:
394 def roomUserJoinedHandler(self, room_jid, user_nick, user_data, profile): 549 self.widgets.deleteWidget(chat_widget)
550 self.contact_lists[profile].remove(room_jid)
551
552 def roomUserJoinedHandler(self, room_jid_s, user_nick, user_data, profile):
395 """Called when an user joined a MUC room""" 553 """Called when an user joined a MUC room"""
396 if room_jid in self.chat_wins: 554 room_jid = jid.JID(room_jid_s)
397 self.chat_wins[room_jid].replaceUser(user_nick) 555 chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile)
398 log.debug(_("user [%(user_nick)s] joined room [%(room_jid)s]") % {'user_nick': user_nick, 'room_jid': room_jid}) 556 chat_widget.replaceUser(user_nick)
399 557 log.debug("user [%(user_nick)s] joined room [%(room_jid)s]" % {'user_nick': user_nick, 'room_jid': room_jid})
400 def roomUserLeftHandler(self, room_jid, user_nick, user_data, profile): 558
559 def roomUserLeftHandler(self, room_jid_s, user_nick, user_data, profile):
401 """Called when an user joined a MUC room""" 560 """Called when an user joined a MUC room"""
402 if room_jid in self.chat_wins: 561 room_jid = jid.JID(room_jid_s)
403 self.chat_wins[room_jid].removeUser(user_nick) 562 chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile)
404 log.debug(_("user [%(user_nick)s] left room [%(room_jid)s]") % {'user_nick': user_nick, 'room_jid': room_jid}) 563 chat_widget.removeUser(user_nick)
405 564 log.debug("user [%(user_nick)s] left room [%(room_jid)s]" % {'user_nick': user_nick, 'room_jid': room_jid})
406 def roomUserChangedNickHandler(self, room_jid, old_nick, new_nick, profile): 565
566 def roomUserChangedNickHandler(self, room_jid_s, old_nick, new_nick, profile):
407 """Called when an user joined a MUC room""" 567 """Called when an user joined a MUC room"""
408 if room_jid in self.chat_wins: 568 room_jid = jid.JID(room_jid_s)
409 self.chat_wins[room_jid].changeUserNick(old_nick, new_nick) 569 chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile)
410 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}) 570 chat_widget.changeUserNick(old_nick, new_nick)
411 571 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})
412 def roomNewSubjectHandler(self, room_jid, subject, profile): 572
573 def roomNewSubjectHandler(self, room_jid_s, subject, profile):
413 """Called when subject of MUC room change""" 574 """Called when subject of MUC room change"""
414 if room_jid in self.chat_wins: 575 room_jid = jid.JID(room_jid_s)
415 self.chat_wins[room_jid].setSubject(subject) 576 chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile)
416 log.debug(_("new subject for room [%(room_jid)s]: %(subject)s") % {'room_jid': room_jid, "subject": subject}) 577 chat_widget.setSubject(subject)
417 578 log.debug("new subject for room [%(room_jid)s]: %(subject)s" % {'room_jid': room_jid, "subject": subject})
418 def tarotGameStartedHandler(self, room_jid, referee, players, profile):
419 log.debug(_("Tarot Game Started \o/"))
420 if room_jid in self.chat_wins:
421 self.chat_wins[room_jid].startGame("Tarot", referee, players)
422 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]})
423
424 def tarotGameNewHandler(self, room_jid, hand, profile):
425 log.debug(_("New Tarot Game"))
426 if room_jid in self.chat_wins:
427 self.chat_wins[room_jid].getGame("Tarot").newGame(hand)
428
429 def tarotGameChooseContratHandler(self, room_jid, xml_data, profile):
430 """Called when the player has to select his contrat"""
431 log.debug(_("Tarot: need to select a contrat"))
432 if room_jid in self.chat_wins:
433 self.chat_wins[room_jid].getGame("Tarot").chooseContrat(xml_data)
434
435 def tarotGameShowCardsHandler(self, room_jid, game_stage, cards, data, profile):
436 log.debug(_("Show cards"))
437 if room_jid in self.chat_wins:
438 self.chat_wins[room_jid].getGame("Tarot").showCards(game_stage, cards, data)
439
440 def tarotGameYourTurnHandler(self, room_jid, profile):
441 log.debug(_("My turn to play"))
442 if room_jid in self.chat_wins:
443 self.chat_wins[room_jid].getGame("Tarot").myTurn()
444
445 def tarotGameScoreHandler(self, room_jid, xml_data, winners, loosers, profile):
446 """Called when the game is finished and the score are updated"""
447 log.debug(_("Tarot: score received"))
448 if room_jid in self.chat_wins:
449 self.chat_wins[room_jid].getGame("Tarot").showScores(xml_data, winners, loosers)
450
451 def tarotGameCardsPlayedHandler(self, room_jid, player, cards, profile):
452 log.debug(_("Card(s) played (%(player)s): %(cards)s") % {"player": player, "cards": cards})
453 if room_jid in self.chat_wins:
454 self.chat_wins[room_jid].getGame("Tarot").cardsPlayed(player, cards)
455
456 def tarotGameInvalidCardsHandler(self, room_jid, phase, played_cards, invalid_cards, profile):
457 log.debug(_("Cards played are not valid: %s") % invalid_cards)
458 if room_jid in self.chat_wins:
459 self.chat_wins[room_jid].getGame("Tarot").invalidCards(phase, played_cards, invalid_cards)
460
461 def quizGameStartedHandler(self, room_jid, referee, players, profile):
462 log.debug(_("Quiz Game Started \o/"))
463 if room_jid in self.chat_wins:
464 self.chat_wins[room_jid].startGame("Quiz", referee, players)
465 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]})
466
467 def quizGameNewHandler(self, room_jid, data, profile):
468 log.debug(_("New Quiz Game"))
469 if room_jid in self.chat_wins:
470 self.chat_wins[room_jid].getGame("Quiz").quizGameNewHandler(data)
471
472 def quizGameQuestionHandler(self, room_jid, question_id, question, timer, profile):
473 """Called when a new question is asked"""
474 log.debug(_(u"Quiz: new question: %s") % question)
475 if room_jid in self.chat_wins:
476 self.chat_wins[room_jid].getGame("Quiz").quizGameQuestionHandler(question_id, question, timer)
477
478 def quizGamePlayerBuzzedHandler(self, room_jid, player, pause, profile):
479 """Called when a player pushed the buzzer"""
480 if room_jid in self.chat_wins:
481 self.chat_wins[room_jid].getGame("Quiz").quizGamePlayerBuzzedHandler(player, pause)
482
483 def quizGamePlayerSaysHandler(self, room_jid, player, text, delay, profile):
484 """Called when a player say something"""
485 if room_jid in self.chat_wins:
486 self.chat_wins[room_jid].getGame("Quiz").quizGamePlayerSaysHandler(player, text, delay)
487
488 def quizGameAnswerResultHandler(self, room_jid, player, good_answer, score, profile):
489 """Called when a player say something"""
490 if room_jid in self.chat_wins:
491 self.chat_wins[room_jid].getGame("Quiz").quizGameAnswerResultHandler(player, good_answer, score)
492
493 def quizGameTimerExpiredHandler(self, room_jid, profile):
494 """Called when nobody answered the question in time"""
495 if room_jid in self.chat_wins:
496 self.chat_wins[room_jid].getGame("Quiz").quizGameTimerExpiredHandler()
497
498 def quizGameTimerRestartedHandler(self, room_jid, time_left, profile):
499 """Called when the question is not answered, and we still have time"""
500 if room_jid in self.chat_wins:
501 self.chat_wins[room_jid].getGame("Quiz").quizGameTimerRestartedHandler(time_left)
502 579
503 def chatStateReceivedHandler(self, from_jid_s, state, profile): 580 def chatStateReceivedHandler(self, from_jid_s, state, profile):
504 """Callback when a new chat state is received. 581 """Called when a new chat state is received.
582
505 @param from_jid_s: JID of the contact who sent his state, or '@ALL@' 583 @param from_jid_s: JID of the contact who sent his state, or '@ALL@'
506 @param state: new state (string) 584 @param state: new state (string)
507 @profile: current profile 585 @profile: current profile
508 """ 586 """
509 587 from_jid = jid.JID(from_jid_s) if from_jid_s != C.ENTITY_ALL else C.ENTITY_ALL
510 if from_jid_s == '@ALL@': 588 for widget in self.widgets.getWidgets(quick_chat.QuickChat):
511 target = '@ALL@' 589 if from_jid == C.ENTITY_ALL or from_jid.bare == widget.target.bare:
512 nick = C.ALL_OCCUPANTS 590 widget.updateChatState(from_jid, state)
513 else: 591
514 from_jid = JID(from_jid_s) 592 def personalEventHandler(self, sender, event_type, data):
515 target = from_jid.bare 593 """Called when a PEP event is received.
516 nick = from_jid.resource 594
517 595 @param sender (jid.JID): event sender
518 for bare in self.chat_wins.keys(): 596 @param event_type (unicode): event type, e.g. 'MICROBLOG' or 'MICROBLOG_DELETE'
519 if target == '@ALL' or target == bare: 597 @param data (dict): event data
520 chat_win = self.chat_wins[bare] 598 """
521 if chat_win.type == 'one2one': 599 # FIXME move some code from Libervia to here and put the magic strings to constants
522 chat_win.updateChatState(state) 600 pass
523 elif chat_win.type == 'group':
524 chat_win.updateChatState(state, nick=nick)
525 601
526 def _subscribe_cb(self, answer, data): 602 def _subscribe_cb(self, answer, data):
527 entity, profile = data 603 entity, profile = data
528 if answer: 604 type_ = "subscribed" if answer else "unsubscribed"
529 self.bridge.subscription("subscribed", entity.bare, profile_key=profile) 605 self.bridge.subscription(type_, unicode(entity.bare), profile_key=profile)
530 else:
531 self.bridge.subscription("unsubscribed", entity.bare, profile_key=profile)
532 606
533 def subscribeHandler(self, type, raw_jid, profile): 607 def subscribeHandler(self, type, raw_jid, profile):
534 """Called when a subsciption management signal is received""" 608 """Called when a subsciption management signal is received"""
535 entity = JID(raw_jid) 609 entity = jid.JID(raw_jid)
536 if type == "subscribed": 610 if type == "subscribed":
537 # this is a subscription confirmation, we just have to inform user 611 # this is a subscription confirmation, we just have to inform user
538 self.showDialog(_("The contact %s has accepted your subscription") % entity.bare, _('Subscription confirmation')) 612 self.showDialog(_("The contact %s has accepted your subscription") % entity.bare, _('Subscription confirmation'))
539 elif type == "unsubscribed": 613 elif type == "unsubscribed":
540 # this is a subscription refusal, we just have to inform user 614 # this is a subscription refusal, we just have to inform user
541 self.showDialog(_("The contact %s has refused your subscription") % entity.bare, _('Subscription refusal'), 'error') 615 self.showDialog(_("The contact %s has refused your subscription") % entity.bare, _('Subscription refusal'), 'error')
542 elif type == "subscribe": 616 elif type == "subscribe":
543 # this is a subscriptionn request, we have to ask for user confirmation 617 # this is a subscriptionn request, we have to ask for user confirmation
544 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)) 618 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))
545 619
546 def showDialog(self, message, title, type="info", answer_cb=None): 620 def showDialog(self, message, title, type="info", answer_cb=None, answer_data=None):
547 raise NotImplementedError 621 raise NotImplementedError
548 622
549 def showAlert(self, message): 623 def showAlert(self, message):
550 pass #FIXME 624 pass #FIXME
551 625
552 def paramUpdateHandler(self, name, value, namespace, profile): 626 def paramUpdateHandler(self, name, value, namespace, profile):
553 log.debug(_("param update: [%(namespace)s] %(name)s = %(value)s") % {'namespace': namespace, 'name': name, 'value': value}) 627 log.debug(_("param update: [%(namespace)s] %(name)s = %(value)s") % {'namespace': namespace, 'name': name, 'value': value})
554 if (namespace, name) == ("Connection", "JabberID"): 628 if (namespace, name) == ("Connection", "JabberID"):
555 log.debug(_("Changing JID to %s") % value) 629 log.debug(_("Changing JID to %s") % value)
556 self.profiles[profile]['whoami'] = JID(value) 630 self.profiles[profile].whoami = jid.JID(value)
557 elif (namespace, name) == ("Misc", "Watched"): 631 elif (namespace, name) == ("Misc", "Watched"):
558 self.profiles[profile]['watched'] = value.split() 632 self.profiles[profile]['watched'] = value.split()
559 elif (namespace, name) == ('General', C.SHOW_OFFLINE_CONTACTS): 633 elif (namespace, name) == ('General', C.SHOW_OFFLINE_CONTACTS):
560 self.contact_list.showOfflineContacts(C.bool(value)) 634 self.contact_lists[profile].showOfflineContacts(C.bool(value))
561 elif (namespace, name) == ('General', C.SHOW_EMPTY_GROUPS): 635 elif (namespace, name) == ('General', C.SHOW_EMPTY_GROUPS):
562 self.contact_list.showEmptyGroups(C.bool(value)) 636 self.contact_lists[profile].showEmptyGroups(C.bool(value))
563 637
564 def contactDeletedHandler(self, jid, profile): 638 def contactDeletedHandler(self, jid_s, profile):
565 target = JID(jid) 639 target = jid.JID(jid_s)
566 self.contact_list.remove(target) 640 self.contact_lists[profile].remove(target)
567 try: 641
568 self.profiles[profile]['onlineContact'].remove(target.bare) 642 def entityDataUpdatedHandler(self, entity_s, key, value, profile):
569 except KeyError: 643 entity = jid.JID(entity_s)
570 pass
571
572 def entityDataUpdatedHandler(self, jid_str, key, value, profile):
573 jid = JID(jid_str)
574 if key == "nick": 644 if key == "nick":
575 if jid in self.contact_list: 645 if entity in self.contact_lists[profile]:
576 self.contact_list.setCache(jid, 'nick', value) 646 self.contact_lists[profile].setCache(entity, 'nick', value)
577 self.contact_list.replace(jid) 647 self.callListeners('nick', entity, value, profile=profile)
578 elif key == "avatar": 648 elif key == "avatar":
579 if jid in self.contact_list: 649 if entity in self.contact_lists[profile]:
580 filename = self.bridge.getAvatarFile(value) 650 def gotFilename(filename):
581 self.contact_list.setCache(jid, 'avatar', filename) 651 self.contact_lists[profile].setCache(entity, 'avatar', filename)
582 self.contact_list.replace(jid) 652 self.callListeners('avatar', entity, filename, profile=profile)
653 self.bridge.getAvatarFile(value, callback=gotFilename)
583 654
584 def askConfirmationHandler(self, confirm_id, confirm_type, data, profile): 655 def askConfirmationHandler(self, confirm_id, confirm_type, data, profile):
585 raise NotImplementedError 656 raise NotImplementedError
586 657
587 def actionResultHandler(self, type, id, data, profile): 658 def actionResultHandler(self, type, id, data, profile):
588 raise NotImplementedError 659 raise NotImplementedError
589 660
590 def launchAction(self, callback_id, data=None, profile_key="@NONE@"): 661 def launchAction(self, callback_id, data=None, callback=None, profile=C.PROF_KEY_NONE):
591 """ Launch a dynamic action 662 """Launch a dynamic action
592 @param callback_id: id of the action to launch 663 @param callback_id: id of the action to launch
593 @param data: data needed only for certain actions 664 @param data: data needed only for certain actions
594 @param profile_key: %(doc_profile_key)s 665 @param callback: if not None and 'validated' key is present, it will be called with the following parameters:
666 - callback_id
667 - data
668 - profile_key
669 @param profile_key: %(doc_profile)s
595 670
596 """ 671 """
597 raise NotImplementedError 672 raise NotImplementedError
673
674 def disconnect(self, profile):
675 log.info("disconnecting")
676 self.callListeners('disconnect', profile=profile)
677 self.bridge.disconnect(profile)
598 678
599 def onExit(self): 679 def onExit(self):
600 """Must be called when the frontend is terminating""" 680 """Must be called when the frontend is terminating"""
601 #TODO: mange multi-profile here 681 to_unplug = []
602 try: 682 for profile in self.profiles:
603 if self.bridge.isConnected(self.profile): 683 if self.bridge.isConnected(profile):
604 if self.bridge.getParamA("autodisconnect", "Connection", profile_key=self.profile) == "true": 684 if C.bool(self.bridge.getParamA("autodisconnect", "Connection", profile_key=profile)):
605 #The user wants autodisconnection 685 #The user wants autodisconnection
606 self.bridge.disconnect(self.profile) 686 self.disconnect(profile)
607 except: 687 to_unplug.append(profile)
608 pass 688 for profile in to_unplug:
689 self.unplug_profile(profile)