# HG changeset patch # User Goffi # Date 1485458988 -3600 # Node ID 2f264f3df2807c340e8bb0eac58162ebb39a91e5 # Parent ca82c97db195e8d37cba8d70d29953a65e3455f4 core (menus): improvments: - use the new convention for bridge names (getMenus ==> menusGet, etc.) - menu now use canonical path, which is the untranslated path with each element stripped and lowercase, it must be unique by menu type - added menuLaunch method to manually launch a menu like an action, canonical path is used instead of id - added SECURITY_LIMIT_MAX constant diff -r ca82c97db195 -r 2f264f3df280 frontends/src/bridge/dbus_bridge.py --- a/frontends/src/bridge/dbus_bridge.py Thu Jan 26 20:24:58 2017 +0100 +++ b/frontends/src/bridge/dbus_bridge.py Thu Jan 26 20:29:48 2017 +0100 @@ -338,34 +338,6 @@ kwargs['error_handler'] = error_handler return unicode(self.db_core_iface.getMainResource(contact_jid, profile_key, **kwargs)) - def getMenuHelp(self, menu_id, language, callback=None, errback=None): - if callback is None: - error_handler = None - else: - if errback is None: - errback = log.error - error_handler = lambda err:errback(dbus_to_bridge_exception(err)) - kwargs={} - if callback is not None: - kwargs['timeout'] = const_TIMEOUT - kwargs['reply_handler'] = callback - kwargs['error_handler'] = error_handler - return unicode(self.db_core_iface.getMenuHelp(menu_id, language, **kwargs)) - - def getMenus(self, language, security_limit, callback=None, errback=None): - if callback is None: - error_handler = None - else: - if errback is None: - errback = log.error - error_handler = lambda err:errback(dbus_to_bridge_exception(err)) - kwargs={} - if callback is not None: - kwargs['timeout'] = const_TIMEOUT - kwargs['reply_handler'] = callback - kwargs['error_handler'] = error_handler - return self.db_core_iface.getMenus(language, security_limit, **kwargs) - def getParamA(self, name, category, attribute="value", profile_key="@DEFAULT@", callback=None, errback=None): if callback is None: error_handler = None @@ -528,6 +500,43 @@ kwargs['error_handler'] = error_handler return self.db_core_iface.loadParamsTemplate(filename, **kwargs) + def menuHelpGet(self, menu_id, language, callback=None, errback=None): + if callback is None: + error_handler = None + else: + if errback is None: + errback = log.error + error_handler = lambda err:errback(dbus_to_bridge_exception(err)) + kwargs={} + if callback is not None: + kwargs['timeout'] = const_TIMEOUT + kwargs['reply_handler'] = callback + kwargs['error_handler'] = error_handler + return unicode(self.db_core_iface.menuHelpGet(menu_id, language, **kwargs)) + + def menuLaunch(self, menu_type, path, data, security_limit, profile_key, callback=None, errback=None): + if callback is None: + error_handler = None + else: + if errback is None: + errback = log.error + error_handler = lambda err:errback(dbus_to_bridge_exception(err)) + return self.db_core_iface.menuLaunch(menu_type, path, data, security_limit, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler) + + def menusGet(self, language, security_limit, callback=None, errback=None): + if callback is None: + error_handler = None + else: + if errback is None: + errback = log.error + error_handler = lambda err:errback(dbus_to_bridge_exception(err)) + kwargs={} + if callback is not None: + kwargs['timeout'] = const_TIMEOUT + kwargs['reply_handler'] = callback + kwargs['error_handler'] = error_handler + return self.db_core_iface.menusGet(language, security_limit, **kwargs) + def messageSend(self, to_jid, message, subject={}, mess_type="auto", extra={}, profile_key="@NONE@", callback=None, errback=None): if callback is None: error_handler = None diff -r ca82c97db195 -r 2f264f3df280 frontends/src/primitivus/primitivus --- a/frontends/src/primitivus/primitivus Thu Jan 26 20:24:58 2017 +0100 +++ b/frontends/src/primitivus/primitivus Thu Jan 26 20:29:48 2017 +0100 @@ -514,7 +514,7 @@ """ def add_menu_cb(callback_id): self.launchAction(callback_id, menu_data, profile=self.current_profile) - for id_, type_, path, path_i18n, extra in self.bridge.getMenus("", C.NO_SECURITY_LIMIT ): # TODO: manage extra + for id_, type_, path, path_i18n, extra in self.bridge.menusGet("", C.NO_SECURITY_LIMIT ): # TODO: manage extra if type_ != type_filter: continue if len(path) != 2: diff -r ca82c97db195 -r 2f264f3df280 frontends/src/quick_frontend/quick_app.py --- a/frontends/src/quick_frontend/quick_app.py Thu Jan 26 20:24:58 2017 +0100 +++ b/frontends/src/quick_frontend/quick_app.py Thu Jan 26 20:29:48 2017 +0100 @@ -857,8 +857,15 @@ if action_data: raise exceptions.DataError(u"Not all keys in action_data are managed ({keys})".format(keys=', '.join(action_data.keys()))) + + def _actionCb(self, data, callback, callback_id, profile): + if callback is None: + self.actionManager(data, profile=profile) + else: + callback(data=data, cb_id=callback_id, profile=profile) + def launchAction(self, callback_id, data=None, callback=None, profile=C.PROF_KEY_NONE): - """ Launch a dynamic action + """Launch a dynamic action @param callback_id: id of the action to launch @param data: data needed only for certain actions @@ -873,15 +880,28 @@ """ if data is None: data = dict() + action_cb = lambda data: self._actionCb(data, callback, callback_id, profile) + self.bridge.launchAction(callback_id, data, profile, callback=action_cb, errback=self.dialogFailure) + def launchMenu(self, menu_type, path, data=None, callback=None, security_limit=C.SECURITY_LIMIT_MAX, profile=C.PROF_KEY_NONE): + """Launch a menu manually - def action_cb(data): - if callback is None: - self.actionManager(data, profile=profile) - else: - callback(data=data, cb_id=callback_id, profile=profile) + @param menu_type(unicode): type of the menu to launch + @param path(iterable[unicode]): path to the menu + @param data: data needed only for certain actions + @param callback(callable, None): will be called with the resut + if None, self.actionManager will be called + else the callable will be called with the following kw parameters: + - data: action_data + - cb_id: (menu_type, path) tuple + - profile: %(doc_profile)s + @param profile: %(doc_profile)s - self.bridge.launchAction(callback_id, data, profile, callback=action_cb, errback=self.dialogFailure) + """ + if data is None: + data = dict() + action_cb = lambda data: self._actionCb(data, callback, (menu_type, path), profile) + self.bridge.menuLaunch(menu_type, path, data, security_limit, profile, callback=action_cb, errback=self.dialogFailure) def _avatarGetCb(self, avatar_path, entity, contact_list, profile): path = avatar_path or self.getDefaultAvatar(entity) diff -r ca82c97db195 -r 2f264f3df280 src/bridge/bridge_constructor/bridge_template.ini --- a/src/bridge/bridge_constructor/bridge_template.ini Thu Jan 26 20:24:58 2017 +0100 +++ b/src/bridge/bridge_constructor/bridge_template.ini Thu Jan 26 20:29:48 2017 +0100 @@ -640,7 +640,7 @@ progress_dict map progress_id to progress_data progress_data is the same dict as returned by [progressGet] -[getMenus] +[menusGet] type=method category=core sig_in=si @@ -656,7 +656,22 @@ - menu_path_i18n: translated path of the menu - extra: extra data, like icon name -[getMenuHelp] +[menuLaunch] +async= +type=method +category=core +sig_in=sasa{ss}is +sig_out=a{ss} +doc=Launch a registred menu +doc_param_0=menu_type: type of the menu (C.MENU_*) +doc_param_1=path: canonical (untranslated) path of the menu +doc_param_2=data: optional data +doc_param_3=%(doc_security_limit)s +doc_param_4=%(doc_profile_key)s +doc_return=dict where key can be: + - xmlui: a XMLUI need to be displayed + +[menuHelpGet] type=method category=core sig_in=ss diff -r ca82c97db195 -r 2f264f3df280 src/bridge/dbus_bridge.py --- a/src/bridge/dbus_bridge.py Thu Jan 26 20:24:58 2017 +0100 +++ b/src/bridge/dbus_bridge.py Thu Jan 26 20:29:48 2017 +0100 @@ -300,18 +300,6 @@ return self._callback("getMainResource", unicode(contact_jid), unicode(profile_key)) @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='ss', out_signature='s', - async_callbacks=None) - def getMenuHelp(self, menu_id, language): - return self._callback("getMenuHelp", unicode(menu_id), unicode(language)) - - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='si', out_signature='a(ssasasa{ss})', - async_callbacks=None) - def getMenus(self, language, security_limit): - return self._callback("getMenus", unicode(language), security_limit) - - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, in_signature='ssss', out_signature='s', async_callbacks=None) def getParamA(self, name, category, attribute="value", profile_key="@DEFAULT@"): @@ -390,6 +378,24 @@ return self._callback("loadParamsTemplate", unicode(filename)) @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, + in_signature='ss', out_signature='s', + async_callbacks=None) + def menuHelpGet(self, menu_id, language): + return self._callback("menuHelpGet", unicode(menu_id), unicode(language)) + + @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, + in_signature='sasa{ss}is', out_signature='a{ss}', + async_callbacks=('callback', 'errback')) + def menuLaunch(self, menu_type, path, data, security_limit, profile_key, callback=None, errback=None): + return self._callback("menuLaunch", unicode(menu_type), path, data, security_limit, unicode(profile_key), callback=callback, errback=errback) + + @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, + in_signature='si', out_signature='a(ssasasa{ss})', + async_callbacks=None) + def menusGet(self, language, security_limit): + return self._callback("menusGet", unicode(language), security_limit) + + @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, in_signature='sa{ss}a{ss}sa{ss}s', out_signature='', async_callbacks=('callback', 'errback')) def messageSend(self, to_jid, message, subject={}, mess_type="auto", extra={}, profile_key="@NONE@", callback=None, errback=None): diff -r ca82c97db195 -r 2f264f3df280 src/core/constants.py --- a/src/core/constants.py Thu Jan 26 20:24:58 2017 +0100 +++ b/src/core/constants.py Thu Jan 26 20:29:48 2017 +0100 @@ -49,7 +49,8 @@ ## Parameters ## - NO_SECURITY_LIMIT = -1 + NO_SECURITY_LIMIT = -1 # FIXME: to rename + SECURITY_LIMIT_MAX = 0 INDIVIDUAL = "individual" GENERAL = "general" # General parameters @@ -64,6 +65,7 @@ MEMORY_CRYPTO_NAMESPACE = 'crypto' # for the private persistent binary dict MEMORY_CRYPTO_KEY = 'personal_key' # Parameters for static blog pages + # FIXME: blog constants should not be in core constants STATIC_BLOG_KEY = "Blog page" STATIC_BLOG_PARAM_TITLE = "Title" STATIC_BLOG_PARAM_BANNER = "Banner" diff -r ca82c97db195 -r 2f264f3df280 src/core/sat_main.py --- a/src/core/sat_main.py Thu Jan 26 20:24:58 2017 +0100 +++ b/src/core/sat_main.py Thu Jan 26 20:29:48 2017 +0100 @@ -54,6 +54,7 @@ def __init__(self): self._cb_map = {} # map from callback_id to callbacks self._menus = OrderedDict() # dynamic menus. key: callback_id, value: menu data (dictionnary) + self._menus_paths = {} # path to id. key: (menu_type, lower case tuple of path), value: menu id self.initialised = defer.Deferred() self.profiles = {} self.plugins = {} @@ -112,8 +113,9 @@ self.bridge.register_method("actionsGet", self.actionsGet) self.bridge.register_method("progressGet", self._progressGet) self.bridge.register_method("progressGetAll", self._progressGetAll) - self.bridge.register_method("getMenus", self.getMenus) - self.bridge.register_method("getMenuHelp", self.getMenuHelp) + self.bridge.register_method("menusGet", self.getMenus) + self.bridge.register_method("menuHelpGet", self.getMenuHelp) + self.bridge.register_method("menuLaunch", self._launchMenu) self.bridge.register_method("discoInfos", self.memory.disco._discoInfos) self.bridge.register_method("discoItems", self.memory.disco._discoItems) self.bridge.register_method("saveParamsTemplate", self.memory.save_xml) @@ -917,10 +919,11 @@ def registerCallback(self, callback, *args, **kwargs): """Register a callback. - Use with_data=True in kwargs if the callback use the optional data dict - use force_id=id to avoid generated id. Can lead to name conflict, avoid if possible - use one_shot=True to delete callback once it have been called - @param callback: any callable + @param callback(callable): method to call + @param kwargs: can contain: + with_data(bool): True if the callback use the optional data dict + force_id(unicode): id to avoid generated id. Can lead to name conflict, avoid if possible + one_shot(bool): True to delete callback once it have been called @return: id of the registered callback """ callback_id = kwargs.pop('force_id', None) @@ -949,6 +952,7 @@ def launchCallback(self, callback_id, data=None, profile_key=C.PROF_KEY_NONE): """Launch a specific callback + @param callback_id: id of the action (callback) to launch @param data: optional data @profile_key: %(doc_profile_key)s @@ -958,13 +962,14 @@ - C.BOOL_TRUE - C.BOOL_FALSE """ + # FIXME: security limit need to be checked here try: client = self.getClient(profile_key) except exceptions.NotFound: # client is not available yet profile = self.memory.getProfileName(profile_key) if not profile: - raise exceptions.ProfileUnknownError(_('trying to launch action with a non-existant profile')) + raise exceptions.ProfileUnknownError(_(u'trying to launch action with a non-existant profile')) else: profile = client.profile # we check if the action is kept, and remove it @@ -996,17 +1001,27 @@ #Menus management + def _getMenuCanonicalPath(self, path): + """give canonical form of path + + canonical form is a tuple of the path were every element is stripped and lowercase + @param path(iterable[unicode]): untranslated path to menu + @return (tuple[unicode]): canonical form of path + """ + return tuple((p.lower().strip() for p in path)) + def importMenu(self, path, callback, security_limit=C.NO_SECURITY_LIMIT, help_string="", type_=C.MENU_GLOBAL): """register a new menu for frontends - @param path: path to go to the menu (category/subcategory/.../item), must be an iterable (e.g.: ("File", "Open")) + @param path(iterable[unicode]): path to go to the menu (category/subcategory/.../item) (e.g.: ("File", "Open")) /!\ use D_() instead of _() for translations (e.g. (D_("File"), D_("Open"))) - @param callback: method to be called when menuitem is selected, callable or a callback id (string) as returned by [registerCallback] - @param security_limit: %(doc_security_limit)s + untranslated/lower case path can be used to identity a menu, for this reason it must be unique independently of case. + @param callback(callable): method to be called when menuitem is selected, callable or a callback id (string) as returned by [registerCallback] + @param security_limit(int): %(doc_security_limit)s /!\ security_limit MUST be added to data in launchCallback if used #TODO - @param help_string: string used to indicate what the menu do (can be show as a tooltip). + @param help_string(unicode): string used to indicate what the menu do (can be show as a tooltip). /!\ use D_() instead of _() for translations - @param type: one of: + @param type(unicode): one of: - C.MENU_GLOBAL: classical menu, can be shown in a menubar on top (e.g. something like File/Open) - C.MENU_ROOM: like a global menu, but only shown in multi-user chat menu_data must contain a "room_jid" data @@ -1018,7 +1033,7 @@ menu_data must contain a "room_jid" data - C.MENU_ROSTER_GROUP_CONTEXT: contextual menu, used with group (e.g.: publish microblog, group is already filled) menu_data must contain a "group" data - @return: menu_id (same as callback_id) + @return (unicode): menu_id (same as callback_id) """ if callable(callback): @@ -1038,13 +1053,22 @@ if menu_data['path'] == path and menu_data['type'] == type_: raise exceptions.ConflictError(_("A menu with the same path and type already exists")) - menu_data = {'path': path, + path_canonical = self._getMenuCanonicalPath(path) + menu_key = (type_, path_canonical) + + if menu_key in self._menus_paths: + raise exceptions.ConflictError(u"this menu path is already used: {path} ({menu_key})".format( + path=path_canonical, menu_key=menu_key)) + + menu_data = {'path': tuple(path), + 'path_canonical': path_canonical, 'security_limit': security_limit, 'help_string': help_string, 'type': type_ } self._menus[callback_id] = menu_data + self._menus_paths[menu_key] = callback_id return callback_id @@ -1077,6 +1101,29 @@ return ret + def _launchMenu(self, menu_type, path, data=None, security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE): + client = self.getClient(profile_key) + return self.launchMenu(client, menu_type, path, data, security_limit) + + def launchMenu(self, client, menu_type, path, data=None, security_limit=C.NO_SECURITY_LIMIT): + """launch action a menu action + + @param menu_type(unicode): type of menu to launch + @param path(iterable[unicode]): canonical path of the menu + @params data(dict): menu data + @raise NotFound: this path is not known + """ + # FIXME: manage security_limit here + # defaut security limit should be high instead of C.NO_SECURITY_LIMIT + canonical_path = self._getMenuCanonicalPath(path) + menu_key = (menu_type, canonical_path) + try: + callback_id = self._menus_paths[menu_key] + except KeyError: + raise exceptions.NotFound(u"Can't find menu {path} ({menu_type})".format( + path=canonical_path, menu_type=menu_type)) + return self.launchCallback(callback_id, data, client.profile) + def getMenuHelp(self, menu_id, language=''): """return the help string of the menu