changeset 1305:3dc7f61677bb frontends_multi_profiles

merged souliane commits
author Goffi <goffi@goffi.org>
date Fri, 06 Feb 2015 19:29:37 +0100
parents 1a61b18703c4 (diff) ef7e8e23b353 (current diff)
children 789e86a8919d
files frontends/src/quick_frontend/quick_contact_list.py
diffstat 5 files changed, 116 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/frontends/src/constants.py	Thu Feb 05 12:10:42 2015 +0100
+++ b/frontends/src/constants.py	Fri Feb 06 19:29:37 2015 +0100
@@ -62,6 +62,14 @@
     # Roster
     GROUP_NOT_IN_ROSTER = D_('Not in roster')
 
-    #Chats
+    # Chats
     CHAT_ONE2ONE = 'one2one'
     CHAT_GROUP = 'group'
+
+    # Widgets management
+    # FIXME: should be in quick_frontend.constant, but Libervia doesn't inherit from it
+    WIDGET_NEW = 'NEW'
+    WIDGET_KEEP = 'KEEP'
+    WIDGET_RAISE = 'RAISE'
+    WIDGET_RECREATE = 'RECREATE'
+
--- a/frontends/src/quick_frontend/quick_contact_list.py	Thu Feb 05 12:10:42 2015 +0100
+++ b/frontends/src/quick_frontend/quick_contact_list.py	Fri Feb 06 19:29:37 2015 +0100
@@ -293,6 +293,15 @@
                 return True
         return False
 
+    def isEntityInGroup(self, entity, group):
+        """Tell if an entity is in a roster group
+
+        @param entity(jid.JID): jid of the entity
+        @param group(unicode): group to check
+        @return (bool): True if the entity is in the group
+        """
+        return entity in self.getGroupData(group, "jids")
+
     def remove(self, entity):
         """remove a contact from the list
 
--- a/frontends/src/quick_frontend/quick_widgets.py	Thu Feb 05 12:10:42 2015 +0100
+++ b/frontends/src/quick_frontend/quick_widgets.py	Fri Feb 06 19:29:37 2015 +0100
@@ -21,6 +21,7 @@
 log = getLogger(__name__)
 from sat.core import exceptions
 
+from sat_frontends.quick_frontend.constants import Const as C
 
 classes_map = {}
 
@@ -39,7 +40,13 @@
     @param child_cls: inherited class to use when Quick... class is requested, must inherit from base_cls.
         Can be None if it's the base_cls itself which register
     """
-    classes_map[base_cls] = child_cls
+    # FIXME: we use base_cls.__name__ instead of base_cls directly because pyjamas because
+    #        in the second case
+    classes_map[base_cls.__name__] = child_cls
+
+
+class WidgetAlreadyExistsError(Exception):
+    pass
 
 
 class QuickWidgetsManager(object):
@@ -63,7 +70,9 @@
         @return: class actually used to create widget
         """
         try:
-            cls = classes_map[class_]
+            # FIXME: we use base_cls.__name__ instead of base_cls directly because pyjamas bugs
+            #        in the second case
+            cls = classes_map[class_.__name__]
         except KeyError:
             cls = class_
         if cls is None:
@@ -78,7 +87,7 @@
         """
         class_ = self.getRealClass(class_)
         try:
-            widgets_map = self._widgets[class_]
+            widgets_map = self._widgets[class_.__name__]
         except KeyError:
             return iter([])
         else:
@@ -95,15 +104,19 @@
             if 'profile' key is present, it will be popped and put in 'profiles'
             if there is neither 'profile' nor 'profiles', None will be used for 'profiles'
             if 'on_new_widget' is present it can have the following values:
-                'NEW_WIDGET' [default]: self.host.newWidget will be called on widget creation
+                C.WIDGET_NEW [default]: self.host.newWidget will be called on widget creation
                 [callable]: this method will be called instead of self.host.newWidget
                 None: do nothing
