comparison frontends/src/quick_frontend/quick_app.py @ 1337:f29beedb33b0 frontends_multi_profiles

merged souliane changes
author Goffi <goffi@goffi.org>
date Mon, 23 Feb 2015 18:08:22 +0100
parents 2ecc07a8f91b 0f92b6a150ff
children 139263ee85c5
comparison
equal deleted inserted replaced
1336:2ecc07a8f91b 1337:f29beedb33b0
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__)
22
23 from sat.core.i18n import _
24 from sat.core import exceptions 24 from sat.core import exceptions
25
25 from sat_frontends.tools import jid 26 from sat_frontends.tools import jid
26 from sat_frontends.quick_frontend.quick_widgets import QuickWidgetsManager 27 from sat_frontends.quick_frontend.quick_widgets import QuickWidgetsManager
27 from sat_frontends.quick_frontend import quick_chat 28 from sat_frontends.quick_frontend import quick_chat
28
29 from sat_frontends.quick_frontend.constants import Const as C 29 from sat_frontends.quick_frontend.constants import Const as C
30
31 import sys
32 from collections import OrderedDict
33
34 try:
35 # FIXME: to be removed when an acceptable solution is here
36 unicode('') # XXX: unicode doesn't exist in pyjamas
37 except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options
38 unicode = str
30 39
31 40
32 class ProfileManager(object): 41 class ProfileManager(object):
33 """Class managing all data relative to one profile, and plugging in mechanism""" 42 """Class managing all data relative to one profile, and plugging in mechanism"""
34 host = None 43 host = None
73 log.error("Couldn't get cached values: {}".format(failure)) 82 log.error("Couldn't get cached values: {}".format(failure))
74 self._plug_profile_gotCachedValues({}) 83 self._plug_profile_gotCachedValues({})
75 84
76 def _plug_profile_gotCachedValues(self, cached_values): 85 def _plug_profile_gotCachedValues(self, cached_values):
77 # TODO: watched plugin 86 # TODO: watched plugin
87
88 # add the contact list and its listener
78 contact_list = self.host.addContactList(self.profile) 89 contact_list = self.host.addContactList(self.profile)
90 self.host.contact_lists[self.profile] = contact_list
79 91
80 for entity, data in cached_values.iteritems(): 92 for entity, data in cached_values.iteritems():
81 for key, value in data.iteritems(): 93 for key, value in data.iteritems():
82 self.host.contact_lists[self.profile].setCache(jid.JID(entity), key, value) 94 contact_list.setCache(jid.JID(entity), key, value)
83 95
84 if not self.bridge.isConnected(self.profile): 96 if not self.bridge.isConnected(self.profile):
85 self.host.setStatusOnline(False, profile=self.profile) 97 self.host.setStatusOnline(False, profile=self.profile)
86 else: 98 else:
87 self.host.setStatusOnline(True, profile=self.profile) 99 self.host.setStatusOnline(True, profile=self.profile)
167 self._profiles[profile].plug() 179 self._profiles[profile].plug()
168 180
169 def unplug(self, profile): 181 def unplug(self, profile):
170 if profile not in self._profiles: 182 if profile not in self._profiles:
171 raise ValueError('The profile [{}] is not plugged'.format(profile)) 183 raise ValueError('The profile [{}] is not plugged'.format(profile))
184
185 # remove the contact list and its listener
186 host = self._profiles[profile].host
187 host.contact_lists[profile].onDelete()
188 del host.contact_lists[profile]
189
172 del self._profiles[profile] 190 del self._profiles[profile]
173 191
174 def chooseOneProfile(self): 192 def chooseOneProfile(self):
175 return self._profiles.keys()[0] 193 return self._profiles.keys()[0]
194
176 195
177 class QuickApp(object): 196 class QuickApp(object):
178 """This class contain the main methods needed for the frontend""" 197 """This class contain the main methods needed for the frontend"""
179 198
180 def __init__(self, create_bridge, check_options=None): 199 def __init__(self, create_bridge, check_options=None):
194 213
195 # widgets 214 # widgets
196 self.selected_widget = None # widget currently selected (must be filled by frontend) 215 self.selected_widget = None # widget currently selected (must be filled by frontend)
197 216
198 # listeners 217 # listeners
199 self._listeners = {} # key: listerner type ("avatar", "selected", etc), value: list of callbacks 218 self._listeners = {} # key: listener type ("avatar", "selected", etc), value: list of callbacks
200 219
201 ## bridge ## 220 ## bridge ##
202 try: 221 try:
203 self.bridge = create_bridge() 222 self.bridge = create_bridge()
204 except exceptions.BridgeExceptionNoService: 223 except exceptions.BridgeExceptionNoService:
285 if profile is not None and not self.check_profile(profile): 304 if profile is not None and not self.check_profile(profile):
286 return # we ignore signal for profiles we don't manage 305 return # we ignore signal for profiles we don't manage
287 handler(*args, **kwargs) 306 handler(*args, **kwargs)
288 self.bridge.register(functionName, signalReceived, iface) 307 self.bridge.register(functionName, signalReceived, iface)
289 308
290 def addListerner(self, type_, callback): 309 def addListener(self, type_, callback, profiles_filter=None):
291 """Add a listerner for an event 310 """Add a listener for an event
292 311
293 /!\ don't forget to remove listener when not used anymore (e.g. if you delete a widget) 312 /!\ don't forget to remove listener when not used anymore (e.g. if you delete a widget)
294 @param type_: type of event, can be: 313 @param type_: type of event, can be:
295 - avatar: called when avatar data is updated 314 - avatar: called when avatar data is updated
296 args: (entity, avatar file, profile) 315 args: (entity, avatar file)
316 - presence: called when a presence is received
317 args: (entity, show, priority, statuses)
297 @param callback: method to call on event 318 @param callback: method to call on event
319 @param profiles_filter (set[unicode]): if set and not empty, the
320 listener will be callable only by one of the given profiles.
298 """ 321 """
299 assert type_ in C.LISTENERS 322 assert type_ in C.LISTENERS
300 self._listeners.setdefault(type_, []).append(callback) 323 self._listeners.setdefault(type_, OrderedDict())[callback] = profiles_filter
301 324
302 def removeListener(self, type_, callback): 325 def removeListener(self, type_, callback):
303 """Remove a callback from listeners 326 """Remove a callback from listeners
304 327
305 @param type_: same as for [addListerner] 328 @param type_: same as for [addListener]
306 @param callback: callback to remove 329 @param callback: callback to remove
307 """ 330 """
308 assert type_ in C.LISTENERS 331 assert type_ in C.LISTENERS
309 self._listeners[type_].remove(callback) 332 self._listeners[type_].pop(callback)
310 333
311 def callListeners(self, type_, *args): 334 def callListeners(self, type_, profile, *args):
312 """Call all methods which listen of type_ event 335 """Call the methods which listen type_ event. If a profiles filter has
313 336 been register with a listener and profile argument is not None, the
314 @param type_: same as for [addListerner] 337 listener will be called only if profile is in the profiles filter list.
338
339 @param type_: same as for [addListener]
340 @param profile (unicode): %(doc_profile)s
315 @param *args: arguments sent to callback 341 @param *args: arguments sent to callback
316 """ 342 """
317 assert type_ in C.LISTENERS 343 assert type_ in C.LISTENERS
318 try: 344 try:
319 listeners = self._listeners[type_] 345 listeners = self._listeners[type_]
320 except KeyError: 346 except KeyError:
321 pass 347 pass
322 else: 348 else:
323 for listener in listeners: 349 for listener, profiles_filter in listeners.iteritems():
324 listener(*args) 350 if profile is None or not profiles_filter or profile in profiles_filter:
351 listener(*args)
325 352
326 def check_profile(self, profile): 353 def check_profile(self, profile):
327 """Tell if the profile is currently followed by the application""" 354 """Tell if the profile is currently followed by the application"""
328 return profile in self.profiles 355 return profile in self.profiles
329 356
372 399
373 def unplug_profile(self, profile): 400 def unplug_profile(self, profile):
374 """Tell the application to not follow anymore the profile""" 401 """Tell the application to not follow anymore the profile"""
375 if not profile in self.profiles: 402 if not profile in self.profiles:
376 raise ValueError("The profile [{}] is not plugged".format(profile)) 403 raise ValueError("The profile [{}] is not plugged".format(profile))
377 self.profiles.remove(profile) 404 self.profiles.unplug(profile)
378 405
379 def clear_profile(self): 406 def clear_profile(self):
380 self.profiles.clear() 407 self.profiles.clear()
381 408
382 def addContactList(self, profile): 409 def addContactList(self, profile):
466 493
467 # #FIXME: must be moved in a plugin 494 # #FIXME: must be moved in a plugin
468 # if entity.bare in self.profiles[profile].data.get('watched',[]) and not entity.bare in self.profiles[profile]['onlineContact']: 495 # if entity.bare in self.profiles[profile].data.get('watched',[]) and not entity.bare in self.profiles[profile]['onlineContact']:
469 # self.showAlert(_("Watched jid [%s] is connected !") % entity.bare) 496 # self.showAlert(_("Watched jid [%s] is connected !") % entity.bare)
470 497
471 self.contact_lists[profile].updatePresence(entity, show, priority, statuses) 498 self.callListeners('presence', profile, entity, show, priority, statuses)
472 499
473 def roomJoinedHandler(self, room_jid_s, room_nicks, user_nick, profile): 500 def roomJoinedHandler(self, room_jid_s, room_nicks, user_nick, profile):
474 """Called when a MUC room is joined""" 501 """Called when a MUC room is joined"""
475 log.debug("Room [%(room_jid)s] joined by %(profile)s, users presents:%(users)s" % {'room_jid': room_jid_s, 'profile': profile, 'users': room_nicks}) 502 log.debug("Room [%(room_jid)s] joined by %(profile)s, users presents:%(users)s" % {'room_jid': room_jid_s, 'profile': profile, 'users': room_nicks})
476 room_jid = jid.JID(room_jid_s) 503 room_jid = jid.JID(room_jid_s)
612 if from_jid == C.ENTITY_ALL or from_jid.bare == widget.target.bare: 639 if from_jid == C.ENTITY_ALL or from_jid.bare == widget.target.bare:
613 widget.updateChatState(from_jid, state) 640 widget.updateChatState(from_jid, state)
614 641
615 def _subscribe_cb(self, answer, data): 642 def _subscribe_cb(self, answer, data):
616 entity, profile = data 643 entity, profile = data
617 if answer: 644 type_ = "subscribed" if answer else "unsubscribed"
618 self.bridge.subscription("subscribed", entity.bare, profile_key=profile) 645 self.bridge.subscription(type_, unicode(entity.bare), profile_key=profile)
619 else:
620 self.bridge.subscription("unsubscribed", entity.bare, profile_key=profile)
621 646
622 def subscribeHandler(self, type, raw_jid, profile): 647 def subscribeHandler(self, type, raw_jid, profile):
623 """Called when a subsciption management signal is received""" 648 """Called when a subsciption management signal is received"""
624 entity = jid.JID(raw_jid) 649 entity = jid.JID(raw_jid)
625 if type == "subscribed": 650 if type == "subscribed":
630 self.showDialog(_("The contact %s has refused your subscription") % entity.bare, _('Subscription refusal'), 'error') 655 self.showDialog(_("The contact %s has refused your subscription") % entity.bare, _('Subscription refusal'), 'error')
631 elif type == "subscribe": 656 elif type == "subscribe":
632 # this is a subscriptionn request, we have to ask for user confirmation 657 # this is a subscriptionn request, we have to ask for user confirmation
633 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)) 658 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))
634 659
635 def showDialog(self, message, title, type="info", answer_cb=None): 660 def showDialog(self, message, title, type="info", answer_cb=None, answer_data=None):
636 raise NotImplementedError 661 raise NotImplementedError
637 662
638 def showAlert(self, message): 663 def showAlert(self, message):
639 pass #FIXME 664 pass #FIXME
640 665
648 elif (namespace, name) == ('General', C.SHOW_OFFLINE_CONTACTS): 673 elif (namespace, name) == ('General', C.SHOW_OFFLINE_CONTACTS):
649 self.contact_lists[profile].showOfflineContacts(C.bool(value)) 674 self.contact_lists[profile].showOfflineContacts(C.bool(value))
650 elif (namespace, name) == ('General', C.SHOW_EMPTY_GROUPS): 675 elif (namespace, name) == ('General', C.SHOW_EMPTY_GROUPS):
651 self.contact_lists[profile].showEmptyGroups(C.bool(value)) 676 self.contact_lists[profile].showEmptyGroups(C.bool(value))
652 677
653 def contactDeletedHandler(self, jid, profile): 678 def contactDeletedHandler(self, jid_s, profile):
654 target = jid.JID(jid) 679 target = jid.JID(jid_s)
655 self.contact_lists[profile].remove(target) 680 self.contact_lists[profile].remove(target)
656 681
657 def entityDataUpdatedHandler(self, entity_s, key, value, profile): 682 def entityDataUpdatedHandler(self, entity_s, key, value, profile):
658 entity = jid.JID(entity_s) 683 entity = jid.JID(entity_s)
659 if key == "nick": 684 if key == "nick":
661 self.contact_lists[profile].setCache(entity, 'nick', value) 686 self.contact_lists[profile].setCache(entity, 'nick', value)
662 elif key == "avatar": 687 elif key == "avatar":
663 if entity in self.contact_lists[profile]: 688 if entity in self.contact_lists[profile]:
664 def gotFilename(filename): 689 def gotFilename(filename):
665 self.contact_lists[profile].setCache(entity, 'avatar', filename) 690 self.contact_lists[profile].setCache(entity, 'avatar', filename)
666 self.callListeners('avatar', entity, filename, profile) 691 self.callListeners('avatar', profile, entity, filename)
667 self.bridge.getAvatarFile(value, callback=gotFilename) 692 self.bridge.getAvatarFile(value, callback=gotFilename)
668 693
669 def askConfirmationHandler(self, confirm_id, confirm_type, data, profile): 694 def askConfirmationHandler(self, confirm_id, confirm_type, data, profile):
670 raise NotImplementedError 695 raise NotImplementedError
671 696
685 """ 710 """
686 raise NotImplementedError 711 raise NotImplementedError
687 712
688 def onExit(self): 713 def onExit(self):
689 """Must be called when the frontend is terminating""" 714 """Must be called when the frontend is terminating"""
715 to_unplug = []
690 for profile in self.profiles: 716 for profile in self.profiles:
691 if self.bridge.isConnected(profile): 717 if self.bridge.isConnected(profile):
692 if C.bool(self.bridge.getParamA("autodisconnect", "Connection", profile_key=profile)): 718 if C.bool(self.bridge.getParamA("autodisconnect", "Connection", profile_key=profile)):
693 #The user wants autodisconnection 719 #The user wants autodisconnection
694 self.bridge.disconnect(profile) 720 self.bridge.disconnect(profile)
721 to_unplug.append(profile)
722 for profile in to_unplug:
723 self.unplug_profile(profile)