comparison sat/core/sat_main.py @ 2643:189e38fb11ff

core: style improvments (90 chars limit)
author Goffi <goffi@goffi.org>
date Sun, 29 Jul 2018 18:44:27 +0200
parents 56f94936df1e
children f2cf1daa42cb
comparison
equal deleted inserted replaced
2642:755a0b8643bd 2643:189e38fb11ff
53 def __init__(self): 53 def __init__(self):
54 self._cb_map = {} # map from callback_id to callbacks 54 self._cb_map = {} # map from callback_id to callbacks
55 self._menus = ( 55 self._menus = (
56 OrderedDict() 56 OrderedDict()
57 ) # dynamic menus. key: callback_id, value: menu data (dictionnary) 57 ) # dynamic menus. key: callback_id, value: menu data (dictionnary)
58 self._menus_paths = {} # path to id. key: (menu_type, lower case tuple of path), value: menu id 58 self._menus_paths = {} # path to id. key: (menu_type, lower case tuple of path),
59 # value: menu id
59 self.initialised = defer.Deferred() 60 self.initialised = defer.Deferred()
60 self.profiles = {} 61 self.profiles = {}
61 self.plugins = {} 62 self.plugins = {}
62 self.ns_map = { 63 self.ns_map = {
63 u"x-data": u"jabber:x:data" 64 u"x-data": u"jabber:x:data"
151 """Return the short version of SàT""" 152 """Return the short version of SàT"""
152 return C.APP_VERSION 153 return C.APP_VERSION
153 154
154 @property 155 @property
155 def full_version(self): 156 def full_version(self):
156 """Return the full version of SàT (with release name and extra data when in development mode)""" 157 """Return the full version of SàT
158
159 In developement mode, release name and extra data are returned too
160 """
157 version = self.version 161 version = self.version
158 if version[-1] == "D": 162 if version[-1] == "D":
159 # we are in debug version, we add extra data 163 # we are in debug version, we add extra data
160 try: 164 try:
161 return self._version_cache 165 return self._version_cache
199 def _import_plugins(self): 203 def _import_plugins(self):
200 """Import all plugins found in plugins directory""" 204 """Import all plugins found in plugins directory"""
201 # FIXME: module imported but cancelled should be deleted 205 # FIXME: module imported but cancelled should be deleted
202 # TODO: make this more generic and reusable in tools.common 206 # TODO: make this more generic and reusable in tools.common
203 # FIXME: should use imp 207 # FIXME: should use imp
204 # TODO: do not import all plugins if no needed: component plugins are not needed if we 208 # TODO: do not import all plugins if no needed: component plugins are not needed
205 # just use a client, and plugin blacklisting should be possible in sat.conf 209 # if we just use a client, and plugin blacklisting should be possible in
210 # sat.conf
206 plugins_path = os.path.dirname(sat.plugins.__file__) 211 plugins_path = os.path.dirname(sat.plugins.__file__)
207 plugin_glob = "plugin*." + C.PLUGIN_EXT 212 plugin_glob = "plugin*." + C.PLUGIN_EXT
208 plug_lst = [ 213 plug_lst = [
209 os.path.splitext(plugin)[0] 214 os.path.splitext(plugin)[0]
210 for plugin in map( 215 for plugin in map(
217 try: 222 try:
218 __import__(plugin_path) 223 __import__(plugin_path)
219 except exceptions.MissingModule as e: 224 except exceptions.MissingModule as e:
220 self._unimport_plugin(plugin_path) 225 self._unimport_plugin(plugin_path)
221 log.warning( 226 log.warning(
222 u"Can't import plugin [{path}] because of an unavailale third party module:\n{msg}".format( 227 u"Can't import plugin [{path}] because of an unavailale third party "
228 u"module:\n{msg}".format(
223 path=plugin_path, msg=e 229 path=plugin_path, msg=e
224 ) 230 )
225 ) 231 )
226 continue 232 continue
227 except exceptions.CancelError as e: 233 except exceptions.CancelError as e:
263 continue 269 continue
264 270
265 if import_name in plugins_to_import: 271 if import_name in plugins_to_import:
266 log.error( 272 log.error(
267 _( 273 _(
268 u"Name conflict for import name [{import_name}], can't import plugin [{name}]" 274 u"Name conflict for import name [{import_name}], can't import "
275 u"plugin [{name}]"
269 ).format(**plugin_info) 276 ).format(**plugin_info)
270 ) 277 )
271 continue 278 continue
272 plugins_to_import[import_name] = (plugin_path, mod, plugin_info) 279 plugins_to_import[import_name] = (plugin_path, mod, plugin_info)
273 while True: 280 while True:
281 def _import_plugins_from_dict( 288 def _import_plugins_from_dict(
282 self, plugins_to_import, import_name=None, optional=False 289 self, plugins_to_import, import_name=None, optional=False
283 ): 290 ):
284 """Recursively import and their dependencies in the right order 291 """Recursively import and their dependencies in the right order
285 292
286 @param plugins_to_import(dict): key=import_name and values=(plugin_path, module, plugin_info) 293 @param plugins_to_import(dict): key=import_name and values=(plugin_path, module,
287 @param import_name(unicode, None): name of the plugin to import as found in PLUGIN_INFO['import_name'] 294 plugin_info)
288 @param optional(bool): if False and plugin is not found, an ImportError exception is raised 295 @param import_name(unicode, None): name of the plugin to import as found in
296 PLUGIN_INFO['import_name']
297 @param optional(bool): if False and plugin is not found, an ImportError exception
298 is raised
289 """ 299 """
290 if import_name in self.plugins: 300 if import_name in self.plugins:
291 log.debug(u"Plugin {} already imported, passing".format(import_name)) 301 log.debug(u"Plugin {} already imported, passing".format(import_name))
292 return 302 return
293 if not import_name: 303 if not import_name:
415 def getFeatures(self, profile_key=C.PROF_KEY_NONE): 425 def getFeatures(self, profile_key=C.PROF_KEY_NONE):
416 """Get available features 426 """Get available features
417 427
418 Return list of activated plugins and plugin specific data 428 Return list of activated plugins and plugin specific data
419 @param profile_key: %(doc_profile_key)s 429 @param profile_key: %(doc_profile_key)s
420 C.PROF_KEY_NONE can be used to have general plugins data (i.e. not profile dependent) 430 C.PROF_KEY_NONE can be used to have general plugins data (i.e. not profile
431 dependent)
421 @return (dict)[Deferred]: features data where: 432 @return (dict)[Deferred]: features data where:
422 - key is plugin import name, present only for activated plugins 433 - key is plugin import name, present only for activated plugins
423 - value is a an other dict, when meaning is specific to each plugin. 434 - value is a an other dict, when meaning is specific to each plugin.
424 this dict is return by plugin's getFeature method. 435 this dict is return by plugin's getFeature method.
425 If this method doesn't exists, an empty dict is returned. 436 If this method doesn't exists, an empty dict is returned.
532 return self.profiles[profile] 543 return self.profiles[profile]
533 except KeyError: 544 except KeyError:
534 raise exceptions.NotFound(profile_key) 545 raise exceptions.NotFound(profile_key)
535 546
536 def getClients(self, profile_key): 547 def getClients(self, profile_key):
537 """Convenient method to get list of clients from profile key (manage list through profile_key like C.PROF_KEY_ALL) 548 """Convenient method to get list of clients from profile key
538 549
550 Manage list through profile_key like C.PROF_KEY_ALL
539 @param profile_key: %(doc_profile_key)s 551 @param profile_key: %(doc_profile_key)s
540 @return: list of clients 552 @return: list of clients
541 """ 553 """
542 if not profile_key: 554 if not profile_key:
543 raise exceptions.DataError(_(u"profile_key must not be empty")) 555 raise exceptions.DataError(_(u"profile_key must not be empty"))
597 @param component(bool): if True, path will be prefixed with C.COMPONENTS_DIR 609 @param component(bool): if True, path will be prefixed with C.COMPONENTS_DIR
598 @param profile(bool): if True, path will be suffixed by profile name 610 @param profile(bool): if True, path will be suffixed by profile name
599 @param *extra_path: extra path element(s) to use 611 @param *extra_path: extra path element(s) to use
600 @return (unicode): path 612 @return (unicode): path
601 """ 613 """
602 # FIXME: component and profile are parsed with **kwargs because of python 2 limitations 614 # FIXME: component and profile are parsed with **kwargs because of python 2
603 # once moved to python 3, this can be fixed 615 # limitations. Once moved to python 3, this can be fixed
604 component = kwargs.pop("component", False) 616 component = kwargs.pop("component", False)
605 profile = kwargs.pop("profile", True) 617 profile = kwargs.pop("profile", True)
606 assert not kwargs 618 assert not kwargs
607 619
608 path_elts = [self.memory.getConfig("", "local_dir")] 620 path_elts = [self.memory.getConfig("", "local_dir")]
637 return False 649 return False
638 return self.profiles[profile].isConnected() 650 return self.profiles[profile].isConnected()
639 651
640 ## XMPP methods ## 652 ## XMPP methods ##
641 653
642 def _messageSend( 654 def _messageSend(self, to_jid_s, message, subject=None, mess_type="auto", extra=None,
643 self, 655 profile_key=C.PROF_KEY_NONE,):
644 to_jid_s,
645 message,
646 subject=None,
647 mess_type="auto",
648 extra=None,
649 profile_key=C.PROF_KEY_NONE,
650 ):
651 client = self.getClient(profile_key) 656 client = self.getClient(profile_key)
652 to_jid = jid.JID(to_jid_s) 657 to_jid = jid.JID(to_jid_s)
653 # XXX: we need to use the dictionary comprehension because D-Bus return its own types, and pickle can't manage them. TODO: Need to find a better way 658 # XXX: we need to use the dictionary comprehension because D-Bus return its own
659 # types, and pickle can't manage them. TODO: Need to find a better way
654 return client.sendMessage( 660 return client.sendMessage(
655 to_jid, 661 to_jid,
656 message, 662 message,
657 subject, 663 subject,
658 mess_type, 664 mess_type,
672 assert profile 678 assert profile
673 priority = int( 679 priority = int(
674 self.memory.getParamA("Priority", "Connection", profile_key=profile) 680 self.memory.getParamA("Priority", "Connection", profile_key=profile)
675 ) 681 )
676 self.profiles[profile].presence.available(to_jid, show, statuses, priority) 682 self.profiles[profile].presence.available(to_jid, show, statuses, priority)
677 # XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not broadcasted to generating resource) 683 # XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not
684 # broadcasted to generating resource)
678 if "" in statuses: 685 if "" in statuses:
679 statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop("") 686 statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop("")
680 self.bridge.presenceUpdate( 687 self.bridge.presenceUpdate(
681 self.profiles[profile].jid.full(), show, int(priority), statuses, profile 688 self.profiles[profile].jid.full(), show, int(priority), statuses, profile
682 ) 689 )
707 714
708 def addContact(self, to_jid, profile_key): 715 def addContact(self, to_jid, profile_key):
709 """Add a contact in roster list""" 716 """Add a contact in roster list"""
710 profile = self.memory.getProfileName(profile_key) 717 profile = self.memory.getProfileName(profile_key)
711 assert profile 718 assert profile
712 # presence is sufficient, as a roster push will be sent according to RFC 6121 §3.1.2 719 # presence is sufficient, as a roster push will be sent according to
720 # RFC 6121 §3.1.2
713 self.profiles[profile].presence.subscribe(to_jid) 721 self.profiles[profile].presence.subscribe(to_jid)
714 722
715 def _updateContact(self, to_jid_s, name, groups, profile_key): 723 def _updateContact(self, to_jid_s, name, groups, profile_key):
716 return self.updateContact(jid.JID(to_jid_s), name, groups, profile_key) 724 return self.updateContact(jid.JID(to_jid_s), name, groups, profile_key)
717 725
799 local_device=False, 807 local_device=False,
800 ): 808 ):
801 """retrieve all services or contacts managing a set a features 809 """retrieve all services or contacts managing a set a features
802 810
803 @param namespaces(list[unicode]): features which must be handled 811 @param namespaces(list[unicode]): features which must be handled
804 @param identities(list[tuple[unicode,unicode]], None): if not None or empty, only keep those identities 812 @param identities(list[tuple[unicode,unicode]], None): if not None or empty,
805 tuple must by (category, type) 813 only keep those identities tuple must by (category, type)
806 @param bare_jids(bool): retrieve only bare_jids if True 814 @param bare_jids(bool): retrieve only bare_jids if True
807 if False, retrieve full jid of connected devices 815 if False, retrieve full jid of connected devices
808 @param service(bool): if True return service from our roster 816 @param service(bool): if True return service from our roster
809 @param roster(bool): if True, return entities in roster 817 @param roster(bool): if True, return entities in roster
810 full jid of all matching resources available will be returned 818 full jid of all matching resources available will be returned
811 @param own_jid(bool): if True, return profile's jid resources 819 @param own_jid(bool): if True, return profile's jid resources
812 @param local_device(bool): if True, return profile's jid local resource (i.e. client.jid) 820 @param local_device(bool): if True, return profile's jid local resource
813 @return (tuple(dict[jid.JID(), tuple[unicode, unicode, unicode]]*3)): found entities in a tuple with: 821 (i.e. client.jid)
822 @return (tuple(dict[jid.JID(), tuple[unicode, unicode, unicode]]*3)): found
823 entities in a tuple with:
814 - service entities 824 - service entities
815 - own entities 825 - own entities
816 - roster entities 826 - roster entities
817 """ 827 """
818 if not identities: 828 if not identities:
894 ): 904 ):
895 """Shortcut to bridge.actionNew which generate and id and keep for retrieval 905 """Shortcut to bridge.actionNew which generate and id and keep for retrieval
896 906
897 @param action_data(dict): action data (see bridge documentation) 907 @param action_data(dict): action data (see bridge documentation)
898 @param security_limit: %(doc_security_limit)s 908 @param security_limit: %(doc_security_limit)s
899 @param keep_id(None, unicode): if not None, used to keep action for differed retrieval 909 @param keep_id(None, unicode): if not None, used to keep action for differed
900 must be set to the callback_id 910 retrieval. Must be set to the callback_id.
901 action will be deleted after 30 min. 911 Action will be deleted after 30 min.
902 @param profile: %(doc_profile)s 912 @param profile: %(doc_profile)s
903 """ 913 """
904 id_ = unicode(uuid.uuid4()) 914 id_ = unicode(uuid.uuid4())
905 if keep_id is not None: 915 if keep_id is not None:
906 client = self.getClient(profile) 916 client = self.getClient(profile)
946 @param progress_id(unicode): unique id of the progressing element 956 @param progress_id(unicode): unique id of the progressing element
947 @param profile: %(doc_profile)s 957 @param profile: %(doc_profile)s
948 @return (dict): data with the following keys: 958 @return (dict): data with the following keys:
949 'position' (int): current possition 959 'position' (int): current possition
950 'size' (int): end_position 960 'size' (int): end_position
951 if id doesn't exists (may be a finished progression), and empty dict is returned 961 if id doesn't exists (may be a finished progression), and empty dict is
962 returned
952 """ 963 """
953 client = self.getClient(profile) 964 client = self.getClient(profile)
954 try: 965 try:
955 data = client._progress_cb[progress_id][0](progress_id, profile) 966 data = client._progress_cb[progress_id][0](progress_id, profile)
956 except KeyError: 967 except KeyError:
967 978
968 def progressGetAllMetadata(self, profile_key): 979 def progressGetAllMetadata(self, profile_key):
969 """Return all progress metadata at once 980 """Return all progress metadata at once
970 981
971 @param profile_key: %(doc_profile)s 982 @param profile_key: %(doc_profile)s
972 if C.PROF_KEY_ALL is used, all progress metadata from all profiles are returned 983 if C.PROF_KEY_ALL is used, all progress metadata from all profiles are
984 returned
973 @return (dict[dict[dict]]): a dict which map profile to progress_dict 985 @return (dict[dict[dict]]): a dict which map profile to progress_dict
974 progress_dict map progress_id to progress_data 986 progress_dict map progress_id to progress_data
975 progress_metadata is the same dict as sent by [progressStarted] 987 progress_metadata is the same dict as sent by [progressStarted]
976 """ 988 """
977 clients = self.getClients(profile_key) 989 clients = self.getClients(profile_key)
1010 """Register a callback. 1022 """Register a callback.
1011 1023
1012 @param callback(callable): method to call 1024 @param callback(callable): method to call
1013 @param kwargs: can contain: 1025 @param kwargs: can contain:
1014 with_data(bool): True if the callback use the optional data dict 1026 with_data(bool): True if the callback use the optional data dict
1015 force_id(unicode): id to avoid generated id. Can lead to name conflict, avoid if possible 1027 force_id(unicode): id to avoid generated id. Can lead to name conflict, avoid
1028 if possible
1016 one_shot(bool): True to delete callback once it have been called 1029 one_shot(bool): True to delete callback once it have been called
1017 @return: id of the registered callback 1030 @return: id of the registered callback
1018 """ 1031 """
1019 callback_id = kwargs.pop("force_id", None) 1032 callback_id = kwargs.pop("force_id", None)
1020 if callback_id is None: 1033 if callback_id is None:
1048 @param callback_id: id of the action (callback) to launch 1061 @param callback_id: id of the action (callback) to launch
1049 @param data: optional data 1062 @param data: optional data
1050 @profile_key: %(doc_profile_key)s 1063 @profile_key: %(doc_profile_key)s
1051 @return: a deferred which fire a dict where key can be: 1064 @return: a deferred which fire a dict where key can be:
1052 - xmlui: a XMLUI need to be displayed 1065 - xmlui: a XMLUI need to be displayed
1053 - validated: if present, can be used to launch a callback, it can have the values 1066 - validated: if present, can be used to launch a callback, it can have the
1067 values
1054 - C.BOOL_TRUE 1068 - C.BOOL_TRUE
1055 - C.BOOL_FALSE 1069 - C.BOOL_FALSE
1056 """ 1070 """
1057 #  FIXME: security limit need to be checked here 1071 #  FIXME: security limit need to be checked here
1058 try: 1072 try:
1115 help_string="", 1129 help_string="",
1116 type_=C.MENU_GLOBAL, 1130 type_=C.MENU_GLOBAL,
1117 ): 1131 ):
1118 """register a new menu for frontends 1132 """register a new menu for frontends
1119 1133
1120 @param path(iterable[unicode]): path to go to the menu (category/subcategory/.../item) (e.g.: ("File", "Open")) 1134 @param path(iterable[unicode]): path to go to the menu
1135 (category/subcategory/.../item) (e.g.: ("File", "Open"))
1121 /!\ use D_() instead of _() for translations (e.g. (D_("File"), D_("Open"))) 1136 /!\ use D_() instead of _() for translations (e.g. (D_("File"), D_("Open")))
1122 untranslated/lower case path can be used to identity a menu, for this reason it must be unique independently of case. 1137 untranslated/lower case path can be used to identity a menu, for this reason
1123 @param callback(callable): method to be called when menuitem is selected, callable or a callback id (string) as returned by [registerCallback] 1138 it must be unique independently of case.
1139 @param callback(callable): method to be called when menuitem is selected, callable
1140 or a callback id (string) as returned by [registerCallback]
1124 @param security_limit(int): %(doc_security_limit)s 1141 @param security_limit(int): %(doc_security_limit)s
1125 /!\ security_limit MUST be added to data in launchCallback if used #TODO 1142 /!\ security_limit MUST be added to data in launchCallback if used #TODO
1126 @param help_string(unicode): string used to indicate what the menu do (can be show as a tooltip). 1143 @param help_string(unicode): string used to indicate what the menu do (can be
1144 show as a tooltip).
1127 /!\ use D_() instead of _() for translations 1145 /!\ use D_() instead of _() for translations
1128 @param type(unicode): one of: 1146 @param type(unicode): one of:
1129 - C.MENU_GLOBAL: classical menu, can be shown in a menubar on top (e.g. something like File/Open) 1147 - C.MENU_GLOBAL: classical menu, can be shown in a menubar on top (e.g.
1148 something like File/Open)
1130 - C.MENU_ROOM: like a global menu, but only shown in multi-user chat 1149 - C.MENU_ROOM: like a global menu, but only shown in multi-user chat
1131 menu_data must contain a "room_jid" data 1150 menu_data must contain a "room_jid" data
1132 - C.MENU_SINGLE: like a global menu, but only shown in one2one chat 1151 - C.MENU_SINGLE: like a global menu, but only shown in one2one chat
1133 menu_data must contain a "jid" data 1152 menu_data must contain a "jid" data
1134 - C.MENU_JID_CONTEXT: contextual menu, used with any jid (e.g.: ad hoc commands, jid is already filled) 1153 - C.MENU_JID_CONTEXT: contextual menu, used with any jid (e.g.: ad hoc
1154 commands, jid is already filled)
1135 menu_data must contain a "jid" data 1155 menu_data must contain a "jid" data
1136 - C.MENU_ROSTER_JID_CONTEXT: like JID_CONTEXT, but restricted to jids in roster. 1156 - C.MENU_ROSTER_JID_CONTEXT: like JID_CONTEXT, but restricted to jids in
1157 roster.
1137 menu_data must contain a "room_jid" data 1158 menu_data must contain a "room_jid" data
1138 - C.MENU_ROSTER_GROUP_CONTEXT: contextual menu, used with group (e.g.: publish microblog, group is already filled) 1159 - C.MENU_ROSTER_GROUP_CONTEXT: contextual menu, used with group (e.g.: publish
1160 microblog, group is already filled)
1139 menu_data must contain a "group" data 1161 menu_data must contain a "group" data
1140 @return (unicode): menu_id (same as callback_id) 1162 @return (unicode): menu_id (same as callback_id)
1141 """ 1163 """
1142 1164
1143 if callable(callback): 1165 if callable(callback):