+            if 'on_existing_widget' is present it can have the following values:
+                C.WIDGET_KEEP  [default]: return the existing widget
+                C.WIDGET_RAISE: raise WidgetAlreadyExistsError
+                C.WIDGET_RECREATE: create a new widget *WITH A NEW HASH*
             if 'force_hash' is present, the hash given in value will be used instead of the one returned by class_.getWidgetHash
         @return: a class_ instance, either new or already existing
         """
         cls = self.getRealClass(class_)
 
-        # arguments management
+        ## arguments management ##
         _args = [self.host, target] + list(args) or [] # FIXME: check if it's really necessary to use optional args
         _kwargs = kwargs or {}
         if 'profiles' in _kwargs and 'profile' in _kwargs:
@@ -114,14 +127,27 @@
             if not 'profiles' in _kwargs:
                 _kwargs['profiles'] = None
 
-        # we get the hash
+        #on_new_widget tell what to do for the new widget creation
+        try:
+            on_new_widget = _kwargs.pop('on_new_widget')
+        except KeyError:
+            on_new_widget = C.WIDGET_NEW
+
+        #on_existing_widget tell what to do when the widget already exists
+        try:
+            on_existing_widget = _kwargs.pop('on_existing_widget')
+        except KeyError:
+            on_existing_widget = C.WIDGET_KEEP
+
+        ## we get the hash ##
         try:
             hash_ = _kwargs.pop('force_hash')
         except KeyError:
             hash_ = cls.getWidgetHash(target, _kwargs['profiles'])
 
-        # widget creation or retrieval
-        widgets_map = self._widgets.setdefault(cls, {}) # we sorts widgets by classes
+        ## widget creation or retrieval ##
+
+        widgets_map = self._widgets.setdefault(cls.__name__, {}) # we sorts widgets by classes
         if not cls.SINGLE:
             widget = None # if the class is not SINGLE, we always create a new widget
         else:
@@ -133,25 +159,59 @@
 
         if widget is None:
             # we need to create a new widget
-            try:
-                #on_new_widget tell what to do for the new widget creation
-                on_new_widget = _kwargs.pop('on_new_widget')
-            except KeyError:
-                on_new_widget = 'NEW_WIDGET'
-
             log.debug(u"Creating new widget for target {} {}".format(target, cls))
             widget = cls(*_args, **_kwargs)
             widgets_map[hash_] = widget
 
-            if on_new_widget == 'NEW_WIDGET':
+            if on_new_widget == C.WIDGET_NEW:
                 self.host.newWidget(widget)
             elif callable(on_new_widget):
                 on_new_widget(widget)
             else:
                 assert on_new_widget is None
+        else:
+            # the widget already exists
+            if on_existing_widget == C.WIDGET_RAISE:
+                raise WidgetAlreadyExistsError(hash_)
+            elif on_existing_widget == C.WIDGET_RECREATE:
+                # we use getOrCreateWidget to recreate the new widget
+                # /!\ we use args and kwargs and not _args and _kwargs because we need the original args
+                #     we need to get rid of kwargs special options
+                new_kwargs = kwargs.copy()
+                try:
+                    new_kwargs.pop('force_hash')  # FIXME: we use pop instead of del here because pyjamas doesn't raise error on del
+                except KeyError:
+                    pass
+                else:
+                    raise ValueError("force_hash option can't be used with on_existing_widget=RECREATE")
 
+                # XXX: keep up-to-date if new special kwargs are added (i.e.: delete these keys here)
+                new_kwargs['on_new_widget'] = None
+                new_kwargs['on_existing_widget'] = C.WIDGET_RAISE
+                hash_idx = 1
+                while True:
+                    new_kwargs['force_hash'] = hash_ + "_new_instance_{}".format(hash_idx)
+                    try:
+                        widget = self.getOrCreateWidget(class_, target, *args, **new_kwargs)
+                    except WidgetAlreadyExistsError:
+                        hash_idx += 1
+                    else:
+                        log.debug(u"Widget already exists, a new one has been recreated with hash {}".format(new_kwargs['force_hash']))
+                        break
         return widget
 
+    def deleteWidget(self, widget_to_delete):
+        """Delete a widget
+
+        widget's onDelete method will be called before deletion
+        """
+        widget_to_delete.onDelete()
+
+        for widget_map in self._widgets.itervalues():
+            for hash_, widget in widget_map.iteritems():
+                if widget_to_delete is widget:
+                    del widget_map[hash_]
+
 
 class QuickWidget(object):
     """generic widget base"""
@@ -220,3 +280,7 @@
         @return: a hash (can correspond to one or many targets or profiles, depending of widget class)
         """
         return unicode(target) # by defaut, there is one hash for one target
