Mercurial > libervia-backend
comparison src/core/sat_main.py @ 2126:2f264f3df280
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
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 26 Jan 2017 20:29:48 +0100 |
parents | 9c861d07b5b6 |
children | aa94f33fd2ad |
comparison
equal
deleted
inserted
replaced
2125:ca82c97db195 | 2126:2f264f3df280 |
---|---|
52 class SAT(service.Service): | 52 class SAT(service.Service): |
53 | 53 |
54 def __init__(self): | 54 def __init__(self): |
55 self._cb_map = {} # map from callback_id to callbacks | 55 self._cb_map = {} # map from callback_id to callbacks |
56 self._menus = OrderedDict() # dynamic menus. key: callback_id, value: menu data (dictionnary) | 56 self._menus = OrderedDict() # dynamic menus. key: callback_id, value: menu data (dictionnary) |
57 self._menus_paths = {} # path to id. key: (menu_type, lower case tuple of path), value: menu id | |
57 self.initialised = defer.Deferred() | 58 self.initialised = defer.Deferred() |
58 self.profiles = {} | 59 self.profiles = {} |
59 self.plugins = {} | 60 self.plugins = {} |
60 | 61 |
61 self.memory = Memory(self) | 62 self.memory = Memory(self) |
110 self.bridge.register_method("isConnected", self.isConnected) | 111 self.bridge.register_method("isConnected", self.isConnected) |
111 self.bridge.register_method("launchAction", self.launchCallback) | 112 self.bridge.register_method("launchAction", self.launchCallback) |
112 self.bridge.register_method("actionsGet", self.actionsGet) | 113 self.bridge.register_method("actionsGet", self.actionsGet) |
113 self.bridge.register_method("progressGet", self._progressGet) | 114 self.bridge.register_method("progressGet", self._progressGet) |
114 self.bridge.register_method("progressGetAll", self._progressGetAll) | 115 self.bridge.register_method("progressGetAll", self._progressGetAll) |
115 self.bridge.register_method("getMenus", self.getMenus) | 116 self.bridge.register_method("menusGet", self.getMenus) |
116 self.bridge.register_method("getMenuHelp", self.getMenuHelp) | 117 self.bridge.register_method("menuHelpGet", self.getMenuHelp) |
118 self.bridge.register_method("menuLaunch", self._launchMenu) | |
117 self.bridge.register_method("discoInfos", self.memory.disco._discoInfos) | 119 self.bridge.register_method("discoInfos", self.memory.disco._discoInfos) |
118 self.bridge.register_method("discoItems", self.memory.disco._discoItems) | 120 self.bridge.register_method("discoItems", self.memory.disco._discoItems) |
119 self.bridge.register_method("saveParamsTemplate", self.memory.save_xml) | 121 self.bridge.register_method("saveParamsTemplate", self.memory.save_xml) |
120 self.bridge.register_method("loadParamsTemplate", self.memory.load_xml) | 122 self.bridge.register_method("loadParamsTemplate", self.memory.load_xml) |
121 self.bridge.register_method("sessionInfosGet", self.getSessionInfos) | 123 self.bridge.register_method("sessionInfosGet", self.getSessionInfos) |
915 return progress_all | 917 return progress_all |
916 | 918 |
917 def registerCallback(self, callback, *args, **kwargs): | 919 def registerCallback(self, callback, *args, **kwargs): |
918 """Register a callback. | 920 """Register a callback. |
919 | 921 |
920 Use with_data=True in kwargs if the callback use the optional data dict | 922 @param callback(callable): method to call |
921 use force_id=id to avoid generated id. Can lead to name conflict, avoid if possible | 923 @param kwargs: can contain: |
922 use one_shot=True to delete callback once it have been called | 924 with_data(bool): True if the callback use the optional data dict |
923 @param callback: any callable | 925 force_id(unicode): id to avoid generated id. Can lead to name conflict, avoid if possible |
926 one_shot(bool): True to delete callback once it have been called | |
924 @return: id of the registered callback | 927 @return: id of the registered callback |
925 """ | 928 """ |
926 callback_id = kwargs.pop('force_id', None) | 929 callback_id = kwargs.pop('force_id', None) |
927 if callback_id is None: | 930 if callback_id is None: |
928 callback_id = str(uuid4()) | 931 callback_id = str(uuid4()) |
947 log.debug("Removing callback [%s]" % callback_id) | 950 log.debug("Removing callback [%s]" % callback_id) |
948 del self._cb_map[callback_id] | 951 del self._cb_map[callback_id] |
949 | 952 |
950 def launchCallback(self, callback_id, data=None, profile_key=C.PROF_KEY_NONE): | 953 def launchCallback(self, callback_id, data=None, profile_key=C.PROF_KEY_NONE): |
951 """Launch a specific callback | 954 """Launch a specific callback |
955 | |
952 @param callback_id: id of the action (callback) to launch | 956 @param callback_id: id of the action (callback) to launch |
953 @param data: optional data | 957 @param data: optional data |
954 @profile_key: %(doc_profile_key)s | 958 @profile_key: %(doc_profile_key)s |
955 @return: a deferred which fire a dict where key can be: | 959 @return: a deferred which fire a dict where key can be: |
956 - xmlui: a XMLUI need to be displayed | 960 - xmlui: a XMLUI need to be displayed |
957 - validated: if present, can be used to launch a callback, it can have the values | 961 - validated: if present, can be used to launch a callback, it can have the values |
958 - C.BOOL_TRUE | 962 - C.BOOL_TRUE |
959 - C.BOOL_FALSE | 963 - C.BOOL_FALSE |
960 """ | 964 """ |
965 #Â FIXME: security limit need to be checked here | |
961 try: | 966 try: |
962 client = self.getClient(profile_key) | 967 client = self.getClient(profile_key) |
963 except exceptions.NotFound: | 968 except exceptions.NotFound: |
964 # client is not available yet | 969 # client is not available yet |
965 profile = self.memory.getProfileName(profile_key) | 970 profile = self.memory.getProfileName(profile_key) |
966 if not profile: | 971 if not profile: |
967 raise exceptions.ProfileUnknownError(_('trying to launch action with a non-existant profile')) | 972 raise exceptions.ProfileUnknownError(_(u'trying to launch action with a non-existant profile')) |
968 else: | 973 else: |
969 profile = client.profile | 974 profile = client.profile |
970 # we check if the action is kept, and remove it | 975 # we check if the action is kept, and remove it |
971 try: | 976 try: |
972 action_tuple = client.actions[callback_id] | 977 action_tuple = client.actions[callback_id] |
994 | 999 |
995 return defer.maybeDeferred(callback, *args, **kwargs) | 1000 return defer.maybeDeferred(callback, *args, **kwargs) |
996 | 1001 |
997 #Menus management | 1002 #Menus management |
998 | 1003 |
1004 def _getMenuCanonicalPath(self, path): | |
1005 """give canonical form of path | |
1006 | |
1007 canonical form is a tuple of the path were every element is stripped and lowercase | |
1008 @param path(iterable[unicode]): untranslated path to menu | |
1009 @return (tuple[unicode]): canonical form of path | |
1010 """ | |
1011 return tuple((p.lower().strip() for p in path)) | |
1012 | |
999 def importMenu(self, path, callback, security_limit=C.NO_SECURITY_LIMIT, help_string="", type_=C.MENU_GLOBAL): | 1013 def importMenu(self, path, callback, security_limit=C.NO_SECURITY_LIMIT, help_string="", type_=C.MENU_GLOBAL): |
1000 """register a new menu for frontends | 1014 """register a new menu for frontends |
1001 | 1015 |
1002 @param path: path to go to the menu (category/subcategory/.../item), must be an iterable (e.g.: ("File", "Open")) | 1016 @param path(iterable[unicode]): path to go to the menu (category/subcategory/.../item) (e.g.: ("File", "Open")) |
1003 /!\ use D_() instead of _() for translations (e.g. (D_("File"), D_("Open"))) | 1017 /!\ use D_() instead of _() for translations (e.g. (D_("File"), D_("Open"))) |
1004 @param callback: method to be called when menuitem is selected, callable or a callback id (string) as returned by [registerCallback] | 1018 untranslated/lower case path can be used to identity a menu, for this reason it must be unique independently of case. |
1005 @param security_limit: %(doc_security_limit)s | 1019 @param callback(callable): method to be called when menuitem is selected, callable or a callback id (string) as returned by [registerCallback] |
1020 @param security_limit(int): %(doc_security_limit)s | |
1006 /!\ security_limit MUST be added to data in launchCallback if used #TODO | 1021 /!\ security_limit MUST be added to data in launchCallback if used #TODO |
1007 @param help_string: string used to indicate what the menu do (can be show as a tooltip). | 1022 @param help_string(unicode): string used to indicate what the menu do (can be show as a tooltip). |
1008 /!\ use D_() instead of _() for translations | 1023 /!\ use D_() instead of _() for translations |
1009 @param type: one of: | 1024 @param type(unicode): one of: |
1010 - C.MENU_GLOBAL: classical menu, can be shown in a menubar on top (e.g. something like File/Open) | 1025 - C.MENU_GLOBAL: classical menu, can be shown in a menubar on top (e.g. something like File/Open) |
1011 - C.MENU_ROOM: like a global menu, but only shown in multi-user chat | 1026 - C.MENU_ROOM: like a global menu, but only shown in multi-user chat |
1012 menu_data must contain a "room_jid" data | 1027 menu_data must contain a "room_jid" data |
1013 - C.MENU_SINGLE: like a global menu, but only shown in one2one chat | 1028 - C.MENU_SINGLE: like a global menu, but only shown in one2one chat |
1014 menu_data must contain a "jid" data | 1029 menu_data must contain a "jid" data |
1016 menu_data must contain a "jid" data | 1031 menu_data must contain a "jid" data |
1017 - C.MENU_ROSTER_JID_CONTEXT: like JID_CONTEXT, but restricted to jids in roster. | 1032 - C.MENU_ROSTER_JID_CONTEXT: like JID_CONTEXT, but restricted to jids in roster. |
1018 menu_data must contain a "room_jid" data | 1033 menu_data must contain a "room_jid" data |
1019 - C.MENU_ROSTER_GROUP_CONTEXT: contextual menu, used with group (e.g.: publish microblog, group is already filled) | 1034 - C.MENU_ROSTER_GROUP_CONTEXT: contextual menu, used with group (e.g.: publish microblog, group is already filled) |
1020 menu_data must contain a "group" data | 1035 menu_data must contain a "group" data |
1021 @return: menu_id (same as callback_id) | 1036 @return (unicode): menu_id (same as callback_id) |
1022 """ | 1037 """ |
1023 | 1038 |
1024 if callable(callback): | 1039 if callable(callback): |
1025 callback_id = self.registerCallback(callback, with_data=True) | 1040 callback_id = self.registerCallback(callback, with_data=True) |
1026 elif isinstance(callback, basestring): | 1041 elif isinstance(callback, basestring): |
1036 | 1051 |
1037 for menu_data in self._menus.itervalues(): | 1052 for menu_data in self._menus.itervalues(): |
1038 if menu_data['path'] == path and menu_data['type'] == type_: | 1053 if menu_data['path'] == path and menu_data['type'] == type_: |
1039 raise exceptions.ConflictError(_("A menu with the same path and type already exists")) | 1054 raise exceptions.ConflictError(_("A menu with the same path and type already exists")) |
1040 | 1055 |
1041 menu_data = {'path': path, | 1056 path_canonical = self._getMenuCanonicalPath(path) |
1057 menu_key = (type_, path_canonical) | |
1058 | |
1059 if menu_key in self._menus_paths: | |
1060 raise exceptions.ConflictError(u"this menu path is already used: {path} ({menu_key})".format( | |
1061 path=path_canonical, menu_key=menu_key)) | |
1062 | |
1063 menu_data = {'path': tuple(path), | |
1064 'path_canonical': path_canonical, | |
1042 'security_limit': security_limit, | 1065 'security_limit': security_limit, |
1043 'help_string': help_string, | 1066 'help_string': help_string, |
1044 'type': type_ | 1067 'type': type_ |
1045 } | 1068 } |
1046 | 1069 |
1047 self._menus[callback_id] = menu_data | 1070 self._menus[callback_id] = menu_data |
1071 self._menus_paths[menu_key] = callback_id | |
1048 | 1072 |
1049 return callback_id | 1073 return callback_id |
1050 | 1074 |
1051 def getMenus(self, language='', security_limit=C.NO_SECURITY_LIMIT): | 1075 def getMenus(self, language='', security_limit=C.NO_SECURITY_LIMIT): |
1052 """Return all menus registered | 1076 """Return all menus registered |
1075 extra = {} # TODO: manage extra data like icon | 1099 extra = {} # TODO: manage extra data like icon |
1076 ret.append((menu_id, type_, path, path_i18n, extra)) | 1100 ret.append((menu_id, type_, path, path_i18n, extra)) |
1077 | 1101 |
1078 return ret | 1102 return ret |
1079 | 1103 |
1104 def _launchMenu(self, menu_type, path, data=None, security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE): | |
1105 client = self.getClient(profile_key) | |
1106 return self.launchMenu(client, menu_type, path, data, security_limit) | |
1107 | |
1108 def launchMenu(self, client, menu_type, path, data=None, security_limit=C.NO_SECURITY_LIMIT): | |
1109 """launch action a menu action | |
1110 | |
1111 @param menu_type(unicode): type of menu to launch | |
1112 @param path(iterable[unicode]): canonical path of the menu | |
1113 @params data(dict): menu data | |
1114 @raise NotFound: this path is not known | |
1115 """ | |
1116 # FIXME: manage security_limit here | |
1117 # defaut security limit should be high instead of C.NO_SECURITY_LIMIT | |
1118 canonical_path = self._getMenuCanonicalPath(path) | |
1119 menu_key = (menu_type, canonical_path) | |
1120 try: | |
1121 callback_id = self._menus_paths[menu_key] | |
1122 except KeyError: | |
1123 raise exceptions.NotFound(u"Can't find menu {path} ({menu_type})".format( | |
1124 path=canonical_path, menu_type=menu_type)) | |
1125 return self.launchCallback(callback_id, data, client.profile) | |
1126 | |
1080 def getMenuHelp(self, menu_id, language=''): | 1127 def getMenuHelp(self, menu_id, language=''): |
1081 """return the help string of the menu | 1128 """return the help string of the menu |
1082 | 1129 |
1083 @param menu_id: id of the menu (same as callback_id) | 1130 @param menu_id: id of the menu (same as callback_id) |
1084 @param language: language used for translation, or empty string for default | 1131 @param language: language used for translation, or empty string for default |