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