diff 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
line wrap: on
line diff
--- a/frontends/src/quick_frontend/quick_app.py	Sat Jan 24 00:15:01 2015 +0100
+++ b/frontends/src/quick_frontend/quick_app.py	Sat Jan 24 01:00:29 2015 +0100
@@ -22,11 +22,9 @@
 from sat.core.log import getLogger
 log = getLogger(__name__)
 from sat.core import exceptions
-from sat_frontends.bridge.DBus import DBusBridgeFrontend
 from sat_frontends.tools import jid
 from sat_frontends.quick_frontend.quick_widgets import QuickWidgetsManager
 from sat_frontends.quick_frontend import quick_chat
-from optparse import OptionParser
 
 from sat_frontends.quick_frontend.constants import Const as C
 
@@ -78,20 +76,54 @@
             contact_list.fill()
 
             #The waiting subscription requests
-            waitingSub = self.bridge.getWaitingSub(self.profile)
-            for sub in waitingSub:
-                self.host.subscribeHandler(waitingSub[sub], sub, self.profile)
+            self.bridge.getWaitingSub(self.profile, callback=self._plug_profile_gotWaitingSub)
+
+    def _plug_profile_gotWaitingSub(self, waiting_sub):
+        for sub in waiting_sub:
+            self.host.subscribeHandler(waiting_sub[sub], sub, self.profile)
+
+        self.bridge.getRoomsJoined(self.profile, callback=self._plug_profile_gotRoomsJoined)
+
+    def _plug_profile_gotRoomsJoined(self, rooms_args):
+        #Now we open the MUC window where we already are:
+        for room_args in rooms_args:
+            self.host.roomJoinedHandler(*room_args, profile=self.profile)
+
+        self.bridge.getRoomsSubjects(self.profile, callback=self._plug_profile_gotRoomsSubjects)
+
+    def _plug_profile_gotRoomsSubjects(self, subjects_args):
+        for subject_args in subjects_args:
+            self.host.roomNewSubjectHandler(*subject_args, profile=self.profile)
+
+        #Presence must be requested after rooms are filled
+        self.host.bridge.getPresenceStatuses(self.profile, callback=self._plug_profile_gotPresences)
+
 
-            #Now we open the MUC window where we already are:
-            for room_args in self.bridge.getRoomsJoined(self.profile):
-                self.host.roomJoinedHandler(*room_args, profile=self.profile)
+    def _plug_profile_gotPresences(self, presences):
+        def gotEntityData(data, contact):
+            for key in ('avatar', 'nick'):
+                if key in data:
+                    self.host.entityDataUpdatedHandler(contact, key, data[key], self.profile)
 
-            for subject_args in self.bridge.getRoomsSubjects(self.profile):
-                self.host.roomNewSubjectHandler(*subject_args, profile=self.profile)
+        for contact in presences:
+            for res in presences[contact]:
+                jabber_id = ('%s/%s' % (jid.JID(contact).bare, res)) if res else contact
+                show = presences[contact][res][0]
+                priority = presences[contact][res][1]
+                statuses = presences[contact][res][2]
+                self.host.presenceUpdateHandler(jabber_id, show, priority, statuses, self.profile)
+            self.host.bridge.getEntityData(contact, ['avatar', 'nick'], self.profile, callback=lambda data, contact=contact: gotEntityData(data, contact), errback=lambda failure, contact=contact: log.debug("No cache data for {}".format(contact)))
 
-            #Finaly, we get the waiting confirmation requests
-            for confirm_id, confirm_type, data in self.bridge.getWaitingConf(self.profile):
-                self.host.askConfirmationHandler(confirm_id, confirm_type, data, self.profile)
+        #Finaly, we get the waiting confirmation requests
+        self.bridge.getWaitingConf(self.profile, callback=self._plug_profile_gotWaitingConf)
+
+    def _plug_profile_gotWaitingConf(self, waiting_confs):
+        for confirm_id, confirm_type, data in waiting_confs:
+            self.host.askConfirmationHandler(confirm_id, confirm_type, data, self.profile)
+
+        # At this point, profile should be fully plugged
+        # and we launch frontend specific method
+        self.host.profilePlugged(self.profile)
 
     def _getParamError(self, ignore):
         log.error(_("Can't get profile parameter"))
@@ -132,20 +164,27 @@
 class QuickApp(object):
     """This class contain the main methods needed for the frontend"""
 
-    def __init__(self):
+    def __init__(self, create_bridge, check_options=None):
+        """Create a frontend application
+
+        @param create_bridge: method to use to create the Bridge
+        @param check_options: method to call to check options (usually command line arguments)
+        """
         ProfileManager.host = self
         self.profiles = ProfilesManager()
         self.contact_lists = {}
         self.widgets = QuickWidgetsManager(self)
