# HG changeset patch # User Goffi # Date 1423247377 -3600 # Node ID 3dc7f61677bbb7a6339c810e1ad66082a3666a34 # Parent 1a61b18703c4563ce9380af427270163169253c8# Parent ef7e8e23b35385469296651054966efca69d1caf merged souliane commits diff -r ef7e8e23b353 -r 3dc7f61677bb frontends/src/constants.py --- 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' + diff -r ef7e8e23b353 -r 3dc7f61677bb frontends/src/quick_frontend/quick_contact_list.py --- 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 diff -r ef7e8e23b353 -r 3dc7f61677bb frontends/src/quick_frontend/quick_widgets.py --- 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 diff -r ef7e8e23b353 -r 3dc7f61677bb src/core/constants.py --- 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 diff -r ef7e8e23b353 -r 3dc7f61677bb src/core/xmpp.py --- 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):