Mercurial > libervia-backend
comparison frontends/src/quick_frontend/quick_app.py @ 1290:faa1129559b8 frontends_multi_profiles
core, frontends: refactoring to base Libervia on QuickFrontend (big mixed commit):
/!\ not finished, everything is still instable !
- bridge: DBus bridge has been modified to allow blocking call to be called in the same way as asynchronous calls
- bridge: calls with a callback and no errback are now possible, default errback log the error
- constants: removed hack to manage presence without OrderedDict, as an OrderedDict like class has been implemented in Libervia
- core: getLastResource has been removed and replaced by getMainResource (there is a global better management of resources)
- various style improvments: use of constants when possible, fixed variable overlaps, import of module instead of direct class import
- frontends: printInfo and printMessage methods in (Quick)Chat are more generic (use of extra instead of timestamp)
- frontends: bridge creation and option parsing (command line arguments) are now specified by the frontend in QuickApp __init__
- frontends: ProfileManager manage a more complete plug sequence (some stuff formerly manage in contact_list have moved to ProfileManager)
- quick_frontend (quick_widgets): QuickWidgetsManager is now iterable (all widgets are then returned), or can return an iterator on a specific class (return all widgets of this class) with getWidgets
- frontends: tools.jid can now be used in Pyjamas, with some care
- frontends (XMLUI): profile is now managed
- core (memory): big improvment on entities cache management (and specially resource management)
- core (params/exceptions): added PermissionError
- various fixes and improvments, check diff for more details
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 24 Jan 2015 01:00:29 +0100 |
parents | e3a9ea76de35 |
children | 6c7d89843f1b |
comparison
equal
deleted
inserted
replaced
1289:653f2e2eea31 | 1290:faa1129559b8 |
---|---|
20 from sat.core.i18n import _ | 20 from sat.core.i18n import _ |
21 import sys | 21 import sys |
22 from sat.core.log import getLogger | 22 from sat.core.log import getLogger |
23 log = getLogger(__name__) | 23 log = getLogger(__name__) |
24 from sat.core import exceptions | 24 from sat.core import exceptions |
25 from sat_frontends.bridge.DBus import DBusBridgeFrontend | |
26 from sat_frontends.tools import jid | 25 from sat_frontends.tools import jid |
27 from sat_frontends.quick_frontend.quick_widgets import QuickWidgetsManager | 26 from sat_frontends.quick_frontend.quick_widgets import QuickWidgetsManager |
28 from sat_frontends.quick_frontend import quick_chat | 27 from sat_frontends.quick_frontend import quick_chat |
29 from optparse import OptionParser | |
30 | 28 |
31 from sat_frontends.quick_frontend.constants import Const as C | 29 from sat_frontends.quick_frontend.constants import Const as C |
32 | 30 |
33 | 31 |
34 class ProfileManager(object): | 32 class ProfileManager(object): |
76 self.host.setStatusOnline(True, profile=self.profile) | 74 self.host.setStatusOnline(True, profile=self.profile) |
77 | 75 |
78 contact_list.fill() | 76 contact_list.fill() |
79 | 77 |
80 #The waiting subscription requests | 78 #The waiting subscription requests |
81 waitingSub = self.bridge.getWaitingSub(self.profile) | 79 self.bridge.getWaitingSub(self.profile, callback=self._plug_profile_gotWaitingSub) |
82 for sub in waitingSub: | 80 |
83 self.host.subscribeHandler(waitingSub[sub], sub, self.profile) | 81 def _plug_profile_gotWaitingSub(self, waiting_sub): |
84 | 82 for sub in waiting_sub: |
85 #Now we open the MUC window where we already are: | 83 self.host.subscribeHandler(waiting_sub[sub], sub, self.profile) |
86 for room_args in self.bridge.getRoomsJoined(self.profile): | 84 |
87 self.host.roomJoinedHandler(*room_args, profile=self.profile) | 85 self.bridge.getRoomsJoined(self.profile, callback=self._plug_profile_gotRoomsJoined) |
88 | 86 |
89 for subject_args in self.bridge.getRoomsSubjects(self.profile): | 87 def _plug_profile_gotRoomsJoined(self, rooms_args): |
90 self.host.roomNewSubjectHandler(*subject_args, profile=self.profile) | 88 #Now we open the MUC window where we already are: |
91 | 89 for room_args in rooms_args: |
92 #Finaly, we get the waiting confirmation requests | 90 self.host.roomJoinedHandler(*room_args, profile=self.profile) |
93 for confirm_id, confirm_type, data in self.bridge.getWaitingConf(self.profile): | 91 |
94 self.host.askConfirmationHandler(confirm_id, confirm_type, data, self.profile) | 92 self.bridge.getRoomsSubjects(self.profile, callback=self._plug_profile_gotRoomsSubjects) |
93 | |
94 def _plug_profile_gotRoomsSubjects(self, subjects_args): | |
95 for subject_args in subjects_args: | |
96 self.host.roomNewSubjectHandler(*subject_args, profile=self.profile) | |
97 | |
98 #Presence must be requested after rooms are filled | |
99 self.host.bridge.getPresenceStatuses(self.profile, callback=self._plug_profile_gotPresences) | |
100 | |
101 | |
102 def _plug_profile_gotPresences(self, presences): | |
103 def gotEntityData(data, contact): | |
104 for key in ('avatar', 'nick'): | |
105 if key in data: | |
106 self.host.entityDataUpdatedHandler(contact, key, data[key], self.profile) | |
107 | |
108 for contact in presences: | |
109 for res in presences[contact]: | |
110 jabber_id = ('%s/%s' % (jid.JID(contact).bare, res)) if res else contact | |
111 show = presences[contact][res][0] | |
112 priority = presences[contact][res][1] | |
113 statuses = presences[contact][res][2] | |
114 self.host.presenceUpdateHandler(jabber_id, show, priority, statuses, self.profile) | |
115 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))) | |
116 | |
117 #Finaly, we get the waiting confirmation requests | |
118 self.bridge.getWaitingConf(self.profile, callback=self._plug_profile_gotWaitingConf) | |
119 | |
120 def _plug_profile_gotWaitingConf(self, waiting_confs): | |
121 for confirm_id, confirm_type, data in waiting_confs: | |
122 self.host.askConfirmationHandler(confirm_id, confirm_type, data, self.profile) | |
123 | |
124 # At this point, profile should be fully plugged | |
125 # and we launch frontend specific method | |
126 self.host.profilePlugged(self.profile) | |
95 | 127 |
96 def _getParamError(self, ignore): | 128 def _getParamError(self, ignore): |
97 log.error(_("Can't get profile parameter")) | 129 log.error(_("Can't get profile parameter")) |
98 | 130 |
99 | 131 |
130 return self._profiles.keys()[0] | 162 return self._profiles.keys()[0] |
131 | 163 |
132 class QuickApp(object): | 164 class QuickApp(object): |
133 """This class contain the main methods needed for the frontend""" | 165 """This class contain the main methods needed for the frontend""" |
134 | 166 |
135 def __init__(self): | 167 def __init__(self, create_bridge, check_options=None): |
168 """Create a frontend application | |
169 | |
170 @param create_bridge: method to use to create the Bridge | |
171 @param check_options: method to call to check options (usually command line arguments) | |
172 """ | |
136 ProfileManager.host = self | 173 ProfileManager.host = self |
137 self.profiles = ProfilesManager() | 174 self.profiles = ProfilesManager() |
138 self.contact_lists = {} | 175 self.contact_lists = {} |
139 self.widgets = QuickWidgetsManager(self) | 176 self.widgets = QuickWidgetsManager(self) |
140 self.check_options() | 177 if check_options is not None: |
178 self.options = check_options() | |
179 else: | |
180 self.options = None | |
141 | 181 |
142 # widgets | 182 # widgets |
143 self.visible_widgets = set() # widgets currently visible (must be filled by frontend) | |
144 self.selected_widget = None # widget currently selected (must be filled by frontend) | 183 self.selected_widget = None # widget currently selected (must be filled by frontend) |
145 | 184 |
146 ## bridge ## | 185 ## bridge ## |
147 try: | 186 try: |
148 self.bridge = DBusBridgeFrontend() | 187 self.bridge = create_bridge() |
149 except exceptions.BridgeExceptionNoService: | 188 except exceptions.BridgeExceptionNoService: |
150 print(_(u"Can't connect to SàT backend, are you sure it's launched ?")) | 189 print(_(u"Can't connect to SàT backend, are you sure it's launched ?")) |
151 sys.exit(1) | 190 sys.exit(1) |
152 except exceptions.BridgeInitError: | 191 except exceptions.BridgeInitError: |
153 print(_(u"Can't init bridge")) | 192 print(_(u"Can't init bridge")) |
200 try: | 239 try: |
201 return self.selected_widget.profile | 240 return self.selected_widget.profile |
202 except (TypeError, AttributeError): | 241 except (TypeError, AttributeError): |
203 return self.profiles.chooseOneProfile() | 242 return self.profiles.chooseOneProfile() |
204 | 243 |
244 @property | |
245 def visible_widgets(self): | |
246 """widgets currently visible (must be implemented by frontend)""" | |
247 raise NotImplementedError | |
248 | |
205 def registerSignal(self, functionName, handler=None, iface="core", with_profile=True): | 249 def registerSignal(self, functionName, handler=None, iface="core", with_profile=True): |
206 """Register a handler for a signal | 250 """Register a handler for a signal |
207 | 251 |
208 @param functionName (str): name of the signal to handle | 252 @param functionName (str): name of the signal to handle |
209 @param handler (instancemethod): method to call when the signal arrive, None for calling an automatically named handler (functionName + 'Handler') | 253 @param handler (instancemethod): method to call when the signal arrive, None for calling an automatically named handler (functionName + 'Handler') |
210 @param iface (str): interface of the bridge to use ('core' or 'plugin') | 254 @param iface (str): interface of the bridge to use ('core' or 'plugin') |
211 @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 | 255 @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 |
212 """ | 256 """ |
213 if handler is None: | 257 if handler is None: |
214 handler = getattr(self, "%s%s" % (functionName, 'Handler')) | 258 handler = getattr(self, "{}{}".format(functionName, 'Handler')) |
215 if not with_profile: | 259 if not with_profile: |
216 self.bridge.register(functionName, handler, iface) | 260 self.bridge.register(functionName, handler, iface) |
217 return | 261 return |
218 | 262 |
219 def signalReceived(*args, **kwargs): | 263 def signalReceived(*args, **kwargs): |
234 def postInit(self, profile_manager): | 278 def postInit(self, profile_manager): |
235 """Must be called after initialization is done, do all automatic task (auto plug profile) | 279 """Must be called after initialization is done, do all automatic task (auto plug profile) |
236 | 280 |
237 @param profile_manager: instance of a subclass of Quick_frontend.QuickProfileManager | 281 @param profile_manager: instance of a subclass of Quick_frontend.QuickProfileManager |
238 """ | 282 """ |
239 if self.options.profile: | 283 if self.options and self.options.profile: |
240 profile_manager.autoconnect([self.options.profile]) | 284 profile_manager.autoconnect([self.options.profile]) |
241 | 285 |
242 def check_options(self): | 286 def profilePlugged(self, profile): |
243 """Check command line options""" | 287 """Method called when the profile is fully plugged, to launch frontend specific workflow |
244 usage = _(""" | 288 |
245 %prog [options] | 289 @param profile(unicode): %(doc_profile)s |
246 | 290 """ |
247 %prog --help for options list | 291 pass |
248 """) | |
249 parser = OptionParser(usage=usage) # TODO: use argparse | |
250 | |
251 parser.add_option("-p", "--profile", help=_("Select the profile to use")) | |
252 | |
253 (self.options, args) = parser.parse_args() | |
254 if self.options.profile: | |
255 self.options.profile = self.options.profile.decode('utf-8') | |
256 return args | |
257 | 292 |
258 def asyncConnect(self, profile, callback=None, errback=None): | 293 def asyncConnect(self, profile, callback=None, errback=None): |
259 if not callback: | 294 if not callback: |
260 callback = lambda dummy: None | 295 callback = lambda dummy: None |
261 if not errback: | 296 if not errback: |
279 def plugging_profiles(self): | 314 def plugging_profiles(self): |
280 """Method to subclass to manage frontend specific things to do | 315 """Method to subclass to manage frontend specific things to do |
281 | 316 |
282 will be called when profiles are choosen and are to be plugged soon | 317 will be called when profiles are choosen and are to be plugged soon |
283 """ | 318 """ |
284 raise NotImplementedError | 319 pass |
285 | 320 |
286 def unplug_profile(self, profile): | 321 def unplug_profile(self, profile): |
287 """Tell the application to not follow anymore the profile""" | 322 """Tell the application to not follow anymore the profile""" |
288 if not profile in self.profiles: | 323 if not profile in self.profiles: |
289 raise ValueError("The profile [{}] is not plugged".format(profile)) | 324 raise ValueError("The profile [{}] is not plugged".format(profile)) |
290 self.profiles.remove(profile) | 325 self.profiles.remove(profile) |
291 | 326 |
292 def clear_profile(self): | 327 def clear_profile(self): |
293 self.profiles.clear() | 328 self.profiles.clear() |
294 | 329 |
330 def addContactList(self, profile): | |
331 """Method to subclass to add a contact list widget | |
332 | |
333 will be called on each profile session build | |
334 @return: a ContactList widget | |
335 """ | |
336 return NotImplementedError | |
337 | |
295 def newWidget(self, widget): | 338 def newWidget(self, widget): |
296 raise NotImplementedError | 339 raise NotImplementedError |
297 | 340 |
298 def connectedHandler(self, profile): | 341 def connectedHandler(self, profile): |
299 """called when the connection is made""" | 342 """called when the connection is made""" |
329 | 372 |
330 chat_widget.newMessage(from_jid, target, msg, type_, extra, profile) | 373 chat_widget.newMessage(from_jid, target, msg, type_, extra, profile) |
331 | 374 |
332 def sendMessage(self, to_jid, message, subject='', mess_type="auto", extra={}, callback=None, errback=None, profile_key=C.PROF_KEY_NONE): | 375 def sendMessage(self, to_jid, message, subject='', mess_type="auto", extra={}, callback=None, errback=None, profile_key=C.PROF_KEY_NONE): |
333 if callback is None: | 376 if callback is None: |
334 callback = lambda: None | 377 callback = lambda dummy=None: None # FIXME: optional argument is here because pyjamas doesn't support callback without arg with json proxy |
335 if errback is None: | 378 if errback is None: |
336 errback = lambda failure: self.showDialog(failure.fullname, failure.message, "error") | 379 errback = lambda failure: self.showDialog(failure.fullname, failure.message, "error") |
337 self.bridge.sendMessage(to_jid, message, subject, mess_type, extra, profile_key, callback=callback, errback=errback) | 380 self.bridge.sendMessage(to_jid, message, subject, mess_type, extra, profile_key, callback=callback, errback=errback) |
338 | 381 |
339 def newAlertHandler(self, msg, title, alert_type, profile): | 382 def newAlertHandler(self, msg, title, alert_type, profile): |
498 @param from_jid_s: JID of the contact who sent his state, or '@ALL@' | 541 @param from_jid_s: JID of the contact who sent his state, or '@ALL@' |
499 @param state: new state (string) | 542 @param state: new state (string) |
500 @profile: current profile | 543 @profile: current profile |
501 """ | 544 """ |
502 from_jid = jid.JID(from_jid_s) if from_jid_s != C.ENTITY_ALL else C.ENTITY_ALL | 545 from_jid = jid.JID(from_jid_s) if from_jid_s != C.ENTITY_ALL else C.ENTITY_ALL |
503 for widget in self.visible_widgets: | 546 for widget in self.widgets.getWidgets(quick_chat.QuickChat): |
504 if isinstance(widget, quick_chat.QuickChat): | 547 if from_jid == C.ENTITY_ALL or from_jid.bare == widget.target.bare: |
505 if from_jid == C.ENTITY_ALL or from_jid.bare == widget.target.bare: | 548 widget.updateChatState(from_jid, state) |
506 widget.updateChatState(from_jid, state) | |
507 | 549 |
508 def _subscribe_cb(self, answer, data): | 550 def _subscribe_cb(self, answer, data): |
509 entity, profile = data | 551 entity, profile = data |
510 if answer: | 552 if answer: |
511 self.bridge.subscription("subscribed", entity.bare, profile_key=profile) | 553 self.bridge.subscription("subscribed", entity.bare, profile_key=profile) |
552 if key == "nick": | 594 if key == "nick": |
553 if entity in self.contact_lists[profile]: | 595 if entity in self.contact_lists[profile]: |
554 self.contact_lists[profile].setCache(entity, 'nick', value) | 596 self.contact_lists[profile].setCache(entity, 'nick', value) |
555 elif key == "avatar": | 597 elif key == "avatar": |
556 if entity in self.contact_lists[profile]: | 598 if entity in self.contact_lists[profile]: |
557 filename = self.bridge.getAvatarFile(value) | 599 def gotFilename(filename): |
558 self.contact_lists[profile].setCache(entity, 'avatar', filename) | 600 self.contact_lists[profile].setCache(entity, 'avatar', filename) |
601 self.bridge.getAvatarFile(value, callback=gotFilename) | |
559 | 602 |
560 def askConfirmationHandler(self, confirm_id, confirm_type, data, profile): | 603 def askConfirmationHandler(self, confirm_id, confirm_type, data, profile): |
561 raise NotImplementedError | 604 raise NotImplementedError |
562 | 605 |
563 def actionResultHandler(self, type, id, data, profile): | 606 def actionResultHandler(self, type, id, data, profile): |