+
+    def onDelete(self):
+        """Called when a widget is deleted"""
+        log.debug(u"deleting widget {}".format(self)) # Must be implemented by frontends
--- a/src/core/constants.py	Thu Feb 05 12:10:42 2015 +0100
+++ b/src/core/constants.py	Fri Feb 06 19:29:37 2015 +0100
@@ -219,14 +219,12 @@
 
     @classmethod
     def bool(cls, value):
-        """@return: bool value for any type"""
-        if isinstance(value, str) or isinstance(value, unicode):  # dbus.String is unicode but not str
-            return value.lower() == cls.BOOL_TRUE
-        return bool(value)
+        """@return (bool): bool value for associated constant"""
+        assert isinstance(value, basestring)
+        return value.lower() == cls.BOOL_TRUE
 
     @classmethod
-    def str(cls, value):
-        """@return: str text value for any type"""
-        if isinstance(value, bool):
-            return cls.BOOL_TRUE if value else cls.BOOL_FALSE
-        return str(value)
+    def boolConst(cls, value):
+        """@return (str): constant associated to bool value"""
+        assert isinstance(value, bool)
+        return cls.BOOL_TRUE if value else cls.BOOL_FALSE
--- a/src/core/xmpp.py	Thu Feb 05 12:10:42 2015 +0100
+++ b/src/core/xmpp.py	Fri Feb 06 19:29:37 2015 +0100
@@ -180,6 +180,8 @@
     def rosterCb(self, roster):
         assert roster is not None # FIXME: must be managed with roster versioning
         self._jids = roster
+        for roster_item in roster.itervalues():
+            self._registerItem(roster_item)
 
     def _registerItem(self, item):
         """Register item in local cache
@@ -187,10 +189,9 @@
         item must be already registered in self._jids before this method is called
         @param item (RosterIem): item added
         """
-        log.debug("registering item: {}".format(item.jid.full()))
+        log.debug(u"registering item: {}".format(item.jid.full()))
         if item.entity.resource:
-            log.warning("Received a roster item with a resource, this is not common but not restricted by RFC 6121, this case may be not well tested.")
-            import ipdb; ipdb.set_trace()
+            log.warning(u"Received a roster item with a resource, this is not common but not restricted by RFC 6121, this case may be not well tested.")
         if not item.subscriptionTo and not item.subscriptionFrom and not item.ask:
             #XXX: current behaviour: we don't want contact in our roster list
             # if there is no presence subscription
@@ -199,12 +200,11 @@
             return
         if not item.subscriptionTo:
             if not item.subscriptionFrom:
-                log.info(_("There's no subscription between you and [{}]!").format(item.jid.full()))
+                log.info(_(u"There's no subscription between you and [{}]!").format(item.jid.full()))
             else:
-                log.info(_("You are not subscribed to [{}]!").format(item.jid.full()))
+                log.info(_(u"You are not subscribed to [{}]!").format(item.jid.full()))
         if not item.subscriptionFrom:
-            log.info(_("[{}] is not subscribed to you!").format(item.jid.full()))
-        #self.host.memory.addContact(item.jid, item_attr, item.groups, self.parent.profile)
+            log.info(_(u"[{}] is not subscribed to you!").format(item.jid.full()))
 
         for group in item.groups:
             self._groups.setdefault(group, set()).add(item.entity)
@@ -242,7 +242,7 @@
 
     def removeReceived(self, request):
         entity = request.item.entity
-        print _("removing %s from roster list") % entity.full()
+        log.info(u"removing %s from roster list" % entity.full())
 
         # we first remove item from local cache (self._groups and self._jids)
         try:
@@ -267,13 +267,13 @@
         """Return a list of groups"""
         return self._groups.keys()
 
-    def getItem(self, jid):
+    def getItem(self, entity_jid):
         """Return RosterItem for a given jid
 
-        @param jid: jid of the contact
+        @param entity_jid: jid of the contact
         @return: RosterItem or None if contact is not in cache
         """
-        return self._jids.get(jid, None)
+        return self._jids.get(entity_jid, None)
 
     def getJids(self):
         """Return all jids of the roster"""
@@ -291,7 +291,7 @@
         try:
             return self._groups[group]
         except KeyError:
-            raise exceptions.UnknownGroupError
+            raise exceptions.UnknownGroupError(group)
 
 
 class SatPresenceProtocol(xmppim.PresenceClientProtocol):