-        self.check_options()
+        if check_options is not None:
+            self.options = check_options()
+        else:
+            self.options = None
 
         # widgets
-        self.visible_widgets = set() # widgets currently visible (must be filled by frontend)
         self.selected_widget = None # widget currently selected (must be filled by frontend)
 
         ## bridge ##
         try:
-            self.bridge = DBusBridgeFrontend()
+            self.bridge = create_bridge()
         except exceptions.BridgeExceptionNoService:
             print(_(u"Can't connect to SàT backend, are you sure it's launched ?"))
             sys.exit(1)
@@ -202,6 +241,11 @@
         except (TypeError, AttributeError):
             return self.profiles.chooseOneProfile()
 
+    @property
+    def visible_widgets(self):
+        """widgets currently visible (must be implemented by frontend)"""
+        raise NotImplementedError
+
     def registerSignal(self, functionName, handler=None, iface="core", with_profile=True):
         """Register a handler for a signal
 
@@ -211,7 +255,7 @@
         @param with_profile (boolean): True if the signal concerns a specific profile, in that case the profile name has to be passed by the caller
         """
         if handler is None:
-            handler = getattr(self, "%s%s" % (functionName, 'Handler'))
+            handler = getattr(self, "{}{}".format(functionName, 'Handler'))
         if not with_profile:
             self.bridge.register(functionName, handler, iface)
             return
@@ -236,24 +280,15 @@
 
         @param profile_manager: instance of a subclass of Quick_frontend.QuickProfileManager
         """
-        if self.options.profile:
+        if self.options and self.options.profile:
             profile_manager.autoconnect([self.options.profile])
 
-    def check_options(self):
-        """Check command line options"""
-        usage = _("""
-        %prog [options]
+    def profilePlugged(self, profile):
+        """Method called when the profile is fully plugged, to launch frontend specific workflow
 
-        %prog --help for options list
-        """)
-        parser = OptionParser(usage=usage) # TODO: use argparse
-
-        parser.add_option("-p", "--profile", help=_("Select the profile to use"))
-
-        (self.options, args) = parser.parse_args()
-        if self.options.profile:
-            self.options.profile = self.options.profile.decode('utf-8')
-        return args
+        @param profile(unicode): %(doc_profile)s
+        """
+        pass
 
     def asyncConnect(self, profile, callback=None, errback=None):
         if not callback:
@@ -281,7 +316,7 @@
 
         will be called when profiles are choosen and are to be plugged soon
         """
-        raise NotImplementedError
+        pass
 
     def unplug_profile(self, profile):
         """Tell the application to not follow anymore the profile"""
@@ -292,6 +327,14 @@
     def clear_profile(self):
         self.profiles.clear()
 
+    def addContactList(self, profile):
+        """Method to subclass to add a contact list widget
+
+        will be called on each profile session build
+        @return: a ContactList widget
+        """
+        return NotImplementedError
+
     def newWidget(self, widget):
         raise NotImplementedError
 
@@ -331,7 +374,7 @@
 
     def sendMessage(self, to_jid, message, subject='', mess_type="auto", extra={}, callback=None, errback=None, profile_key=C.PROF_KEY_NONE):
         if callback is None:
-            callback = lambda: None
+            callback = lambda dummy=None: None # FIXME: optional argument is here because pyjamas doesn't support callback without arg with json proxy
         if errback is None:
             errback = lambda failure: self.showDialog(failure.fullname, failure.message, "error")
         self.bridge.sendMessage(to_jid, message, subject, mess_type, extra, profile_key, callback=callback, errback=errback)
@@ -500,10 +543,9 @@
         @profile: current profile
         """
         from_jid = jid.JID(from_jid_s) if from_jid_s != C.ENTITY_ALL else C.ENTITY_ALL
-        for widget in self.visible_widgets:
-            if isinstance(widget, quick_chat.QuickChat):
-                if from_jid == C.ENTITY_ALL or from_jid.bare == widget.target.bare:
-                    widget.updateChatState(from_jid, state)
+        for widget in self.widgets.getWidgets(quick_chat.QuickChat):
+            if from_jid == C.ENTITY_ALL or from_jid.bare == widget.target.bare:
+                widget.updateChatState(from_jid, state)
 
     def _subscribe_cb(self, answer, data):
         entity, profile = data
@@ -554,8 +596,9 @@
                 self.contact_lists[profile].setCache(entity, 'nick', value)
         elif key == "avatar":
             if entity in self.contact_lists[profile]:
-                filename = self.bridge.getAvatarFile(value)
-                self.contact_lists[profile].setCache(entity, 'avatar', filename)
+                def gotFilename(filename):
+                    self.contact_lists[profile].setCache(entity, 'avatar', filename)
+                self.bridge.getAvatarFile(value, callback=gotFilename)
 
     def askConfirmationHandler(self, confirm_id, confirm_type, data, profile):
         raise NotImplementedError