Mercurial > libervia-backend
changeset 2624:56f94936df1e
code style reformatting using black
line wrap: on
line diff
--- a/sat/__init__.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/__init__.py Wed Jun 27 20:14:46 2018 +0200 @@ -18,10 +18,10 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os.path -version_file = os.path.join(os.path.dirname(__file__), 'VERSION') +version_file = os.path.join(os.path.dirname(__file__), "VERSION") try: with open(version_file) as f: __version__ = f.read().strip() except NotImplementedError: # pyjamas workaround - __version__ = '0.7D' + __version__ = "0.7D"
--- a/sat/bridge/bridge_constructor/base_constructor.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/bridge/bridge_constructor/base_constructor.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SàT: a XMPP client # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) @@ -29,7 +29,7 @@ class ParseError(Exception): - #Used when the signature parsing is going wrong (invalid signature ?) + # Used when the signature parsing is going wrong (invalid signature ?) pass @@ -41,7 +41,7 @@ # replace ##SIGNALS_PART## in template), while the value is the format # keys starting with "signal" will be used for signals, while ones starting with # "method" will be used for methods - # check D-Bus constructor for an example + # check D-Bus constructor for an example CORE_FORMATS = None CORE_TEMPLATE = None CORE_DEST = None @@ -66,7 +66,7 @@ @param name: Name of the function to get @return: dict, each key has the config value or None if the value is not set""" function = {} - for option in ['type', 'category', 'sig_in', 'sig_out', 'doc']: + for option in ["type", "category", "sig_in", "sig_out", "doc"]: try: value = self.bridge_template.get(name, option) except NoOptionError: @@ -87,7 +87,9 @@ try: idx = int(match.group(1)) except ValueError: - raise ParseError("Invalid value [%s] for parameter number" % match.group(1)) + raise ParseError( + "Invalid value [%s] for parameter number" % match.group(1) + ) default_dict[idx] = self.bridge_template.get(name, option) return default_dict @@ -112,15 +114,17 @@ option_re = re.compile(r"doc_param_(\d+)") value_re = re.compile(r"^(\w+): (.*)$", re.MULTILINE | re.DOTALL) for option in self.bridge_template.options(name): - if option == 'doc_return': - doc_dict['return'] = self.bridge_template.get(name, option) + if option == "doc_return": + doc_dict["return"] = self.bridge_template.get(name, option) continue match = option_re.match(option) if match: try: idx = int(match.group(1)) except ValueError: - raise ParseError("Invalid value [%s] for parameter number" % match.group(1)) + raise ParseError( + "Invalid value [%s] for parameter number" % match.group(1) + ) value_match = value_re.match(self.bridge_template.get(name, option)) if not value_match: raise ParseError("Invalid value for parameter doc [%i]" % idx) @@ -141,21 +145,35 @@ i = 0 while i < len(signature): - if signature[i] not in ['b', 'y', 'n', 'i', 'x', 'q', 'u', 't', 'd', 's', 'a']: + if signature[i] not in [ + "b", + "y", + "n", + "i", + "x", + "q", + "u", + "t", + "d", + "s", + "a", + ]: raise ParseError("Unmanaged attribute type [%c]" % signature[i]) - if signature[i] == 'a': + if signature[i] == "a": i += 1 - if signature[i] != '{' and signature[i] != '(': # FIXME: must manage tuples out of arrays + if ( + signature[i] != "{" and signature[i] != "(" + ): # FIXME: must manage tuples out of arrays i += 1 yield signature[start:i] start = i continue # we have a simple type for the array opening_car = signature[i] - assert(opening_car in ['{', '(']) - closing_car = '}' if opening_car == '{' else ')' + assert opening_car in ["{", "("] + closing_car = "}" if opening_car == "{" else ")" opening_count = 1 - while (True): # we have a dict or a list of tuples + while True: # we have a dict or a list of tuples i += 1 if i >= len(signature): raise ParseError("missing }") @@ -182,11 +200,19 @@ attr_string = [] for arg in self.argumentsParser(signature): - attr_string.append(("unicode(%(name)s)%(default)s" if (unicode_protect and arg == 's') else "%(name)s%(default)s") % { - 'name': name[idx][0] if (name and idx in name) else "arg_%i" % idx, - 'default': "=" + default[idx] if (default and idx in default) else ''}) - # give arg_1, arg2, etc or name1, name2=default, etc. - #give unicode(arg_1), unicode(arg_2), etc. if unicode_protect is set and arg is a string + attr_string.append( + ( + "unicode(%(name)s)%(default)s" + if (unicode_protect and arg == "s") + else "%(name)s%(default)s" + ) + % { + "name": name[idx][0] if (name and idx in name) else "arg_%i" % idx, + "default": "=" + default[idx] if (default and idx in default) else "", + } + ) + # give arg_1, arg2, etc or name1, name2=default, etc. + # give unicode(arg_1), unicode(arg_2), etc. if unicode_protect is set and arg is a string idx += 1 return ", ".join(attr_string) @@ -214,7 +240,6 @@ """override this method to extend completion""" pass - def generate(self, side): """generate bridge @@ -242,7 +267,7 @@ @param side(str): core or frontend """ side_vars = [] - for var in ('FORMATS', 'TEMPLATE', 'DEST'): + for var in ("FORMATS", "TEMPLATE", "DEST"): attr = "{}_{}".format(side.upper(), var) value = getattr(self, attr) if value is None: @@ -252,59 +277,70 @@ FORMATS, TEMPLATE, DEST = side_vars del side_vars - parts = {part.upper():[] for part in FORMATS} + parts = {part.upper(): [] for part in FORMATS} sections = self.bridge_template.sections() sections.sort() for section in sections: function = self.getValues(section) - print ("Adding %s %s" % (section, function["type"])) + print("Adding %s %s" % (section, function["type"])) default = self.getDefault(section) arg_doc = self.getArgumentsDoc(section) async_ = "async" in self.getFlags(section) completion = { - 'sig_in': function['sig_in'] or '', - 'sig_out': function['sig_out'] or '', - 'category': 'plugin' if function['category'] == 'plugin' else 'core', - 'name': section, + "sig_in": function["sig_in"] or "", + "sig_out": function["sig_out"] or "", + "category": "plugin" if function["category"] == "plugin" else "core", + "name": section, # arguments with default values - 'args': self.getArguments(function['sig_in'], name=arg_doc, default=default), - } + "args": self.getArguments( + function["sig_in"], name=arg_doc, default=default + ), + } - extend_method = getattr(self, "{}_completion_{}".format(side, function["type"])) + extend_method = getattr( + self, "{}_completion_{}".format(side, function["type"]) + ) extend_method(completion, function, default, arg_doc, async_) for part, fmt in FORMATS.iteritems(): if part.startswith(function["type"]): parts[part.upper()].append(fmt.format(**completion)) - - #at this point, signals_part, methods_part and direct_calls should be filled, - #we just have to place them in the right part of the template + # at this point, signals_part, methods_part and direct_calls should be filled, + # we just have to place them in the right part of the template bridge = [] - const_override = {env[len(C.ENV_OVERRIDE):]:v for env,v in os.environ.iteritems() if env.startswith(C.ENV_OVERRIDE)} + const_override = { + env[len(C.ENV_OVERRIDE) :]: v + for env, v in os.environ.iteritems() + if env.startswith(C.ENV_OVERRIDE) + } template_path = self.getTemplatePath(TEMPLATE) try: with open(template_path) as template: for line in template: for part, extend_list in parts.iteritems(): - if line.startswith('##{}_PART##'.format(part)): + if line.startswith("##{}_PART##".format(part)): bridge.extend(extend_list) break else: # the line is not a magic part replacement - if line.startswith('const_'): - const_name = line[len('const_'):line.find(' = ')].strip() + if line.startswith("const_"): + const_name = line[len("const_") : line.find(" = ")].strip() if const_name in const_override: print("const {} overriden".format(const_name)) - bridge.append('const_{} = {}'.format(const_name, const_override[const_name])) + bridge.append( + "const_{} = {}".format( + const_name, const_override[const_name] + ) + ) continue - bridge.append(line.replace('\n', '')) + bridge.append(line.replace("\n", "")) except IOError: - print ("can't open template file [{}]".format(template_path)) + print("can't open template file [{}]".format(template_path)) sys.exit(1) - #now we write to final file + # now we write to final file self.finalWrite(DEST, bridge) def finalWrite(self, filename, file_buf): @@ -314,19 +350,24 @@ @param file_buf: list of lines (stings) of the file """ if os.path.exists(self.args.dest_dir) and not os.path.isdir(self.args.dest_dir): - print ("The destination dir [%s] can't be created: a file with this name already exists !") + print( + "The destination dir [%s] can't be created: a file with this name already exists !" + ) sys.exit(1) try: if not os.path.exists(self.args.dest_dir): os.mkdir(self.args.dest_dir) full_path = os.path.join(self.args.dest_dir, filename) if os.path.exists(full_path) and not self.args.force: - print ("The destination file [%s] already exists ! Use --force to overwrite it" % full_path) + print( + "The destination file [%s] already exists ! Use --force to overwrite it" + % full_path + ) try: - with open(full_path, 'w') as dest_file: - dest_file.write('\n'.join(file_buf)) + with open(full_path, "w") as dest_file: + dest_file.write("\n".join(file_buf)) except IOError: - print ("Can't open destination file [%s]" % full_path) + print("Can't open destination file [%s]" % full_path) except OSError: print("It's not possible to generate the file, check your permissions") exit(1)
--- a/sat/bridge/bridge_constructor/bridge_constructor.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/bridge/bridge_constructor/bridge_constructor.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SàT: a XMPP client # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) @@ -23,23 +23,24 @@ from sat.bridge.bridge_constructor import constructors, base_constructor import argparse from ConfigParser import SafeConfigParser as Parser -from importlib import import_module +from importlib import import_module import os import os.path -#consts +# consts __version__ = C.APP_VERSION class BridgeConstructor(object): - def importConstructors(self): constructors_dir = os.path.dirname(constructors.__file__) self.protocoles = {} for dir_ in os.listdir(constructors_dir): - init_path = os.path.join(constructors_dir, dir_, '__init__.py') - constructor_path = os.path.join(constructors_dir, dir_, 'constructor.py') - module_path = "sat.bridge.bridge_constructor.constructors.{}.constructor".format(dir_) + init_path = os.path.join(constructors_dir, dir_, "__init__.py") + constructor_path = os.path.join(constructors_dir, dir_, "constructor.py") + module_path = "sat.bridge.bridge_constructor.constructors.{}.constructor".format( + dir_ + ) if os.path.isfile(init_path) and os.path.isfile(constructor_path): mod = import_module(module_path) for attr in dir(mod): @@ -55,27 +56,66 @@ def parse_args(self): """Check command line options""" - parser = argparse.ArgumentParser(description=C.DESCRIPTION, formatter_class=argparse.RawDescriptionHelpFormatter) + parser = argparse.ArgumentParser( + description=C.DESCRIPTION, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) - parser.add_argument("--version", action="version", version= __version__) - default_protocole = C.DEFAULT_PROTOCOLE if C.DEFAULT_PROTOCOLE in self.protocoles else self.protocoles[0] - parser.add_argument("-p", "--protocole", choices=sorted(self.protocoles), default=default_protocole, - help="generate bridge using PROTOCOLE (default: %(default)s)") # (default: %s, possible values: [%s])" % (DEFAULT_PROTOCOLE, ", ".join(MANAGED_PROTOCOLES))) - parser.add_argument("-s", "--side", choices=("core", "frontend"), default="core", - help="which side of the bridge do you want to make ?") # (default: %default, possible values: [core, frontend])") - default_template = os.path.join(os.path.dirname(bridge_constructor.__file__), 'bridge_template.ini') - parser.add_argument("-t", "--template", type=file, default=default_template, - help="use TEMPLATE to generate bridge (default: %(default)s)") - parser.add_argument("-f", "--force", action="store_true", - help=("force overwritting of existing files")) - parser.add_argument("-d", "--debug", action="store_true", - help=("add debug information printing")) - parser.add_argument("--no-unicode", action="store_false", dest="unicode", - help=("remove unicode type protection from string results")) - parser.add_argument("--flags", nargs='+', default=[], - help=("constructors' specific flags")) - parser.add_argument("--dest-dir", default=C.DEST_DIR_DEFAULT, - help=("directory when the generated files will be written (default: %(default)s)")) + parser.add_argument("--version", action="version", version=__version__) + default_protocole = ( + C.DEFAULT_PROTOCOLE + if C.DEFAULT_PROTOCOLE in self.protocoles + else self.protocoles[0] + ) + parser.add_argument( + "-p", + "--protocole", + choices=sorted(self.protocoles), + default=default_protocole, + help="generate bridge using PROTOCOLE (default: %(default)s)", + ) # (default: %s, possible values: [%s])" % (DEFAULT_PROTOCOLE, ", ".join(MANAGED_PROTOCOLES))) + parser.add_argument( + "-s", + "--side", + choices=("core", "frontend"), + default="core", + help="which side of the bridge do you want to make ?", + ) # (default: %default, possible values: [core, frontend])") + default_template = os.path.join( + os.path.dirname(bridge_constructor.__file__), "bridge_template.ini" + ) + parser.add_argument( + "-t", + "--template", + type=file, + default=default_template, + help="use TEMPLATE to generate bridge (default: %(default)s)", + ) + parser.add_argument( + "-f", + "--force", + action="store_true", + help=("force overwritting of existing files"), + ) + parser.add_argument( + "-d", "--debug", action="store_true", help=("add debug information printing") + ) + parser.add_argument( + "--no-unicode", + action="store_false", + dest="unicode", + help=("remove unicode type protection from string results"), + ) + parser.add_argument( + "--flags", nargs="+", default=[], help=("constructors' specific flags") + ) + parser.add_argument( + "--dest-dir", + default=C.DEST_DIR_DEFAULT, + help=( + "directory when the generated files will be written (default: %(default)s)" + ), + ) return parser.parse_args() @@ -86,7 +126,7 @@ try: template_parser.readfp(args.template) except IOError: - print ("The template file doesn't exist or is not accessible") + print("The template file doesn't exist or is not accessible") exit(1) constructor = self.protocoles[args.protocole](template_parser, args) constructor.generate(args.side)
--- a/sat/bridge/bridge_constructor/constants.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/bridge/bridge_constructor/constants.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SàT: a XMPP client # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) @@ -31,11 +31,13 @@ This program comes with ABSOLUTELY NO WARRANTY; This is free software, and you are welcome to redistribute it under certain conditions. - """.format(name=NAME, version=constants.Const.APP_VERSION) -# TODO: move protocoles in separate files (plugins?) - DEFAULT_PROTOCOLE = 'dbus' + """.format( + name=NAME, version=constants.Const.APP_VERSION + ) + # TODO: move protocoles in separate files (plugins?) + DEFAULT_PROTOCOLE = "dbus" -# flags used method/signal declaration (not to be confused with constructor flags) - DECLARATION_FLAGS = ['deprecated', 'async'] + # flags used method/signal declaration (not to be confused with constructor flags) + DECLARATION_FLAGS = ["deprecated", "async"] ENV_OVERRIDE = "SAT_BRIDGE_CONST_" # Prefix used to override a constant
--- a/sat/bridge/bridge_constructor/constructors/dbus-xml/constructor.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/bridge/bridge_constructor/constructors/dbus-xml/constructor.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SàT: a XMPP client # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) @@ -30,62 +30,73 @@ self.template = "dbus_xml_template.xml" self.core_dest = "org.goffi.sat.xml" - self.default_annotation = {'a{ss}': 'StringDict', - 'a(sa{ss}as)': 'QList<Contact>', - 'a{i(ss)}': 'HistoryT', - 'a(sss)': 'QList<MenuT>', - 'a{sa{s(sia{ss})}}': 'PresenceStatusT', - } + self.default_annotation = { + "a{ss}": "StringDict", + "a(sa{ss}as)": "QList<Contact>", + "a{i(ss)}": "HistoryT", + "a(sss)": "QList<MenuT>", + "a{sa{s(sia{ss})}}": "PresenceStatusT", + } def generateCoreSide(self): try: doc = minidom.parse(self.getTemplatePath(self.template)) - interface_elt = doc.getElementsByTagName('interface')[0] + interface_elt = doc.getElementsByTagName("interface")[0] except IOError: - print ("Can't access template") + print("Can't access template") sys.exit(1) except IndexError: - print ("Template error") + print("Template error") sys.exit(1) sections = self.bridge_template.sections() sections.sort() for section in sections: function = self.getValues(section) - print ("Adding %s %s" % (section, function["type"])) - new_elt = doc.createElement('method' if function["type"] == 'method' else 'signal') - new_elt.setAttribute('name', section) + print("Adding %s %s" % (section, function["type"])) + new_elt = doc.createElement( + "method" if function["type"] == "method" else "signal" + ) + new_elt.setAttribute("name", section) idx = 0 args_doc = self.getArgumentsDoc(section) - for arg in self.argumentsParser(function['sig_in'] or ''): - arg_elt = doc.createElement('arg') - arg_elt.setAttribute('name', args_doc[idx][0] if idx in args_doc else "arg_%i" % idx) - arg_elt.setAttribute('type', arg) - _direction = 'in' if function["type"] == 'method' else 'out' - arg_elt.setAttribute('direction', _direction) + for arg in self.argumentsParser(function["sig_in"] or ""): + arg_elt = doc.createElement("arg") + arg_elt.setAttribute( + "name", args_doc[idx][0] if idx in args_doc else "arg_%i" % idx + ) + arg_elt.setAttribute("type", arg) + _direction = "in" if function["type"] == "method" else "out" + arg_elt.setAttribute("direction", _direction) new_elt.appendChild(arg_elt) if "annotation" in self.args.flags: if arg in self.default_annotation: annot_elt = doc.createElement("annotation") - annot_elt.setAttribute('name', "com.trolltech.QtDBus.QtTypeName.In%d" % idx) - annot_elt.setAttribute('value', self.default_annotation[arg]) + annot_elt.setAttribute( + "name", "com.trolltech.QtDBus.QtTypeName.In%d" % idx + ) + annot_elt.setAttribute("value", self.default_annotation[arg]) new_elt.appendChild(annot_elt) idx += 1 - if function['sig_out']: - arg_elt = doc.createElement('arg') - arg_elt.setAttribute('type', function['sig_out']) - arg_elt.setAttribute('direction', 'out') + if function["sig_out"]: + arg_elt = doc.createElement("arg") + arg_elt.setAttribute("type", function["sig_out"]) + arg_elt.setAttribute("direction", "out") new_elt.appendChild(arg_elt) if "annotation" in self.args.flags: - if function['sig_out'] in self.default_annotation: + if function["sig_out"] in self.default_annotation: annot_elt = doc.createElement("annotation") - annot_elt.setAttribute('name', "com.trolltech.QtDBus.QtTypeName.Out0") - annot_elt.setAttribute('value', self.default_annotation[function['sig_out']]) + annot_elt.setAttribute( + "name", "com.trolltech.QtDBus.QtTypeName.Out0" + ) + annot_elt.setAttribute( + "value", self.default_annotation[function["sig_out"]] + ) new_elt.appendChild(annot_elt) interface_elt.appendChild(new_elt) - #now we write to final file + # now we write to final file self.finalWrite(self.core_dest, [doc.toprettyxml()])
--- a/sat/bridge/bridge_constructor/constructors/dbus/constructor.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/bridge/bridge_constructor/constructors/dbus/constructor.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SàT: a XMPP client # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) @@ -25,73 +25,94 @@ CORE_TEMPLATE = "dbus_core_template.py" CORE_DEST = "dbus_bridge.py" CORE_FORMATS = { - 'signals': """\ + "signals": """\ @dbus.service.signal(const_INT_PREFIX+const_{category}_SUFFIX, signature='{sig_in}') def {name}(self, {args}): {body}\n""", - - 'methods': """\ + "methods": """\ @dbus.service.method(const_INT_PREFIX+const_{category}_SUFFIX, in_signature='{sig_in}', out_signature='{sig_out}', async_callbacks={async_callbacks}) def {name}(self, {args}{async_comma}{async_args_def}): {debug}return self._callback("{name}", {args_result}{async_comma}{async_args_call})\n""", - - 'signal_direct_calls': """\ + "signal_direct_calls": """\ def {name}(self, {args}): self.dbus_bridge.{name}({args})\n""", - } + } FRONTEND_TEMPLATE = "dbus_frontend_template.py" FRONTEND_DEST = CORE_DEST FRONTEND_FORMATS = { - 'methods': """\ + "methods": """\ def {name}(self, {args}{async_comma}{async_args}): - {error_handler}{blocking_call}{debug}return {result}\n""", - } + {error_handler}{blocking_call}{debug}return {result}\n""" + } def core_completion_signal(self, completion, function, default, arg_doc, async_): - completion['category'] = completion['category'].upper() - completion['body'] = "pass" if not self.args.debug else 'log.debug ("{}")'.format(completion['name']) + completion["category"] = completion["category"].upper() + completion["body"] = ( + "pass" + if not self.args.debug + else 'log.debug ("{}")'.format(completion["name"]) + ) def core_completion_method(self, completion, function, default, arg_doc, async_): - completion.update({ - 'debug': "" if not self.args.debug else 'log.debug ("%s")\n%s' % (completion['name'], 8 * ' '), - 'args_result': self.getArguments(function['sig_in'], name=arg_doc, unicode_protect=self.args.unicode), - 'async_comma': ', ' if async_ and function['sig_in'] else '', - 'async_args_def': 'callback=None, errback=None' if async_ else '', - 'async_args_call': 'callback=callback, errback=errback' if async_ else '', - 'async_callbacks': "('callback', 'errback')" if async_ else "None", - 'category': completion['category'].upper(), - }) + completion.update( + { + "debug": "" + if not self.args.debug + else 'log.debug ("%s")\n%s' % (completion["name"], 8 * " "), + "args_result": self.getArguments( + function["sig_in"], name=arg_doc, unicode_protect=self.args.unicode + ), + "async_comma": ", " if async_ and function["sig_in"] else "", + "async_args_def": "callback=None, errback=None" if async_ else "", + "async_args_call": "callback=callback, errback=errback" if async_ else "", + "async_callbacks": "('callback', 'errback')" if async_ else "None", + "category": completion["category"].upper(), + } + ) def frontend_completion_method(self, completion, function, default, arg_doc, async_): - completion.update({ - # XXX: we can manage blocking call in the same way as async one: if callback is None the call will be blocking - 'debug': "" if not self.args.debug else 'log.debug ("%s")\n%s' % (completion['name'], 8 * ' '), - 'args_result': self.getArguments(function['sig_in'], name=arg_doc), - 'async_args': 'callback=None, errback=None', - 'async_comma': ', ' if function['sig_in'] else '', - 'error_handler': """if callback is None: + completion.update( + { + # XXX: we can manage blocking call in the same way as async one: if callback is None the call will be blocking + "debug": "" + if not self.args.debug + else 'log.debug ("%s")\n%s' % (completion["name"], 8 * " "), + "args_result": self.getArguments(function["sig_in"], name=arg_doc), + "async_args": "callback=None, errback=None", + "async_comma": ", " if function["sig_in"] else "", + "error_handler": """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)) """, - }) + } + ) if async_: - completion['blocking_call'] = '' - completion['async_args_result'] = 'timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler' + completion["blocking_call"] = "" + completion[ + "async_args_result" + ] = "timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler" else: # XXX: To have a blocking call, we must have not reply_handler, so we test if callback exists, and add reply_handler only in this case - completion['blocking_call'] = """kwargs={} + completion[ + "blocking_call" + ] = """kwargs={} if callback is not None: kwargs['timeout'] = const_TIMEOUT kwargs['reply_handler'] = callback kwargs['error_handler'] = error_handler """ - completion['async_args_result'] = '**kwargs' - result = "self.db_%(category)s_iface.%(name)s(%(args_result)s%(async_comma)s%(async_args_result)s)" % completion - completion['result'] = ("unicode(%s)" if self.args.unicode and function['sig_out'] == 's' else "%s") % result + completion["async_args_result"] = "**kwargs" + result = ( + "self.db_%(category)s_iface.%(name)s(%(args_result)s%(async_comma)s%(async_args_result)s)" + % completion + ) + completion["result"] = ( + "unicode(%s)" if self.args.unicode and function["sig_out"] == "s" else "%s" + ) % result
--- a/sat/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SAT: a jabber client # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) @@ -23,13 +23,14 @@ import dbus.mainloop.glib import inspect from sat.core.log import getLogger + log = getLogger(__name__) from twisted.internet.defer import Deferred from sat.core.exceptions import BridgeInitError const_INT_PREFIX = "org.goffi.SAT" # Interface prefix const_ERROR_PREFIX = const_INT_PREFIX + ".error" -const_OBJ_PATH = '/org/goffi/SAT/bridge' +const_OBJ_PATH = "/org/goffi/SAT/bridge" const_CORE_SUFFIX = ".core" const_PLUGIN_SUFFIX = ".plugin" @@ -73,11 +74,12 @@ self.args = (message, twisted_error.value.condition) except AttributeError: self.args = (message,) - self._dbus_error_name = '.'.join([const_ERROR_PREFIX, class_.__module__, class_.__name__]) + self._dbus_error_name = ".".join( + [const_ERROR_PREFIX, class_.__module__, class_.__name__] + ) class DbusObject(dbus.service.Object): - def __init__(self, bus, path): dbus.service.Object.__init__(self, bus, path) log.debug("Init DbusObject...") @@ -93,7 +95,7 @@ raise MethodNotRegistered if "callback" in kwargs: - #we must have errback too + # we must have errback too if not "errback" in kwargs: log.error("errback is missing in method call [%s]" % name) raise InternalError @@ -107,27 +109,29 @@ if not isinstance(result, Deferred): log.error("Asynchronous method [%s] does not return a Deferred." % name) raise AsyncNotDeferred - result.addCallback(lambda result: callback() if result is None else callback(result)) + result.addCallback( + lambda result: callback() if result is None else callback(result) + ) result.addErrback(lambda err: errback(GenericException(err))) else: if isinstance(result, Deferred): log.error("Synchronous method [%s] return a Deferred." % name) raise DeferredNotAsync return result + ### signals ### - @dbus.service.signal(const_INT_PREFIX + const_PLUGIN_SUFFIX, - signature='') + @dbus.service.signal(const_INT_PREFIX + const_PLUGIN_SUFFIX, signature="") def dummySignal(self): - #FIXME: workaround for addSignal (doesn't work if one method doensn't + # FIXME: workaround for addSignal (doesn't work if one method doensn't # already exist for plugins), probably missing some initialisation, need # further investigations pass -##SIGNALS_PART## + ##SIGNALS_PART## ### methods ### -##METHODS_PART## + ##METHODS_PART## def __attributes(self, in_sign): """Return arguments to user given a in_sign @param in_sign: in_sign in the short form (using s,a,i,b etc) @@ -136,22 +140,24 @@ idx = 0 attr = [] while i < len(in_sign): - if in_sign[i] not in ['b', 'y', 'n', 'i', 'x', 'q', 'u', 't', 'd', 's', 'a']: + if in_sign[i] not in ["b", "y", "n", "i", "x", "q", "u", "t", "d", "s", "a"]: raise ParseError("Unmanaged attribute type [%c]" % in_sign[i]) attr.append("arg_%i" % idx) idx += 1 - if in_sign[i] == 'a': + if in_sign[i] == "a": i += 1 - if in_sign[i] != '{' and in_sign[i] != '(': # FIXME: must manage tuples out of arrays + if ( + in_sign[i] != "{" and in_sign[i] != "(" + ): # FIXME: must manage tuples out of arrays i += 1 continue # we have a simple type for the array opening_car = in_sign[i] - assert(opening_car in ['{', '(']) - closing_car = '}' if opening_car == '{' else ')' + assert opening_car in ["{", "("] + closing_car = "}" if opening_car == "{" else ")" opening_count = 1 - while (True): # we have a dict or a list of tuples + while True: # we have a dict or a list of tuples i += 1 if i >= len(in_sign): raise ParseError("missing }") @@ -172,47 +178,80 @@ _defaults = list(inspect_args.defaults or []) if inspect.ismethod(method): - #if we have a method, we don't want the first argument (usually 'self') - del(_arguments[0]) + # if we have a method, we don't want the first argument (usually 'self') + del (_arguments[0]) - #first arguments are for the _callback method - arguments_callback = ', '.join([repr(name)] + ((_arguments + ['callback=callback', 'errback=errback']) if async else _arguments)) + # first arguments are for the _callback method + arguments_callback = ", ".join( + [repr(name)] + + ( + (_arguments + ["callback=callback", "errback=errback"]) + if async + else _arguments + ) + ) if async: - _arguments.extend(['callback', 'errback']) + _arguments.extend(["callback", "errback"]) _defaults.extend([None, None]) - #now we create a second list with default values + # now we create a second list with default values for i in range(1, len(_defaults) + 1): _arguments[-i] = "%s = %s" % (_arguments[-i], repr(_defaults[-i])) - arguments_defaults = ', '.join(_arguments) + arguments_defaults = ", ".join(_arguments) - code = compile('def %(name)s (self,%(arguments_defaults)s): return self._callback(%(arguments_callback)s)' % - {'name': name, 'arguments_defaults': arguments_defaults, 'arguments_callback': arguments_callback}, '<DBus bridge>', 'exec') - exec (code) # FIXME: to the same thing in a cleaner way, without compile/exec + code = compile( + "def %(name)s (self,%(arguments_defaults)s): return self._callback(%(arguments_callback)s)" + % { + "name": name, + "arguments_defaults": arguments_defaults, + "arguments_callback": arguments_callback, + }, + "<DBus bridge>", + "exec", + ) + exec(code) # FIXME: to the same thing in a cleaner way, without compile/exec method = locals()[name] - async_callbacks = ('callback', 'errback') if async else None - setattr(DbusObject, name, dbus.service.method( - const_INT_PREFIX + int_suffix, in_signature=in_sign, out_signature=out_sign, - async_callbacks=async_callbacks)(method)) + async_callbacks = ("callback", "errback") if async else None + setattr( + DbusObject, + name, + dbus.service.method( + const_INT_PREFIX + int_suffix, + in_signature=in_sign, + out_signature=out_sign, + async_callbacks=async_callbacks, + )(method), + ) function = getattr(self, name) - func_table = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__][function._dbus_interface] + func_table = self._dbus_class_table[ + self.__class__.__module__ + "." + self.__class__.__name__ + ][function._dbus_interface] func_table[function.__name__] = function # Needed for introspection def addSignal(self, name, int_suffix, signature, doc={}): """Dynamically add a signal to Dbus Bridge""" - attributes = ', '.join(self.__attributes(signature)) - #TODO: use doc parameter to name attributes + attributes = ", ".join(self.__attributes(signature)) + # TODO: use doc parameter to name attributes - #code = compile ('def '+name+' (self,'+attributes+'): log.debug ("'+name+' signal")', '<DBus bridge>','exec') #XXX: the log.debug is too annoying with xmllog - code = compile('def ' + name + ' (self,' + attributes + '): pass', '<DBus bridge>', 'exec') - exec (code) + # code = compile ('def '+name+' (self,'+attributes+'): log.debug ("'+name+' signal")', '<DBus bridge>','exec') #XXX: the log.debug is too annoying with xmllog + code = compile( + "def " + name + " (self," + attributes + "): pass", "<DBus bridge>", "exec" + ) + exec(code) signal = locals()[name] - setattr(DbusObject, name, dbus.service.signal( - const_INT_PREFIX + int_suffix, signature=signature)(signal)) + setattr( + DbusObject, + name, + dbus.service.signal(const_INT_PREFIX + int_suffix, signature=signature)( + signal + ), + ) function = getattr(self, name) - func_table = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__][function._dbus_interface] + func_table = self._dbus_class_table[ + self.__class__.__module__ + "." + self.__class__.__name__ + ][function._dbus_interface] func_table[function.__name__] = function # Needed for introspection @@ -223,20 +262,24 @@ try: self.session_bus = dbus.SessionBus() except dbus.DBusException as e: - if e._dbus_error_name == 'org.freedesktop.DBus.Error.NotSupported': - log.error(_(u"D-Bus is not launched, please see README to see instructions on how to launch it")) + if e._dbus_error_name == "org.freedesktop.DBus.Error.NotSupported": + log.error( + _( + u"D-Bus is not launched, please see README to see instructions on how to launch it" + ) + ) raise BridgeInitError self.dbus_name = dbus.service.BusName(const_INT_PREFIX, self.session_bus) self.dbus_bridge = DbusObject(self.session_bus, const_OBJ_PATH) -##SIGNAL_DIRECT_CALLS_PART## + ##SIGNAL_DIRECT_CALLS_PART## def register_method(self, name, callback): log.debug("registering DBus bridge method [%s]" % name) self.dbus_bridge.register_method(name, callback) def addMethod(self, name, int_suffix, in_sign, out_sign, method, async=False, doc={}): """Dynamically add a method to Dbus Bridge""" - #FIXME: doc parameter is kept only temporary, the time to remove it from calls + # FIXME: doc parameter is kept only temporary, the time to remove it from calls log.debug("Adding method [%s] to DBus bridge" % name) self.dbus_bridge.addMethod(name, int_suffix, in_sign, out_sign, method, async) self.register_method(name, method)
--- a/sat/bridge/bridge_constructor/constructors/embedded/constructor.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/bridge/bridge_constructor/constructors/embedded/constructor.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SàT: a XMPP client # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) @@ -18,7 +18,8 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.bridge.bridge_constructor import base_constructor -# from textwraps import dedent + +# from textwraps import dedent class EmbeddedConstructor(base_constructor.Constructor): @@ -26,11 +27,11 @@ CORE_TEMPLATE = "embedded_template.py" CORE_DEST = "embedded.py" CORE_FORMATS = { - 'methods': """\ + "methods": """\ def {name}(self, {args}{args_comma}callback=None, errback=None): {ret_routine} """, - 'signals': """\ + "signals": """\ def {name}(self, {args}): try: cb = self._signals_cbs["{category}"]["{name}"] @@ -38,22 +39,30 @@ log.warning(u"ignoring signal {name}: no callback registered") else: cb({args_result}) -""" - } +""", + } FRONTEND_TEMPLATE = "embedded_frontend_template.py" FRONTEND_DEST = CORE_DEST FRONTEND_FORMATS = {} def core_completion_method(self, completion, function, default, arg_doc, async_): - completion.update({ - 'debug': "" if not self.args.debug else 'log.debug ("%s")\n%s' % (completion['name'], 8 * ' '), - 'args_result': self.getArguments(function['sig_in'], name=arg_doc), - 'args_comma': ', ' if function['sig_in'] else '', - }) + completion.update( + { + "debug": "" + if not self.args.debug + else 'log.debug ("%s")\n%s' % (completion["name"], 8 * " "), + "args_result": self.getArguments(function["sig_in"], name=arg_doc), + "args_comma": ", " if function["sig_in"] else "", + } + ) if async_: - completion["cb_or_lambda"] = "callback" if function['sig_out'] else "lambda dummy: callback()" - completion["ret_routine"] = """\ + completion["cb_or_lambda"] = ( + "callback" if function["sig_out"] else "lambda dummy: callback()" + ) + completion[ + "ret_routine" + ] = """\ d = self._methods_cbs["{name}"]({args_result}) if callback is not None: d.addCallback({cb_or_lambda}) @@ -62,10 +71,14 @@ else: d.addErrback(errback) return d - """.format(**completion) + """.format( + **completion + ) else: - completion['ret_or_nothing'] = 'ret' if function['sig_out'] else '' - completion["ret_routine"] = """\ + completion["ret_or_nothing"] = "ret" if function["sig_out"] else "" + completion[ + "ret_routine" + ] = """\ try: ret = self._methods_cbs["{name}"]({args_result}) except Exception as e: @@ -77,9 +90,11 @@ if callback is None: return ret else: - callback({ret_or_nothing})""".format(**completion) + callback({ret_or_nothing})""".format( + **completion + ) def core_completion_signal(self, completion, function, default, arg_doc, async_): - completion.update({ - 'args_result': self.getArguments(function['sig_in'], name=arg_doc), - }) + completion.update( + {"args_result": self.getArguments(function["sig_in"], name=arg_doc)} + )
--- a/sat/bridge/bridge_constructor/constructors/embedded/embedded_frontend_template.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/bridge/bridge_constructor/constructors/embedded/embedded_frontend_template.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SàT: a XMPP client # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
--- a/sat/bridge/bridge_constructor/constructors/embedded/embedded_template.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/bridge/bridge_constructor/constructors/embedded/embedded_template.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SàT: a XMPP client # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) @@ -18,6 +18,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions @@ -26,10 +27,7 @@ def __init__(self): log.debug(u"Init embedded bridge...") self._methods_cbs = {} - self._signals_cbs = { - "core": {}, - "plugin": {} - } + self._signals_cbs = {"core": {}, "plugin": {}} def bridgeConnect(self, callback, errback): callback() @@ -43,7 +41,11 @@ def register_signal(self, functionName, handler, iface="core"): iface_dict = self._signals_cbs[iface] if functionName in iface_dict: - raise exceptions.ConflictError(u"signal {name} is already regitered for interface {iface}".format(name=functionName, iface=iface)) + raise exceptions.ConflictError( + u"signal {name} is already regitered for interface {iface}".format( + name=functionName, iface=iface + ) + ) iface_dict[functionName] = handler def call_method(self, name, out_sign, async_, args, kwargs): @@ -84,23 +86,36 @@ cb(*args, **kwargs) def addMethod(self, name, int_suffix, in_sign, out_sign, method, async=False, doc={}): - #FIXME: doc parameter is kept only temporary, the time to remove it from calls + # FIXME: doc parameter is kept only temporary, the time to remove it from calls log.debug("Adding method [{}] to embedded bridge".format(name)) self.register_method(name, method) - setattr(self.__class__, name, lambda self_, *args, **kwargs: self.call_method(name, out_sign, async, args, kwargs)) + setattr( + self.__class__, + name, + lambda self_, *args, **kwargs: self.call_method( + name, out_sign, async, args, kwargs + ), + ) def addSignal(self, name, int_suffix, signature, doc={}): - setattr(self.__class__, name, lambda self_, *args, **kwargs: self.send_signal(name, args, kwargs)) + setattr( + self.__class__, + name, + lambda self_, *args, **kwargs: self.send_signal(name, args, kwargs), + ) ## signals ## + ##SIGNALS_PART## - ## methods ## +## methods ## ##METHODS_PART## # we want the same instance for both core and frontend bridge = None + + def Bridge(): global bridge if bridge is None:
--- a/sat/bridge/bridge_constructor/constructors/mediawiki/constructor.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/bridge/bridge_constructor/constructors/mediawiki/constructor.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SàT: a XMPP client # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) @@ -24,7 +24,6 @@ class MediawikiConstructor(base_constructor.Constructor): - def __init__(self, bridge_template, options): base_constructor.Constructor.__init__(self, bridge_template, options) self.core_template = "mediawiki_template.tpl" @@ -35,10 +34,10 @@ def anchor_link(match): link = match.group(1) - #we add anchor_link for [method_name] syntax: + # we add anchor_link for [method_name] syntax: if link in self.bridge_template.sections(): return "[[#%s|%s]]" % (link, link) - print ("WARNING: found an anchor link to an unknown method") + print("WARNING: found an anchor link to an unknown method") return link return re.sub(r"\[(\w+)\]", anchor_link, text) @@ -51,12 +50,12 @@ arg_doc = self.getArgumentsDoc(name) arg_default = self.getDefault(name) args_str = self.getArguments(sig_in) - args = args_str.split(', ') if args_str else [] # ugly but it works :) + args = args_str.split(", ") if args_str else [] # ugly but it works :) wiki = [] for i in range(len(args)): if i in arg_doc: name, doc = arg_doc[i] - doc = '\n:'.join(doc.rstrip('\n').split('\n')) + doc = "\n:".join(doc.rstrip("\n").split("\n")) wiki.append("; %s: %s" % (name, self._addTextDecorations(doc))) else: wiki.append("; arg_%d: " % i) @@ -70,9 +69,13 @@ """ arg_doc = self.getArgumentsDoc(name) wiki = [] - if 'return' in arg_doc: - wiki.append('\n|-\n! scope=row | return value\n|') - wiki.append('<br />\n'.join(self._addTextDecorations(arg_doc['return']).rstrip('\n').split('\n'))) + if "return" in arg_doc: + wiki.append("\n|-\n! scope=row | return value\n|") + wiki.append( + "<br />\n".join( + self._addTextDecorations(arg_doc["return"]).rstrip("\n").split("\n") + ) + ) return "\n".join(wiki) def generateCoreSide(self): @@ -82,37 +85,49 @@ sections.sort() for section in sections: function = self.getValues(section) - print ("Adding %s %s" % (section, function["type"])) + print("Adding %s %s" % (section, function["type"])) async_msg = """<br />'''This method is asynchronous'''""" deprecated_msg = """<br />'''<font color="#FF0000">/!\ WARNING /!\ : This method is deprecated, please don't use it !</font>'''""" - signature_signal = \ - """\ + signature_signal = ( + """\ ! scope=row | signature | %s |-\ -""" % function['sig_in'] - signature_method = \ - """\ +""" + % function["sig_in"] + ) + signature_method = """\ ! scope=row | signature in | %s |- ! scope=row | signature out | %s |-\ -""" % (function['sig_in'], function['sig_out']) +""" % ( + function["sig_in"], + function["sig_out"], + ) completion = { - 'signature': signature_signal if function['type'] == "signal" else signature_method, - 'sig_out': function['sig_out'] or '', - 'category': function['category'], - 'name': section, - 'doc': self.getDoc(section) or "FIXME: No description available", - 'async': async_msg if "async" in self.getFlags(section) else "", - 'deprecated': deprecated_msg if "deprecated" in self.getFlags(section) else "", - 'parameters': self._wikiParameter(section, function['sig_in']), - 'return': self._wikiReturn(section) if function['type'] == 'method' else ''} + "signature": signature_signal + if function["type"] == "signal" + else signature_method, + "sig_out": function["sig_out"] or "", + "category": function["category"], + "name": section, + "doc": self.getDoc(section) or "FIXME: No description available", + "async": async_msg if "async" in self.getFlags(section) else "", + "deprecated": deprecated_msg + if "deprecated" in self.getFlags(section) + else "", + "parameters": self._wikiParameter(section, function["sig_in"]), + "return": self._wikiReturn(section) + if function["type"] == "method" + else "", + } - dest = signals_part if function['type'] == "signal" else methods_part - dest.append("""\ + dest = signals_part if function["type"] == "signal" else methods_part + dest.append( + """\ == %(name)s == ''%(doc)s'' %(deprecated)s @@ -126,28 +141,28 @@ | %(parameters)s%(return)s |} -""" % completion) +""" + % completion + ) - #at this point, signals_part, and methods_part should be filled, - #we just have to place them in the right part of the template + # at this point, signals_part, and methods_part should be filled, + # we just have to place them in the right part of the template core_bridge = [] template_path = self.getTemplatePath(self.core_template) try: with open(template_path) as core_template: for line in core_template: - if line.startswith('##SIGNALS_PART##'): + if line.startswith("##SIGNALS_PART##"): core_bridge.extend(signals_part) - elif line.startswith('##METHODS_PART##'): + elif line.startswith("##METHODS_PART##"): core_bridge.extend(methods_part) - elif line.startswith('##TIMESTAMP##'): - core_bridge.append('Generated on %s' % datetime.now()) + elif line.startswith("##TIMESTAMP##"): + core_bridge.append("Generated on %s" % datetime.now()) else: - core_bridge.append(line.replace('\n', '')) + core_bridge.append(line.replace("\n", "")) except IOError: - print ("Can't open template file [%s]" % template_path) + print("Can't open template file [%s]" % template_path) sys.exit(1) - #now we write to final file + # now we write to final file self.finalWrite(self.core_dest, core_bridge) - -
--- a/sat/bridge/bridge_constructor/constructors/pb/constructor.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/bridge/bridge_constructor/constructors/pb/constructor.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SàT: a XMPP client # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) @@ -25,32 +25,42 @@ CORE_TEMPLATE = "pb_core_template.py" CORE_DEST = "pb.py" CORE_FORMATS = { - 'signals': """\ + "signals": """\ def {name}(self, {args}): - {debug}self.sendSignal("{name}", {args_no_def})\n""", - } + {debug}self.sendSignal("{name}", {args_no_def})\n""" + } FRONTEND_TEMPLATE = "pb_frontend_template.py" FRONTEND_DEST = CORE_DEST FRONTEND_FORMATS = { - 'methods': """\ + "methods": """\ def {name}(self{args_comma}{args}, callback=None, errback=None): {debug}d = self.root.callRemote("{name}"{args_comma}{args_no_def}) if callback is not None: d.addCallback({callback}) if errback is None: errback = self._generic_errback - d.addErrback(errback)\n""", - } + d.addErrback(errback)\n""" + } def core_completion_signal(self, completion, function, default, arg_doc, async_): - completion['args_no_def'] = self.getArguments(function['sig_in'], name=arg_doc) - completion['debug'] = "" if not self.args.debug else 'log.debug ("%s")\n%s' % (completion['name'], 8 * ' ') + completion["args_no_def"] = self.getArguments(function["sig_in"], name=arg_doc) + completion["debug"] = ( + "" + if not self.args.debug + else 'log.debug ("%s")\n%s' % (completion["name"], 8 * " ") + ) def frontend_completion_method(self, completion, function, default, arg_doc, async_): - completion.update({ - 'args_comma': ', ' if function['sig_in'] else '', - 'args_no_def': self.getArguments(function['sig_in'], name=arg_doc), - 'callback': 'callback' if function['sig_out'] else 'lambda dummy: callback()', - 'debug': "" if not self.args.debug else 'log.debug ("%s")\n%s' % (completion['name'], 8 * ' '), - }) + completion.update( + { + "args_comma": ", " if function["sig_in"] else "", + "args_no_def": self.getArguments(function["sig_in"], name=arg_doc), + "callback": "callback" + if function["sig_out"] + else "lambda dummy: callback()", + "debug": "" + if not self.args.debug + else 'log.debug ("%s")\n%s' % (completion["name"], 8 * " "), + } + )
--- a/sat/bridge/bridge_constructor/constructors/pb/pb_core_template.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/bridge/bridge_constructor/constructors/pb/pb_core_template.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SAT: a jabber client # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) @@ -19,6 +19,7 @@ from sat.core.log import getLogger + log = getLogger(__name__) from twisted.spread import jelly, pb from twisted.internet import reactor @@ -28,17 +29,18 @@ # we monkey patch jelly to handle namedtuple ori_jelly = jelly._Jellier.jelly + def fixed_jelly(self, obj): """this method fix handling of namedtuple""" if isinstance(obj, tuple) and not obj is tuple: obj = tuple(obj) return ori_jelly(self, obj) + jelly._Jellier.jelly = fixed_jelly class PBRoot(pb.Root): - def __init__(self): self.signals_handlers = [] @@ -47,10 +49,11 @@ log.info(u"registered signal handler") def sendSignalEb(self, failure, signal_name): - log.error(u"Error while sending signal {name}: {msg}".format( - name = signal_name, - msg = failure, - )) + log.error( + u"Error while sending signal {name}: {msg}".format( + name=signal_name, msg=failure + ) + ) def sendSignal(self, name, args, kwargs): to_remove = [] @@ -66,11 +69,11 @@ log.debug(u"Removing signal handler for dead frontend") self.signals_handlers.remove(handler) + ##METHODS_PART## class Bridge(object): - def __init__(self): log.info("Init Perspective Broker...") self.root = PBRoot() @@ -85,17 +88,20 @@ def register_method(self, name, callback): log.debug("registering PB bridge method [%s]" % name) - setattr(self.root, "remote_"+name, callback) - # self.root.register_method(name, callback) + setattr(self.root, "remote_" + name, callback) + # self.root.register_method(name, callback) def addMethod(self, name, int_suffix, in_sign, out_sign, method, async=False, doc={}): """Dynamically add a method to PB Bridge""" - #FIXME: doc parameter is kept only temporary, the time to remove it from calls + # FIXME: doc parameter is kept only temporary, the time to remove it from calls log.debug("Adding method {name} to PB bridge".format(name=name)) self.register_method(name, method) def addSignal(self, name, int_suffix, signature, doc={}): log.debug("Adding signal {name} to PB bridge".format(name=name)) - setattr(self, name, lambda *args, **kwargs: self.sendSignal(name, *args, **kwargs)) + setattr( + self, name, lambda *args, **kwargs: self.sendSignal(name, *args, **kwargs) + ) + ##SIGNALS_PART##
--- a/sat/bridge/bridge_constructor/constructors/pb/pb_frontend_template.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/bridge/bridge_constructor/constructors/pb/pb_frontend_template.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SAT communication bridge # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) @@ -18,6 +18,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from twisted.spread import pb @@ -25,11 +26,9 @@ class SignalsHandler(pb.Referenceable): - def __getattr__(self, name): if name.startswith("remote_"): - log.debug(u"calling an unregistered signal: {name}".format( - name = name[7:])) + log.debug(u"calling an unregistered signal: {name}".format(name=name[7:])) return lambda *args, **kwargs: None else: @@ -43,13 +42,15 @@ except AttributeError: pass else: - raise exceptions.InternalError(u"{name} signal handler has been registered twice".format( - name = method_name)) + raise exceptions.InternalError( + u"{name} signal handler has been registered twice".format( + name=method_name + ) + ) setattr(self, method_name, handler) class Bridge(object): - def __init__(self): self.signals_handler = SignalsHandler() @@ -81,11 +82,11 @@ callback = errback = None if kwargs: try: - callback = kwargs.pop('callback') + callback = kwargs.pop("callback") except KeyError: pass try: - errback = kwargs.pop('errback') + errback = kwargs.pop("errback") except KeyError: pass elif len(args) >= 2 and callable(args[-1]) and callable(args[-2]): @@ -124,4 +125,5 @@ def register_signal(self, functionName, handler, iface="core"): self.signals_handler.register_signal(functionName, handler, iface) + ##METHODS_PART##
--- a/sat/bridge/dbus_bridge.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/bridge/dbus_bridge.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SAT: a jabber client # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) @@ -23,13 +23,14 @@ import dbus.mainloop.glib import inspect from sat.core.log import getLogger + log = getLogger(__name__) from twisted.internet.defer import Deferred from sat.core.exceptions import BridgeInitError const_INT_PREFIX = "org.goffi.SAT" # Interface prefix const_ERROR_PREFIX = const_INT_PREFIX + ".error" -const_OBJ_PATH = '/org/goffi/SAT/bridge' +const_OBJ_PATH = "/org/goffi/SAT/bridge" const_CORE_SUFFIX = ".core" const_PLUGIN_SUFFIX = ".plugin" @@ -73,11 +74,12 @@ self.args = (message, twisted_error.value.condition) except AttributeError: self.args = (message,) - self._dbus_error_name = '.'.join([const_ERROR_PREFIX, class_.__module__, class_.__name__]) + self._dbus_error_name = ".".join( + [const_ERROR_PREFIX, class_.__module__, class_.__name__] + ) class DbusObject(dbus.service.Object): - def __init__(self, bus, path): dbus.service.Object.__init__(self, bus, path) log.debug("Init DbusObject...") @@ -93,7 +95,7 @@ raise MethodNotRegistered if "callback" in kwargs: - #we must have errback too + # we must have errback too if not "errback" in kwargs: log.error("errback is missing in method call [%s]" % name) raise InternalError @@ -107,389 +109,798 @@ if not isinstance(result, Deferred): log.error("Asynchronous method [%s] does not return a Deferred." % name) raise AsyncNotDeferred - result.addCallback(lambda result: callback() if result is None else callback(result)) + result.addCallback( + lambda result: callback() if result is None else callback(result) + ) result.addErrback(lambda err: errback(GenericException(err))) else: if isinstance(result, Deferred): log.error("Synchronous method [%s] return a Deferred." % name) raise DeferredNotAsync return result + ### signals ### - @dbus.service.signal(const_INT_PREFIX + const_PLUGIN_SUFFIX, - signature='') + @dbus.service.signal(const_INT_PREFIX + const_PLUGIN_SUFFIX, signature="") def dummySignal(self): - #FIXME: workaround for addSignal (doesn't work if one method doensn't + # FIXME: workaround for addSignal (doesn't work if one method doensn't # already exist for plugins), probably missing some initialisation, need # further investigations pass - @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX, - signature='a{ss}sis') + @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="a{ss}sis") def actionNew(self, action_data, id, security_limit, profile): pass - @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX, - signature='ss') + @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="ss") def connected(self, profile, jid_s): pass - @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX, - signature='ss') + @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="ss") def contactDeleted(self, entity_jid, profile): pass - @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX, - signature='s') + @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="s") def disconnected(self, profile): pass - @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX, - signature='ssss') + @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="ssss") def entityDataUpdated(self, jid, name, value, profile): pass - @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX, - signature='sdssa{ss}a{ss}sa{ss}s') - def messageNew(self, uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile): + @dbus.service.signal( + const_INT_PREFIX + const_CORE_SUFFIX, signature="sdssa{ss}a{ss}sa{ss}s" + ) + def messageNew( + self, + uid, + timestamp, + from_jid, + to_jid, + message, + subject, + mess_type, + extra, + profile, + ): pass - @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX, - signature='sa{ss}ass') + @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="sa{ss}ass") def newContact(self, contact_jid, attributes, groups, profile): pass - @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX, - signature='ssss') + @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="ssss") def paramUpdate(self, name, value, category, profile): pass - @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX, - signature='ssia{ss}s') + @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="ssia{ss}s") def presenceUpdate(self, entity_jid, show, priority, statuses, profile): pass - @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX, - signature='sss') + @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="sss") def progressError(self, id, error, profile): pass - @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX, - signature='sa{ss}s') + @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="sa{ss}s") def progressFinished(self, id, metadata, profile): pass - @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX, - signature='sa{ss}s') + @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="sa{ss}s") def progressStarted(self, id, metadata, profile): pass - @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX, - signature='sss') + @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="sss") def subscribe(self, sub_type, entity_jid, profile): pass ### methods ### - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='s', out_signature='a(a{ss}si)', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="s", + out_signature="a(a{ss}si)", + async_callbacks=None, + ) def actionsGet(self, profile_key="@DEFAULT@"): return self._callback("actionsGet", unicode(profile_key)) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='ss', out_signature='', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="ss", + out_signature="", + async_callbacks=None, + ) def addContact(self, entity_jid, profile_key="@DEFAULT@"): return self._callback("addContact", unicode(entity_jid), unicode(profile_key)) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='s', out_signature='', - async_callbacks=('callback', 'errback')) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="s", + out_signature="", + async_callbacks=("callback", "errback"), + ) def asyncDeleteProfile(self, profile, callback=None, errback=None): - return self._callback("asyncDeleteProfile", unicode(profile), callback=callback, errback=errback) + return self._callback( + "asyncDeleteProfile", unicode(profile), callback=callback, errback=errback + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='sssis', out_signature='s', - async_callbacks=('callback', 'errback')) - def asyncGetParamA(self, name, category, attribute="value", security_limit=-1, profile_key="@DEFAULT@", callback=None, errback=None): - return self._callback("asyncGetParamA", unicode(name), unicode(category), unicode(attribute), security_limit, unicode(profile_key), callback=callback, errback=errback) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="sssis", + out_signature="s", + async_callbacks=("callback", "errback"), + ) + def asyncGetParamA( + self, + name, + category, + attribute="value", + security_limit=-1, + profile_key="@DEFAULT@", + callback=None, + errback=None, + ): + return self._callback( + "asyncGetParamA", + unicode(name), + unicode(category), + unicode(attribute), + security_limit, + unicode(profile_key), + callback=callback, + errback=errback, + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='sis', out_signature='a{ss}', - async_callbacks=('callback', 'errback')) - def asyncGetParamsValuesFromCategory(self, category, security_limit=-1, profile_key="@DEFAULT@", callback=None, errback=None): - return self._callback("asyncGetParamsValuesFromCategory", unicode(category), security_limit, unicode(profile_key), callback=callback, errback=errback) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="sis", + out_signature="a{ss}", + async_callbacks=("callback", "errback"), + ) + def asyncGetParamsValuesFromCategory( + self, + category, + security_limit=-1, + profile_key="@DEFAULT@", + callback=None, + errback=None, + ): + return self._callback( + "asyncGetParamsValuesFromCategory", + unicode(category), + security_limit, + unicode(profile_key), + callback=callback, + errback=errback, + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='ssa{ss}', out_signature='b', - async_callbacks=('callback', 'errback')) - def connect(self, profile_key="@DEFAULT@", password='', options={}, callback=None, errback=None): - return self._callback("connect", unicode(profile_key), unicode(password), options, callback=callback, errback=errback) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="ssa{ss}", + out_signature="b", + async_callbacks=("callback", "errback"), + ) + def connect( + self, + profile_key="@DEFAULT@", + password="", + options={}, + callback=None, + errback=None, + ): + return self._callback( + "connect", + unicode(profile_key), + unicode(password), + options, + callback=callback, + errback=errback, + ) + + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="ss", + out_signature="", + async_callbacks=("callback", "errback"), + ) + def delContact( + self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None + ): + return self._callback( + "delContact", + unicode(entity_jid), + unicode(profile_key), + callback=callback, + errback=errback, + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='ss', out_signature='', - async_callbacks=('callback', 'errback')) - def delContact(self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None): - return self._callback("delContact", unicode(entity_jid), unicode(profile_key), callback=callback, errback=errback) - - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='asa(ss)bbbbbs', out_signature='(a{sa(sss)}a{sa(sss)}a{sa(sss)})', - async_callbacks=('callback', 'errback')) - def discoFindByFeatures(self, namespaces, identities, bare_jid=False, service=True, roster=True, own_jid=True, local_device=False, profile_key=u"@DEFAULT@", callback=None, errback=None): - return self._callback("discoFindByFeatures", namespaces, identities, bare_jid, service, roster, own_jid, local_device, unicode(profile_key), callback=callback, errback=errback) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="asa(ss)bbbbbs", + out_signature="(a{sa(sss)}a{sa(sss)}a{sa(sss)})", + async_callbacks=("callback", "errback"), + ) + def discoFindByFeatures( + self, + namespaces, + identities, + bare_jid=False, + service=True, + roster=True, + own_jid=True, + local_device=False, + profile_key=u"@DEFAULT@", + callback=None, + errback=None, + ): + return self._callback( + "discoFindByFeatures", + namespaces, + identities, + bare_jid, + service, + roster, + own_jid, + local_device, + unicode(profile_key), + callback=callback, + errback=errback, + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='ssbs', out_signature='(asa(sss)a{sa(a{ss}as)})', - async_callbacks=('callback', 'errback')) - def discoInfos(self, entity_jid, node=u'', use_cache=True, profile_key=u"@DEFAULT@", callback=None, errback=None): - return self._callback("discoInfos", unicode(entity_jid), unicode(node), use_cache, unicode(profile_key), callback=callback, errback=errback) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="ssbs", + out_signature="(asa(sss)a{sa(a{ss}as)})", + async_callbacks=("callback", "errback"), + ) + def discoInfos( + self, + entity_jid, + node=u"", + use_cache=True, + profile_key=u"@DEFAULT@", + callback=None, + errback=None, + ): + return self._callback( + "discoInfos", + unicode(entity_jid), + unicode(node), + use_cache, + unicode(profile_key), + callback=callback, + errback=errback, + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='ssbs', out_signature='a(sss)', - async_callbacks=('callback', 'errback')) - def discoItems(self, entity_jid, node=u'', use_cache=True, profile_key=u"@DEFAULT@", callback=None, errback=None): - return self._callback("discoItems", unicode(entity_jid), unicode(node), use_cache, unicode(profile_key), callback=callback, errback=errback) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="ssbs", + out_signature="a(sss)", + async_callbacks=("callback", "errback"), + ) + def discoItems( + self, + entity_jid, + node=u"", + use_cache=True, + profile_key=u"@DEFAULT@", + callback=None, + errback=None, + ): + return self._callback( + "discoItems", + unicode(entity_jid), + unicode(node), + use_cache, + unicode(profile_key), + callback=callback, + errback=errback, + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='s', out_signature='', - async_callbacks=('callback', 'errback')) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="s", + out_signature="", + async_callbacks=("callback", "errback"), + ) def disconnect(self, profile_key="@DEFAULT@", callback=None, errback=None): - return self._callback("disconnect", unicode(profile_key), callback=callback, errback=errback) + return self._callback( + "disconnect", unicode(profile_key), callback=callback, errback=errback + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='ss', out_signature='s', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="ss", + out_signature="s", + async_callbacks=None, + ) def getConfig(self, section, name): return self._callback("getConfig", unicode(section), unicode(name)) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='s', out_signature='a(sa{ss}as)', - async_callbacks=('callback', 'errback')) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="s", + out_signature="a(sa{ss}as)", + async_callbacks=("callback", "errback"), + ) def getContacts(self, profile_key="@DEFAULT@", callback=None, errback=None): - return self._callback("getContacts", unicode(profile_key), callback=callback, errback=errback) + return self._callback( + "getContacts", unicode(profile_key), callback=callback, errback=errback + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='ss', out_signature='as', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="ss", + out_signature="as", + async_callbacks=None, + ) def getContactsFromGroup(self, group, profile_key="@DEFAULT@"): - return self._callback("getContactsFromGroup", unicode(group), unicode(profile_key)) + return self._callback( + "getContactsFromGroup", unicode(group), unicode(profile_key) + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='asass', out_signature='a{sa{ss}}', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="asass", + out_signature="a{sa{ss}}", + async_callbacks=None, + ) def getEntitiesData(self, jids, keys, profile): return self._callback("getEntitiesData", jids, keys, unicode(profile)) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='sass', out_signature='a{ss}', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="sass", + out_signature="a{ss}", + async_callbacks=None, + ) def getEntityData(self, jid, keys, profile): return self._callback("getEntityData", unicode(jid), keys, unicode(profile)) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='s', out_signature='a{sa{ss}}', - async_callbacks=('callback', 'errback')) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="s", + out_signature="a{sa{ss}}", + async_callbacks=("callback", "errback"), + ) def getFeatures(self, profile_key, callback=None, errback=None): - return self._callback("getFeatures", unicode(profile_key), callback=callback, errback=errback) + return self._callback( + "getFeatures", unicode(profile_key), callback=callback, errback=errback + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='ss', out_signature='s', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="ss", + out_signature="s", + async_callbacks=None, + ) def getMainResource(self, contact_jid, profile_key="@DEFAULT@"): - return self._callback("getMainResource", unicode(contact_jid), unicode(profile_key)) + return self._callback( + "getMainResource", unicode(contact_jid), unicode(profile_key) + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='ssss', out_signature='s', - async_callbacks=None) + @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@"): - return self._callback("getParamA", unicode(name), unicode(category), unicode(attribute), unicode(profile_key)) + return self._callback( + "getParamA", + unicode(name), + unicode(category), + unicode(attribute), + unicode(profile_key), + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='', out_signature='as', - async_callbacks=None) - def getParamsCategories(self, ): - return self._callback("getParamsCategories", ) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="", + out_signature="as", + async_callbacks=None, + ) + def getParamsCategories(self,): + return self._callback("getParamsCategories") - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='iss', out_signature='s', - async_callbacks=('callback', 'errback')) - def getParamsUI(self, security_limit=-1, app='', profile_key="@DEFAULT@", callback=None, errback=None): - return self._callback("getParamsUI", security_limit, unicode(app), unicode(profile_key), callback=callback, errback=errback) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="iss", + out_signature="s", + async_callbacks=("callback", "errback"), + ) + def getParamsUI( + self, + security_limit=-1, + app="", + profile_key="@DEFAULT@", + callback=None, + errback=None, + ): + return self._callback( + "getParamsUI", + security_limit, + unicode(app), + unicode(profile_key), + callback=callback, + errback=errback, + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='s', out_signature='a{sa{s(sia{ss})}}', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="s", + out_signature="a{sa{s(sia{ss})}}", + async_callbacks=None, + ) def getPresenceStatuses(self, profile_key="@DEFAULT@"): return self._callback("getPresenceStatuses", unicode(profile_key)) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='', out_signature='', - async_callbacks=('callback', 'errback')) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="", + out_signature="", + async_callbacks=("callback", "errback"), + ) def getReady(self, callback=None, errback=None): return self._callback("getReady", callback=callback, errback=errback) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='', out_signature='s', - async_callbacks=None) - def getVersion(self, ): - return self._callback("getVersion", ) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="", + out_signature="s", + async_callbacks=None, + ) + def getVersion(self,): + return self._callback("getVersion") - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='s', out_signature='a{ss}', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="s", + out_signature="a{ss}", + async_callbacks=None, + ) def getWaitingSub(self, profile_key="@DEFAULT@"): return self._callback("getWaitingSub", unicode(profile_key)) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='ssiba{ss}s', out_signature='a(sdssa{ss}a{ss}sa{ss})', - async_callbacks=('callback', 'errback')) - def historyGet(self, from_jid, to_jid, limit, between=True, filters='', profile="@NONE@", callback=None, errback=None): - return self._callback("historyGet", unicode(from_jid), unicode(to_jid), limit, between, filters, unicode(profile), callback=callback, errback=errback) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="ssiba{ss}s", + out_signature="a(sdssa{ss}a{ss}sa{ss})", + async_callbacks=("callback", "errback"), + ) + def historyGet( + self, + from_jid, + to_jid, + limit, + between=True, + filters="", + profile="@NONE@", + callback=None, + errback=None, + ): + return self._callback( + "historyGet", + unicode(from_jid), + unicode(to_jid), + limit, + between, + filters, + unicode(profile), + callback=callback, + errback=errback, + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='s', out_signature='b', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="s", + out_signature="b", + async_callbacks=None, + ) def isConnected(self, profile_key="@DEFAULT@"): return self._callback("isConnected", unicode(profile_key)) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='sa{ss}s', out_signature='a{ss}', - async_callbacks=('callback', 'errback')) - def launchAction(self, callback_id, data, profile_key="@DEFAULT@", callback=None, errback=None): - return self._callback("launchAction", unicode(callback_id), data, unicode(profile_key), callback=callback, errback=errback) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="sa{ss}s", + out_signature="a{ss}", + async_callbacks=("callback", "errback"), + ) + def launchAction( + self, callback_id, data, profile_key="@DEFAULT@", callback=None, errback=None + ): + return self._callback( + "launchAction", + unicode(callback_id), + data, + unicode(profile_key), + callback=callback, + errback=errback, + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='s', out_signature='b', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="s", + out_signature="b", + async_callbacks=None, + ) def loadParamsTemplate(self, filename): return self._callback("loadParamsTemplate", unicode(filename)) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='ss', out_signature='s', - async_callbacks=None) + @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="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) + @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): - return self._callback("messageSend", unicode(to_jid), message, subject, unicode(mess_type), extra, unicode(profile_key), callback=callback, errback=errback) - - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='', out_signature='a{ss}', - async_callbacks=None) - def namespacesGet(self, ): - return self._callback("namespacesGet", ) + @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, + ): + return self._callback( + "messageSend", + unicode(to_jid), + message, + subject, + unicode(mess_type), + extra, + unicode(profile_key), + callback=callback, + errback=errback, + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='sis', out_signature='', - async_callbacks=None) - def paramsRegisterApp(self, xml, security_limit=-1, app=''): - return self._callback("paramsRegisterApp", unicode(xml), security_limit, unicode(app)) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="", + out_signature="a{ss}", + async_callbacks=None, + ) + def namespacesGet(self,): + return self._callback("namespacesGet") + + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="sis", + out_signature="", + async_callbacks=None, + ) + def paramsRegisterApp(self, xml, security_limit=-1, app=""): + return self._callback( + "paramsRegisterApp", unicode(xml), security_limit, unicode(app) + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='sss', out_signature='', - async_callbacks=('callback', 'errback')) - def profileCreate(self, profile, password='', component='', callback=None, errback=None): - return self._callback("profileCreate", unicode(profile), unicode(password), unicode(component), callback=callback, errback=errback) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="sss", + out_signature="", + async_callbacks=("callback", "errback"), + ) + def profileCreate( + self, profile, password="", component="", callback=None, errback=None + ): + return self._callback( + "profileCreate", + unicode(profile), + unicode(password), + unicode(component), + callback=callback, + errback=errback, + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='s', out_signature='b', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="s", + out_signature="b", + async_callbacks=None, + ) def profileIsSessionStarted(self, profile_key="@DEFAULT@"): return self._callback("profileIsSessionStarted", unicode(profile_key)) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='s', out_signature='s', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="s", + out_signature="s", + async_callbacks=None, + ) def profileNameGet(self, profile_key="@DEFAULT@"): return self._callback("profileNameGet", unicode(profile_key)) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='s', out_signature='', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="s", + out_signature="", + async_callbacks=None, + ) def profileSetDefault(self, profile): return self._callback("profileSetDefault", unicode(profile)) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='ss', out_signature='b', - async_callbacks=('callback', 'errback')) - def profileStartSession(self, password='', profile_key="@DEFAULT@", callback=None, errback=None): - return self._callback("profileStartSession", unicode(password), unicode(profile_key), callback=callback, errback=errback) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="ss", + out_signature="b", + async_callbacks=("callback", "errback"), + ) + def profileStartSession( + self, password="", profile_key="@DEFAULT@", callback=None, errback=None + ): + return self._callback( + "profileStartSession", + unicode(password), + unicode(profile_key), + callback=callback, + errback=errback, + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='bb', out_signature='as', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="bb", + out_signature="as", + async_callbacks=None, + ) def profilesListGet(self, clients=True, components=False): return self._callback("profilesListGet", clients, components) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='ss', out_signature='a{ss}', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="ss", + out_signature="a{ss}", + async_callbacks=None, + ) def progressGet(self, id, profile): return self._callback("progressGet", unicode(id), unicode(profile)) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='s', out_signature='a{sa{sa{ss}}}', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="s", + out_signature="a{sa{sa{ss}}}", + async_callbacks=None, + ) def progressGetAll(self, profile): return self._callback("progressGetAll", unicode(profile)) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='s', out_signature='a{sa{sa{ss}}}', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="s", + out_signature="a{sa{sa{ss}}}", + async_callbacks=None, + ) def progressGetAllMetadata(self, profile): return self._callback("progressGetAllMetadata", unicode(profile)) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='s', out_signature='b', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="s", + out_signature="b", + async_callbacks=None, + ) def saveParamsTemplate(self, filename): return self._callback("saveParamsTemplate", unicode(filename)) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='s', out_signature='a{ss}', - async_callbacks=('callback', 'errback')) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="s", + out_signature="a{ss}", + async_callbacks=("callback", "errback"), + ) def sessionInfosGet(self, profile_key, callback=None, errback=None): - return self._callback("sessionInfosGet", unicode(profile_key), callback=callback, errback=errback) + return self._callback( + "sessionInfosGet", unicode(profile_key), callback=callback, errback=errback + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='sssis', out_signature='', - async_callbacks=None) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="sssis", + out_signature="", + async_callbacks=None, + ) def setParam(self, name, value, category, security_limit=-1, profile_key="@DEFAULT@"): - return self._callback("setParam", unicode(name), unicode(value), unicode(category), security_limit, unicode(profile_key)) + return self._callback( + "setParam", + unicode(name), + unicode(value), + unicode(category), + security_limit, + unicode(profile_key), + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='ssa{ss}s', out_signature='', - async_callbacks=None) - def setPresence(self, to_jid='', show='', statuses={}, profile_key="@DEFAULT@"): - return self._callback("setPresence", unicode(to_jid), unicode(show), statuses, unicode(profile_key)) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="ssa{ss}s", + out_signature="", + async_callbacks=None, + ) + def setPresence(self, to_jid="", show="", statuses={}, profile_key="@DEFAULT@"): + return self._callback( + "setPresence", unicode(to_jid), unicode(show), statuses, unicode(profile_key) + ) + + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="sss", + out_signature="", + async_callbacks=None, + ) + def subscription(self, sub_type, entity, profile_key="@DEFAULT@"): + return self._callback( + "subscription", unicode(sub_type), unicode(entity), unicode(profile_key) + ) - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='sss', out_signature='', - async_callbacks=None) - def subscription(self, sub_type, entity, profile_key="@DEFAULT@"): - return self._callback("subscription", unicode(sub_type), unicode(entity), unicode(profile_key)) - - @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX, - in_signature='ssass', out_signature='', - async_callbacks=('callback', 'errback')) - def updateContact(self, entity_jid, name, groups, profile_key="@DEFAULT@", callback=None, errback=None): - return self._callback("updateContact", unicode(entity_jid), unicode(name), groups, unicode(profile_key), callback=callback, errback=errback) + @dbus.service.method( + const_INT_PREFIX + const_CORE_SUFFIX, + in_signature="ssass", + out_signature="", + async_callbacks=("callback", "errback"), + ) + def updateContact( + self, + entity_jid, + name, + groups, + profile_key="@DEFAULT@", + callback=None, + errback=None, + ): + return self._callback( + "updateContact", + unicode(entity_jid), + unicode(name), + groups, + unicode(profile_key), + callback=callback, + errback=errback, + ) def __attributes(self, in_sign): """Return arguments to user given a in_sign @@ -499,22 +910,24 @@ idx = 0 attr = [] while i < len(in_sign): - if in_sign[i] not in ['b', 'y', 'n', 'i', 'x', 'q', 'u', 't', 'd', 's', 'a']: + if in_sign[i] not in ["b", "y", "n", "i", "x", "q", "u", "t", "d", "s", "a"]: raise ParseError("Unmanaged attribute type [%c]" % in_sign[i]) attr.append("arg_%i" % idx) idx += 1 - if in_sign[i] == 'a': + if in_sign[i] == "a": i += 1 - if in_sign[i] != '{' and in_sign[i] != '(': # FIXME: must manage tuples out of arrays + if ( + in_sign[i] != "{" and in_sign[i] != "(" + ): # FIXME: must manage tuples out of arrays i += 1 continue # we have a simple type for the array opening_car = in_sign[i] - assert(opening_car in ['{', '(']) - closing_car = '}' if opening_car == '{' else ')' + assert opening_car in ["{", "("] + closing_car = "}" if opening_car == "{" else ")" opening_count = 1 - while (True): # we have a dict or a list of tuples + while True: # we have a dict or a list of tuples i += 1 if i >= len(in_sign): raise ParseError("missing }") @@ -535,47 +948,80 @@ _defaults = list(inspect_args.defaults or []) if inspect.ismethod(method): - #if we have a method, we don't want the first argument (usually 'self') - del(_arguments[0]) + # if we have a method, we don't want the first argument (usually 'self') + del (_arguments[0]) - #first arguments are for the _callback method - arguments_callback = ', '.join([repr(name)] + ((_arguments + ['callback=callback', 'errback=errback']) if async else _arguments)) + # first arguments are for the _callback method + arguments_callback = ", ".join( + [repr(name)] + + ( + (_arguments + ["callback=callback", "errback=errback"]) + if async + else _arguments + ) + ) if async: - _arguments.extend(['callback', 'errback']) + _arguments.extend(["callback", "errback"]) _defaults.extend([None, None]) - #now we create a second list with default values + # now we create a second list with default values for i in range(1, len(_defaults) + 1): _arguments[-i] = "%s = %s" % (_arguments[-i], repr(_defaults[-i])) - arguments_defaults = ', '.join(_arguments) + arguments_defaults = ", ".join(_arguments) - code = compile('def %(name)s (self,%(arguments_defaults)s): return self._callback(%(arguments_callback)s)' % - {'name': name, 'arguments_defaults': arguments_defaults, 'arguments_callback': arguments_callback}, '<DBus bridge>', 'exec') - exec (code) # FIXME: to the same thing in a cleaner way, without compile/exec + code = compile( + "def %(name)s (self,%(arguments_defaults)s): return self._callback(%(arguments_callback)s)" + % { + "name": name, + "arguments_defaults": arguments_defaults, + "arguments_callback": arguments_callback, + }, + "<DBus bridge>", + "exec", + ) + exec(code) # FIXME: to the same thing in a cleaner way, without compile/exec method = locals()[name] - async_callbacks = ('callback', 'errback') if async else None - setattr(DbusObject, name, dbus.service.method( - const_INT_PREFIX + int_suffix, in_signature=in_sign, out_signature=out_sign, - async_callbacks=async_callbacks)(method)) + async_callbacks = ("callback", "errback") if async else None + setattr( + DbusObject, + name, + dbus.service.method( + const_INT_PREFIX + int_suffix, + in_signature=in_sign, + out_signature=out_sign, + async_callbacks=async_callbacks, + )(method), + ) function = getattr(self, name) - func_table = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__][function._dbus_interface] + func_table = self._dbus_class_table[ + self.__class__.__module__ + "." + self.__class__.__name__ + ][function._dbus_interface] func_table[function.__name__] = function # Needed for introspection def addSignal(self, name, int_suffix, signature, doc={}): """Dynamically add a signal to Dbus Bridge""" - attributes = ', '.join(self.__attributes(signature)) - #TODO: use doc parameter to name attributes + attributes = ", ".join(self.__attributes(signature)) + # TODO: use doc parameter to name attributes - #code = compile ('def '+name+' (self,'+attributes+'): log.debug ("'+name+' signal")', '<DBus bridge>','exec') #XXX: the log.debug is too annoying with xmllog - code = compile('def ' + name + ' (self,' + attributes + '): pass', '<DBus bridge>', 'exec') - exec (code) + # code = compile ('def '+name+' (self,'+attributes+'): log.debug ("'+name+' signal")', '<DBus bridge>','exec') #XXX: the log.debug is too annoying with xmllog + code = compile( + "def " + name + " (self," + attributes + "): pass", "<DBus bridge>", "exec" + ) + exec(code) signal = locals()[name] - setattr(DbusObject, name, dbus.service.signal( - const_INT_PREFIX + int_suffix, signature=signature)(signal)) + setattr( + DbusObject, + name, + dbus.service.signal(const_INT_PREFIX + int_suffix, signature=signature)( + signal + ), + ) function = getattr(self, name) - func_table = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__][function._dbus_interface] + func_table = self._dbus_class_table[ + self.__class__.__module__ + "." + self.__class__.__name__ + ][function._dbus_interface] func_table[function.__name__] = function # Needed for introspection @@ -586,8 +1032,12 @@ try: self.session_bus = dbus.SessionBus() except dbus.DBusException as e: - if e._dbus_error_name == 'org.freedesktop.DBus.Error.NotSupported': - log.error(_(u"D-Bus is not launched, please see README to see instructions on how to launch it")) + if e._dbus_error_name == "org.freedesktop.DBus.Error.NotSupported": + log.error( + _( + u"D-Bus is not launched, please see README to see instructions on how to launch it" + ) + ) raise BridgeInitError self.dbus_name = dbus.service.BusName(const_INT_PREFIX, self.session_bus) self.dbus_bridge = DbusObject(self.session_bus, const_OBJ_PATH) @@ -607,8 +1057,21 @@ def entityDataUpdated(self, jid, name, value, profile): self.dbus_bridge.entityDataUpdated(jid, name, value, profile) - def messageNew(self, uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile): - self.dbus_bridge.messageNew(uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile) + def messageNew( + self, + uid, + timestamp, + from_jid, + to_jid, + message, + subject, + mess_type, + extra, + profile, + ): + self.dbus_bridge.messageNew( + uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile + ) def newContact(self, contact_jid, attributes, groups, profile): self.dbus_bridge.newContact(contact_jid, attributes, groups, profile) @@ -637,11 +1100,11 @@ def addMethod(self, name, int_suffix, in_sign, out_sign, method, async=False, doc={}): """Dynamically add a method to Dbus Bridge""" - #FIXME: doc parameter is kept only temporary, the time to remove it from calls + # FIXME: doc parameter is kept only temporary, the time to remove it from calls log.debug("Adding method [%s] to DBus bridge" % name) self.dbus_bridge.addMethod(name, int_suffix, in_sign, out_sign, method, async) self.register_method(name, method) def addSignal(self, name, int_suffix, signature, doc={}): self.dbus_bridge.addSignal(name, int_suffix, signature, doc) - setattr(Bridge, name, getattr(self.dbus_bridge, name)) \ No newline at end of file + setattr(Bridge, name, getattr(self.dbus_bridge, name))
--- a/sat/bridge/pb.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/bridge/pb.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SAT: a jabber client # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) @@ -19,6 +19,7 @@ from sat.core.log import getLogger + log = getLogger(__name__) from twisted.spread import jelly, pb from twisted.internet import reactor @@ -28,17 +29,18 @@ # we monkey patch jelly to handle namedtuple ori_jelly = jelly._Jellier.jelly + def fixed_jelly(self, obj): """this method fix handling of namedtuple""" if isinstance(obj, tuple) and not obj is tuple: obj = tuple(obj) return ori_jelly(self, obj) + jelly._Jellier.jelly = fixed_jelly class PBRoot(pb.Root): - def __init__(self): self.signals_handlers = [] @@ -47,10 +49,11 @@ log.info(u"registered signal handler") def sendSignalEb(self, failure, signal_name): - log.error(u"Error while sending signal {name}: {msg}".format( - name = signal_name, - msg = failure, - )) + log.error( + u"Error while sending signal {name}: {msg}".format( + name=signal_name, msg=failure + ) + ) def sendSignal(self, name, args, kwargs): to_remove = [] @@ -66,11 +69,11 @@ log.debug(u"Removing signal handler for dead frontend") self.signals_handlers.remove(handler) + ##METHODS_PART## class Bridge(object): - def __init__(self): log.info("Init Perspective Broker...") self.root = PBRoot() @@ -85,18 +88,20 @@ def register_method(self, name, callback): log.debug("registering PB bridge method [%s]" % name) - setattr(self.root, "remote_"+name, callback) - # self.root.register_method(name, callback) + setattr(self.root, "remote_" + name, callback) + # self.root.register_method(name, callback) def addMethod(self, name, int_suffix, in_sign, out_sign, method, async=False, doc={}): """Dynamically add a method to PB Bridge""" - #FIXME: doc parameter is kept only temporary, the time to remove it from calls + # FIXME: doc parameter is kept only temporary, the time to remove it from calls log.debug("Adding method {name} to PB bridge".format(name=name)) self.register_method(name, method) def addSignal(self, name, int_suffix, signature, doc={}): log.debug("Adding signal {name} to PB bridge".format(name=name)) - setattr(self, name, lambda *args, **kwargs: self.sendSignal(name, *args, **kwargs)) + setattr( + self, name, lambda *args, **kwargs: self.sendSignal(name, *args, **kwargs) + ) def actionNew(self, action_data, id, security_limit, profile): self.sendSignal("actionNew", action_data, id, security_limit, profile) @@ -113,8 +118,30 @@ def entityDataUpdated(self, jid, name, value, profile): self.sendSignal("entityDataUpdated", jid, name, value, profile) - def messageNew(self, uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile): - self.sendSignal("messageNew", uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile) + def messageNew( + self, + uid, + timestamp, + from_jid, + to_jid, + message, + subject, + mess_type, + extra, + profile, + ): + self.sendSignal( + "messageNew", + uid, + timestamp, + from_jid, + to_jid, + message, + subject, + mess_type, + extra, + profile, + ) def newContact(self, contact_jid, attributes, groups, profile): self.sendSignal("newContact", contact_jid, attributes, groups, profile)
--- a/sat/core/constants.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/core/constants.py Wed Jun 27 20:14:46 2018 +0200 @@ -28,23 +28,24 @@ class Const(object): ## Application ## - APP_NAME = u'Salut à Toi' - APP_NAME_SHORT = u'SàT' - APP_NAME_FILE = u'sat' - APP_NAME_FULL = u'{name_short} ({name})'.format(name_short=APP_NAME_SHORT, - name=APP_NAME) - APP_VERSION = sat.__version__ # Please add 'D' at the end of version in sat/VERSION for dev versions - APP_RELEASE_NAME = u'La Commune' - APP_URL = u'https://salut-a-toi.org' - + APP_NAME = u"Salut à Toi" + APP_NAME_SHORT = u"SàT" + APP_NAME_FILE = u"sat" + APP_NAME_FULL = u"{name_short} ({name})".format( + name_short=APP_NAME_SHORT, name=APP_NAME + ) + APP_VERSION = ( + sat.__version__ + ) # Please add 'D' at the end of version in sat/VERSION for dev versions + APP_RELEASE_NAME = u"La Commune" + APP_URL = u"https://salut-a-toi.org" ## Runtime ## PLUGIN_EXT = "py" - HISTORY_SKIP = u'skip' + HISTORY_SKIP = u"skip" ## Main config ## - DEFAULT_BRIDGE = 'dbus' - + DEFAULT_BRIDGE = "dbus" ## Protocol ## XMPP_C2S_PORT = 5222 @@ -53,9 +54,8 @@ # default port used on Prosody, may differ on other servers XMPP_COMPONENT_PORT = 5347 - ## Parameters ## - NO_SECURITY_LIMIT = -1 # FIXME: to rename + NO_SECURITY_LIMIT = -1 # FIXME: to rename SECURITY_LIMIT_MAX = 0 INDIVIDUAL = "individual" GENERAL = "general" @@ -67,9 +67,9 @@ FORCE_SERVER_PARAM = "Force server" FORCE_PORT_PARAM = "Force port" # Parameters related to encryption - PROFILE_PASS_PATH = ('General', 'Password') - MEMORY_CRYPTO_NAMESPACE = 'crypto' # for the private persistent binary dict - MEMORY_CRYPTO_KEY = 'personal_key' + PROFILE_PASS_PATH = ("General", "Password") + 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" @@ -78,7 +78,6 @@ STATIC_BLOG_PARAM_KEYWORDS = "Keywords" STATIC_BLOG_PARAM_DESCRIPTION = "Description" - ## Menus ## MENU_GLOBAL = "GLOBAL" MENU_ROOM = "ROOM" @@ -88,62 +87,64 @@ MENU_ROSTER_GROUP_CONTEXT = "MENU_ROSTER_GROUP_CONTEXT" MENU_ROOM_OCCUPANT_CONTEXT = "MENU_ROOM_OCCUPANT_CONTEXT" - ## Profile and entities ## - PROF_KEY_NONE = '@NONE@' - PROF_KEY_DEFAULT = '@DEFAULT@' - PROF_KEY_ALL = '@ALL@' - ENTITY_ALL = '@ALL@' - ENTITY_ALL_RESOURCES = '@ALL_RESOURCES@' - ENTITY_MAIN_RESOURCE = '@MAIN_RESOURCE@' - ENTITY_CAP_HASH = 'CAP_HASH' - ENTITY_TYPE = 'TYPE' - + PROF_KEY_NONE = "@NONE@" + PROF_KEY_DEFAULT = "@DEFAULT@" + PROF_KEY_ALL = "@ALL@" + ENTITY_ALL = "@ALL@" + ENTITY_ALL_RESOURCES = "@ALL_RESOURCES@" + ENTITY_MAIN_RESOURCE = "@MAIN_RESOURCE@" + ENTITY_CAP_HASH = "CAP_HASH" + ENTITY_TYPE = "TYPE" ## Roster jids selection ## - PUBLIC = 'PUBLIC' - ALL = 'ALL' # ALL means all known contacts, while PUBLIC means everybody, known or not - GROUP = 'GROUP' - JID = 'JID' - + PUBLIC = "PUBLIC" + ALL = ( + "ALL" + ) # ALL means all known contacts, while PUBLIC means everybody, known or not + GROUP = "GROUP" + JID = "JID" ## Messages ## - MESS_TYPE_INFO = 'info' - MESS_TYPE_CHAT = 'chat' - MESS_TYPE_ERROR = 'error' - MESS_TYPE_GROUPCHAT = 'groupchat' - MESS_TYPE_HEADLINE = 'headline' - MESS_TYPE_NORMAL = 'normal' - MESS_TYPE_AUTO = 'auto' # magic value to let the backend guess the type - MESS_TYPE_STANDARD = (MESS_TYPE_CHAT, MESS_TYPE_ERROR, MESS_TYPE_GROUPCHAT, MESS_TYPE_HEADLINE, MESS_TYPE_NORMAL) + MESS_TYPE_INFO = "info" + MESS_TYPE_CHAT = "chat" + MESS_TYPE_ERROR = "error" + MESS_TYPE_GROUPCHAT = "groupchat" + MESS_TYPE_HEADLINE = "headline" + MESS_TYPE_NORMAL = "normal" + MESS_TYPE_AUTO = "auto" # magic value to let the backend guess the type + MESS_TYPE_STANDARD = ( + MESS_TYPE_CHAT, + MESS_TYPE_ERROR, + MESS_TYPE_GROUPCHAT, + MESS_TYPE_HEADLINE, + MESS_TYPE_NORMAL, + ) MESS_TYPE_ALL = MESS_TYPE_STANDARD + (MESS_TYPE_INFO, MESS_TYPE_AUTO) MESS_EXTRA_INFO = "info_type" - ## Chat ## - CHAT_ONE2ONE = 'one2one' - CHAT_GROUP = 'group' - + CHAT_ONE2ONE = "one2one" + CHAT_GROUP = "group" ## Presence ## - PRESENCE_UNAVAILABLE = 'unavailable' - PRESENCE_SHOW_AWAY = 'away' - PRESENCE_SHOW_CHAT = 'chat' - PRESENCE_SHOW_DND = 'dnd' - PRESENCE_SHOW_XA = 'xa' - PRESENCE_SHOW = 'show' - PRESENCE_STATUSES = 'statuses' - PRESENCE_STATUSES_DEFAULT = 'default' - PRESENCE_PRIORITY = 'priority' - + PRESENCE_UNAVAILABLE = "unavailable" + PRESENCE_SHOW_AWAY = "away" + PRESENCE_SHOW_CHAT = "chat" + PRESENCE_SHOW_DND = "dnd" + PRESENCE_SHOW_XA = "xa" + PRESENCE_SHOW = "show" + PRESENCE_STATUSES = "statuses" + PRESENCE_STATUSES_DEFAULT = "default" + PRESENCE_PRIORITY = "priority" ## Common namespaces ## - NS_XML = 'http://www.w3.org/XML/1998/namespace' - NS_CLIENT = 'jabber:client' - NS_FORWARD = 'urn:xmpp:forward:0' - NS_DELAY = 'urn:xmpp:delay' - NS_XHTML = 'http://www.w3.org/1999/xhtml' + NS_XML = "http://www.w3.org/XML/1998/namespace" + NS_CLIENT = "jabber:client" + NS_FORWARD = "urn:xmpp:forward:0" + NS_DELAY = "urn:xmpp:delay" + NS_XHTML = "http://www.w3.org/1999/xhtml" ## Common XPath ## @@ -153,23 +154,24 @@ ## Directories ## # directory for components specific data - COMPONENTS_DIR = u'components' - CACHE_DIR = u'cache' + COMPONENTS_DIR = u"components" + CACHE_DIR = u"cache" # files in file dir are stored for long term # files dir is global, i.e. for all profiles - FILES_DIR = u'files' + FILES_DIR = u"files" # FILES_LINKS_DIR is a directory where files owned by a specific profile # are linked to the global files directory. This way the directory can be - # shared per profiles while keeping global directory where identical files + # shared per profiles while keeping global directory where identical files # shared between different profiles are not duplicated. - FILES_LINKS_DIR = u'files_links' + FILES_LINKS_DIR = u"files_links" # FILES_TMP_DIR is where profile's partially transfered files are put. # Once transfer is completed, they are moved to FILES_DIR - FILES_TMP_DIR = u'files_tmp' - + FILES_TMP_DIR = u"files_tmp" ## Configuration ## - if BaseDirectory: # skipped when xdg module is not available (should not happen in backend) + if ( + BaseDirectory + ): # skipped when xdg module is not available (should not happen in backend) if "org.goffi.cagou.cagou" in BaseDirectory.__file__: # FIXME: hack to make config read from the right location on Android # TODO: fix it in a more proper way @@ -177,56 +179,71 @@ # we need to use Android API to get downloads directory import os.path from jnius import autoclass + Environment = autoclass("android.os.Environment") BaseDirectory = None DEFAULT_CONFIG = { - 'local_dir': '/data/data/org.goffi.cagou.cagou/app', - 'media_dir': '/data/data/org.goffi.cagou.cagou/files/app/media', + "local_dir": "/data/data/org.goffi.cagou.cagou/app", + "media_dir": "/data/data/org.goffi.cagou.cagou/files/app/media", # FIXME: temporary location for downloads, need to call API properly - 'downloads_dir': os.path.join(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(), APP_NAME_FILE), - 'pid_dir': '%(local_dir)s', - 'log_dir': '%(local_dir)s', + "downloads_dir": os.path.join( + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS + ).getAbsolutePath(), + APP_NAME_FILE, + ), + "pid_dir": "%(local_dir)s", + "log_dir": "%(local_dir)s", } - CONFIG_FILES = ['/data/data/org.goffi.cagou.cagou/files/app/android/' + APP_NAME_FILE + '.conf'] + CONFIG_FILES = [ + "/data/data/org.goffi.cagou.cagou/files/app/android/" + + APP_NAME_FILE + + ".conf" + ] else: ## Configuration ## DEFAULT_CONFIG = { - 'media_dir': '/usr/share/' + APP_NAME_FILE + '/media', - 'local_dir': BaseDirectory.save_data_path(APP_NAME_FILE), - 'downloads_dir': '~/Downloads/' + APP_NAME_FILE, - 'pid_dir': '%(local_dir)s', - 'log_dir': '%(local_dir)s', + "media_dir": "/usr/share/" + APP_NAME_FILE + "/media", + "local_dir": BaseDirectory.save_data_path(APP_NAME_FILE), + "downloads_dir": "~/Downloads/" + APP_NAME_FILE, + "pid_dir": "%(local_dir)s", + "log_dir": "%(local_dir)s", } # List of the configuration filenames sorted by ascending priority - CONFIG_FILES = [realpath(expanduser(path) + APP_NAME_FILE + '.conf') for path in - ['/etc/', '~/', '~/.', '', '.'] + - ['%s/' % path for path in list(BaseDirectory.load_config_paths(APP_NAME_FILE))] - ] + CONFIG_FILES = [ + realpath(expanduser(path) + APP_NAME_FILE + ".conf") + for path in ["/etc/", "~/", "~/.", "", "."] + + [ + "%s/" % path + for path in list(BaseDirectory.load_config_paths(APP_NAME_FILE)) + ] + ] ## Templates ## - TEMPLATE_THEME_DEFAULT = u'default' - TEMPLATE_STATIC_DIR = u'static' - + TEMPLATE_THEME_DEFAULT = u"default" + TEMPLATE_STATIC_DIR = u"static" ## Plugins ## # PLUGIN_INFO keys # XXX: we use PI instead of PLUG_INFO which would normally be used # to make the header more readable - PI_NAME = u'name' - PI_IMPORT_NAME = u'import_name' - PI_MAIN = u'main' - PI_HANDLER = u'handler' - PI_TYPE = u'type' # FIXME: should be types, and should handle single unicode type or tuple of types (e.g. "blog" and "import") - PI_MODES = u'modes' - PI_PROTOCOLS = u'protocols' - PI_DEPENDENCIES = u'dependencies' - PI_RECOMMENDATIONS = u'recommendations' - PI_DESCRIPTION = u'description' - PI_USAGE = u'usage' + PI_NAME = u"name" + PI_IMPORT_NAME = u"import_name" + PI_MAIN = u"main" + PI_HANDLER = u"handler" + PI_TYPE = ( + u"type" + ) # FIXME: should be types, and should handle single unicode type or tuple of types (e.g. "blog" and "import") + PI_MODES = u"modes" + PI_PROTOCOLS = u"protocols" + PI_DEPENDENCIES = u"dependencies" + PI_RECOMMENDATIONS = u"recommendations" + PI_DESCRIPTION = u"description" + PI_USAGE = u"usage" # Types PLUG_TYPE_XEP = "XEP" @@ -245,7 +262,7 @@ PLUG_MODE_BOTH = (PLUG_MODE_CLIENT, PLUG_MODE_COMPONENT) # names of widely used plugins - TEXT_CMDS = 'TEXT-COMMANDS' + TEXT_CMDS = "TEXT-COMMANDS" # PubSub event categories PS_PEP = "PEP" @@ -253,19 +270,18 @@ # PubSub PS_PUBLISH = "publish" - PS_RETRACT = "retract" # used for items - PS_DELETE = "delete" # used for nodes + PS_RETRACT = "retract" # used for items + PS_DELETE = "delete" # used for nodes PS_ITEM = "item" - PS_ITEMS = "items" # Can contain publish and retract items + PS_ITEMS = "items" # Can contain publish and retract items PS_EVENTS = (PS_ITEMS, PS_DELETE) - ## XMLUI ## - XMLUI_WINDOW = 'window' - XMLUI_POPUP = 'popup' - XMLUI_FORM = 'form' - XMLUI_PARAM = 'param' - XMLUI_DIALOG = 'dialog' + XMLUI_WINDOW = "window" + XMLUI_POPUP = "popup" + XMLUI_FORM = "form" + XMLUI_PARAM = "param" + XMLUI_DIALOG = "dialog" XMLUI_DIALOG_CONFIRM = "confirm" XMLUI_DIALOG_MESSAGE = "message" XMLUI_DIALOG_NOTE = "note" @@ -284,108 +300,119 @@ XMLUI_DATA_BTNS_SET_OKCANCEL = "ok/cancel" XMLUI_DATA_BTNS_SET_YESNO = "yes/no" XMLUI_DATA_BTNS_SET_DEFAULT = XMLUI_DATA_BTNS_SET_OKCANCEL - XMLUI_DATA_FILETYPE = 'filetype' + XMLUI_DATA_FILETYPE = "filetype" XMLUI_DATA_FILETYPE_FILE = "file" XMLUI_DATA_FILETYPE_DIR = "dir" XMLUI_DATA_FILETYPE_DEFAULT = XMLUI_DATA_FILETYPE_FILE - ## Logging ## - LOG_LVL_DEBUG = 'DEBUG' - LOG_LVL_INFO = 'INFO' - LOG_LVL_WARNING = 'WARNING' - LOG_LVL_ERROR = 'ERROR' - LOG_LVL_CRITICAL = 'CRITICAL' - LOG_LEVELS = (LOG_LVL_DEBUG, LOG_LVL_INFO, LOG_LVL_WARNING, LOG_LVL_ERROR, LOG_LVL_CRITICAL) - LOG_BACKEND_STANDARD = 'standard' - LOG_BACKEND_TWISTED = 'twisted' - LOG_BACKEND_BASIC = 'basic' - LOG_BACKEND_CUSTOM = 'custom' - LOG_BASE_LOGGER = 'root' - LOG_TWISTED_LOGGER = 'twisted' - LOG_OPT_SECTION = 'DEFAULT' # section of sat.conf where log options should be - LOG_OPT_PREFIX = 'log_' + LOG_LVL_DEBUG = "DEBUG" + LOG_LVL_INFO = "INFO" + LOG_LVL_WARNING = "WARNING" + LOG_LVL_ERROR = "ERROR" + LOG_LVL_CRITICAL = "CRITICAL" + LOG_LEVELS = ( + LOG_LVL_DEBUG, + LOG_LVL_INFO, + LOG_LVL_WARNING, + LOG_LVL_ERROR, + LOG_LVL_CRITICAL, + ) + LOG_BACKEND_STANDARD = "standard" + LOG_BACKEND_TWISTED = "twisted" + LOG_BACKEND_BASIC = "basic" + LOG_BACKEND_CUSTOM = "custom" + LOG_BASE_LOGGER = "root" + LOG_TWISTED_LOGGER = "twisted" + LOG_OPT_SECTION = "DEFAULT" # section of sat.conf where log options should be + LOG_OPT_PREFIX = "log_" # (option_name, default_value) tuples - LOG_OPT_COLORS = ('colors', 'true') # true for auto colors, force to have colors even if stdout is not a tty, false for no color - LOG_OPT_TAINTS_DICT = ('levels_taints_dict', { - LOG_LVL_DEBUG: ('cyan',), - LOG_LVL_INFO: (), - LOG_LVL_WARNING: ('yellow',), - LOG_LVL_ERROR: ('red', 'blink', r'/!\ ', 'blink_off'), - LOG_LVL_CRITICAL: ('bold', 'red', 'Guru Meditation ', 'normal_weight') - }) - LOG_OPT_LEVEL = ('level', 'info') - LOG_OPT_FORMAT = ('fmt', '%(message)s') # similar to logging format. - LOG_OPT_LOGGER = ('logger', '') # regex to filter logger name - LOG_OPT_OUTPUT_SEP = '//' - LOG_OPT_OUTPUT_DEFAULT = 'default' - LOG_OPT_OUTPUT_MEMORY = 'memory' + LOG_OPT_COLORS = ( + "colors", + "true", + ) # true for auto colors, force to have colors even if stdout is not a tty, false for no color + LOG_OPT_TAINTS_DICT = ( + "levels_taints_dict", + { + LOG_LVL_DEBUG: ("cyan",), + LOG_LVL_INFO: (), + LOG_LVL_WARNING: ("yellow",), + LOG_LVL_ERROR: ("red", "blink", r"/!\ ", "blink_off"), + LOG_LVL_CRITICAL: ("bold", "red", "Guru Meditation ", "normal_weight"), + }, + ) + LOG_OPT_LEVEL = ("level", "info") + LOG_OPT_FORMAT = ("fmt", "%(message)s") # similar to logging format. + LOG_OPT_LOGGER = ("logger", "") # regex to filter logger name + LOG_OPT_OUTPUT_SEP = "//" + LOG_OPT_OUTPUT_DEFAULT = "default" + LOG_OPT_OUTPUT_MEMORY = "memory" LOG_OPT_OUTPUT_MEMORY_LIMIT = 50 - LOG_OPT_OUTPUT_FILE = 'file' # file is implicit if only output - LOG_OPT_OUTPUT = ('output', LOG_OPT_OUTPUT_SEP + LOG_OPT_OUTPUT_DEFAULT) # //default = normal output (stderr or a file with twistd), path/to/file for a file (must be the first if used), //memory for memory (options can be put in parenthesis, e.g.: //memory(500) for a 500 lines memory) - + LOG_OPT_OUTPUT_FILE = "file" # file is implicit if only output + LOG_OPT_OUTPUT = ( + "output", + LOG_OPT_OUTPUT_SEP + LOG_OPT_OUTPUT_DEFAULT, + ) # //default = normal output (stderr or a file with twistd), path/to/file for a file (must be the first if used), //memory for memory (options can be put in parenthesis, e.g.: //memory(500) for a 500 lines memory) ## action constants ## META_TYPE_FILE = "file" META_TYPE_OVERWRITE = "overwrite" - ## HARD-CODED ACTIONS IDS (generated with uuid.uuid4) ## - AUTHENTICATE_PROFILE_ID = u'b03bbfa8-a4ae-4734-a248-06ce6c7cf562' - CHANGE_XMPP_PASSWD_ID = u'878b9387-de2b-413b-950f-e424a147bcd0' - + AUTHENTICATE_PROFILE_ID = u"b03bbfa8-a4ae-4734-a248-06ce6c7cf562" + CHANGE_XMPP_PASSWD_ID = u"878b9387-de2b-413b-950f-e424a147bcd0" ## Text values ## BOOL_TRUE = "true" BOOL_FALSE = "false" - ## Special values used in bridge methods calls ## HISTORY_LIMIT_DEFAULT = -1 HISTORY_LIMIT_NONE = -2 - ## Progress error special values ## - PROGRESS_ERROR_DECLINED = u'declined' # session has been declined by peer user - + PROGRESS_ERROR_DECLINED = u"declined" # session has been declined by peer user ## Files ## - FILE_TYPE_DIRECTORY = 'directory' - FILE_TYPE_FILE = 'file' - + FILE_TYPE_DIRECTORY = "directory" + FILE_TYPE_FILE = "file" ## Permissions management ## - ACCESS_PERM_READ = u'read' - ACCESS_PERM_WRITE = u'write' + ACCESS_PERM_READ = u"read" + ACCESS_PERM_WRITE = u"write" ACCESS_PERMS = {ACCESS_PERM_READ, ACCESS_PERM_WRITE} - ACCESS_TYPE_PUBLIC = u'public' - ACCESS_TYPE_WHITELIST = u'whitelist' + ACCESS_TYPE_PUBLIC = u"public" + ACCESS_TYPE_WHITELIST = u"whitelist" ACCESS_TYPES = (ACCESS_TYPE_PUBLIC, ACCESS_TYPE_WHITELIST) - ## Common data keys ## - KEY_THUMBNAILS = u'thumbnails' - KEY_PROGRESS_ID = u'progress_id' + KEY_THUMBNAILS = u"thumbnails" + KEY_PROGRESS_ID = u"progress_id" - - #internationalisation - DEFAULT_LOCALE = u'en_GB' - + # internationalisation + DEFAULT_LOCALE = u"en_GB" ## Misc ## SAVEFILE_DATABASE = APP_NAME_FILE + ".db" IQ_SET = '/iq[@type="set"]' - ENV_PREFIX = 'SAT_' # Prefix used for environment variables - IGNORE = 'ignore' - NO_LIMIT = -1 # used in bridge when a integer value is expected - DEFAULT_MAX_AGE = 1209600 # default max age of cached files, in seconds - HASH_SHA1_EMPTY = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' + ENV_PREFIX = "SAT_" # Prefix used for environment variables + IGNORE = "ignore" + NO_LIMIT = -1 # used in bridge when a integer value is expected + DEFAULT_MAX_AGE = 1209600 # default max age of cached files, in seconds + HASH_SHA1_EMPTY = "da39a3ee5e6b4b0d3255bfef95601890afd80709" @classmethod def LOG_OPTIONS(cls): """Return options checked for logs""" # XXX: we use a classmethod so we can use Const inheritance to change default options - return(cls.LOG_OPT_COLORS, cls.LOG_OPT_TAINTS_DICT, cls.LOG_OPT_LEVEL, cls.LOG_OPT_FORMAT, cls.LOG_OPT_LOGGER, cls.LOG_OPT_OUTPUT) + return ( + cls.LOG_OPT_COLORS, + cls.LOG_OPT_TAINTS_DICT, + cls.LOG_OPT_LEVEL, + cls.LOG_OPT_FORMAT, + cls.LOG_OPT_LOGGER, + cls.LOG_OPT_OUTPUT, + ) @classmethod def bool(cls, value):
--- a/sat/core/exceptions.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/core/exceptions.py Wed Jun 27 20:14:46 2018 +0200 @@ -84,7 +84,9 @@ pass -class FeatureNotFound(Exception): # a disco feature/identity which is needed is not present +class FeatureNotFound( + Exception +): # a disco feature/identity which is needed is not present pass
--- a/sat/core/i18n.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/core/i18n.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,27 +19,31 @@ from sat.core.log import getLogger + log = getLogger(__name__) try: import gettext - _ = gettext.translation('sat', 'i18n', fallback=True).ugettext + _ = gettext.translation("sat", "i18n", fallback=True).ugettext _translators = {None: gettext.NullTranslations()} def languageSwitch(lang=None): if not lang in _translators: - _translators[lang] = gettext.translation('sat', languages=[lang], fallback=True) + _translators[lang] = gettext.translation( + "sat", languages=[lang], fallback=True + ) _translators[lang].install(unicode=True) + except ImportError: log.warning("gettext support disabled") - _ = lambda msg: msg # Libervia doesn't support gettext + _ = lambda msg: msg # Libervia doesn't support gettext + def languageSwitch(lang=None): pass -D_ = lambda msg: msg # used for deferred translations - +D_ = lambda msg: msg # used for deferred translations
--- a/sat/core/log_config.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/core/log_config.py Wed Jun 27 20:14:46 2018 +0200 @@ -31,6 +31,7 @@ def __init__(self, *args, **kwargs): super(TwistedLogger, self).__init__(*args, **kwargs) from twisted.python import log as twisted_log + self.twisted_log = twisted_log def out(self, message, level=None): @@ -38,22 +39,30 @@ @param message: formatted message """ - self.twisted_log.msg(message.encode('utf-8', 'ignore'), sat_logged=True, level=level) + self.twisted_log.msg( + message.encode("utf-8", "ignore"), sat_logged=True, level=level + ) class ConfigureBasic(log.ConfigureBase): - def configureColors(self, colors, force_colors, levels_taints_dict): - super(ConfigureBasic, self).configureColors(colors, force_colors, levels_taints_dict) + super(ConfigureBasic, self).configureColors( + colors, force_colors, levels_taints_dict + ) if colors: import sys + try: isatty = sys.stdout.isatty() except AttributeError: isatty = False - if force_colors or isatty: # FIXME: isatty should be tested on each handler, not globaly + if ( + force_colors or isatty + ): # FIXME: isatty should be tested on each handler, not globaly # we need colors - log.Logger.post_treat = lambda logger, level, message: self.ansiColors(level, message) + log.Logger.post_treat = lambda logger, level, message: self.ansiColors( + level, message + ) elif force_colors: raise ValueError("force_colors can't be used if colors is False") @@ -61,30 +70,36 @@ def getProfile(): """Try to find profile value using introspection""" import inspect + stack = inspect.stack() current_path = stack[0][1] for frame_data in stack[:-1]: if frame_data[1] != current_path: - if log.backend == C.LOG_BACKEND_STANDARD and "/logging/__init__.py" in frame_data[1]: + if ( + log.backend == C.LOG_BACKEND_STANDARD + and "/logging/__init__.py" in frame_data[1] + ): continue break frame = frame_data[0] args = inspect.getargvalues(frame) try: - profile = args.locals.get('profile') or args.locals['profile_key'] + profile = args.locals.get("profile") or args.locals["profile_key"] except (TypeError, KeyError): try: try: - profile = args.locals['self'].profile + profile = args.locals["self"].profile except AttributeError: try: - profile = args.locals['self'].parent.profile + profile = args.locals["self"].parent.profile except AttributeError: - profile = args.locals['self'].host.profile # used in quick_frontend for single profile configuration + profile = args.locals[ + "self" + ].host.profile # used in quick_frontend for single profile configuration except Exception: # we can't find profile, we return an empty value - profile = '' + profile = "" return profile @@ -97,16 +112,23 @@ @param observer: original observer to hook @param can_colors: True if observer can display ansi colors """ + def observer_hook(event): """redirect non SàT log to twisted_logger, and add colors when possible""" - if 'sat_logged' in event: # we only want our own logs, other are managed by twistedObserver + if ( + "sat_logged" in event + ): # we only want our own logs, other are managed by twistedObserver # we add colors if possible - if (can_colors and self.LOGGER_CLASS.colors) or self.LOGGER_CLASS.force_colors: - message = event.get('message', tuple()) - level = event.get('level', C.LOG_LVL_INFO) + if ( + can_colors and self.LOGGER_CLASS.colors + ) or self.LOGGER_CLASS.force_colors: + message = event.get("message", tuple()) + level = event.get("level", C.LOG_LVL_INFO) if message: - event['message'] = (self.ansiColors(level, ''.join(message)),) # must be a tuple - observer(event) # we can now call the original observer + event["message"] = ( + self.ansiColors(level, "".join(message)), + ) # must be a tuple + observer(event) # we can now call the original observer return observer_hook @@ -130,7 +152,7 @@ @param observer: observer to hook @return: hooked observer or original one """ - if hasattr(observer, '__self__'): + if hasattr(observer, "__self__"): ori = observer if isinstance(observer.__self__, self.twisted_log.FileLogObserver): observer = self.changeFileLogObserver(observer) @@ -147,12 +169,15 @@ """initialise needed attributes, and install observers hooks""" self.observers = {} from twisted.python import log as twisted_log + self.twisted_log = twisted_log self.log_publisher = twisted_log.msg.__self__ + def addObserverObserver(self_logpub, other): """Install hook so we know when a new observer is added""" other = self.installObserverHook(other) return self_logpub._originalAddObserver(other) + def removeObserverObserver(self_logpub, ori): """removeObserver hook fix @@ -170,11 +195,20 @@ raise ValueError("Unknown observer") # we replace addObserver/removeObserver by our own - twisted_log.LogPublisher._originalAddObserver = twisted_log.LogPublisher.addObserver - twisted_log.LogPublisher._originalRemoveObserver = twisted_log.LogPublisher.removeObserver - import types # see https://stackoverflow.com/a/4267590 (thx Chris Morgan/aaronasterling) - twisted_log.addObserver = types.MethodType(addObserverObserver, self.log_publisher, twisted_log.LogPublisher) - twisted_log.removeObserver = types.MethodType(removeObserverObserver, self.log_publisher, twisted_log.LogPublisher) + twisted_log.LogPublisher._originalAddObserver = ( + twisted_log.LogPublisher.addObserver + ) + twisted_log.LogPublisher._originalRemoveObserver = ( + twisted_log.LogPublisher.removeObserver + ) + import types # see https://stackoverflow.com/a/4267590 (thx Chris Morgan/aaronasterling) + + twisted_log.addObserver = types.MethodType( + addObserverObserver, self.log_publisher, twisted_log.LogPublisher + ) + twisted_log.removeObserver = types.MethodType( + removeObserverObserver, self.log_publisher, twisted_log.LogPublisher + ) # we now change existing observers for idx, observer in enumerate(self.log_publisher.observers): @@ -186,6 +220,7 @@ def configureOutput(self, output): import sys + if output is None: output = C.LOG_OPT_OUTPUT_SEP + C.LOG_OPT_OUTPUT_DEFAULT self.manageOutputs(output) @@ -194,9 +229,11 @@ if C.LOG_OPT_OUTPUT_DEFAULT in log.handlers: # default output is already managed, we just add output to stdout if we are in debug or nodaemon mode if self.backend_data is None: - raise ValueError("You must pass options as backend_data with Twisted backend") + raise ValueError( + "You must pass options as backend_data with Twisted backend" + ) options = self.backend_data - if options.get('nodaemon', False) or options.get('debug', False): + if options.get("nodaemon", False) or options.get("debug", False): addObserver(self.twisted_log.FileLogObserver(sys.stdout).emit) else: # \\default is not in the output, so we remove current observers @@ -208,49 +245,81 @@ if C.LOG_OPT_OUTPUT_FILE in log.handlers: from twisted.python import logfile + for path in log.handlers[C.LOG_OPT_OUTPUT_FILE]: - log_file = sys.stdout if path == '-' else logfile.LogFile.fromFullPath(path) + log_file = ( + sys.stdout if path == "-" else logfile.LogFile.fromFullPath(path) + ) addObserver(self.twisted_log.FileLogObserver(log_file).emit) if C.LOG_OPT_OUTPUT_MEMORY in log.handlers: - raise NotImplementedError("Memory observer is not implemented in Twisted backend") + raise NotImplementedError( + "Memory observer is not implemented in Twisted backend" + ) def configureColors(self, colors, force_colors, levels_taints_dict): - super(ConfigureTwisted, self).configureColors(colors, force_colors, levels_taints_dict) + super(ConfigureTwisted, self).configureColors( + colors, force_colors, levels_taints_dict + ) self.LOGGER_CLASS.colors = colors self.LOGGER_CLASS.force_colors = force_colors if force_colors and not colors: - raise ValueError('colors must be True if force_colors is True') + raise ValueError("colors must be True if force_colors is True") def postTreatment(self): """Install twistedObserver which manage non SàT logs""" + def twistedObserver(event): """Observer which redirect log message not produced by SàT to SàT logging system""" - if not 'sat_logged' in event: + if not "sat_logged" in event: # this log was not produced by SàT from twisted.python import log as twisted_log + text = twisted_log.textFromEventDict(event) if text is None: return twisted_logger = log.getLogger(C.LOG_TWISTED_LOGGER) - log_method = twisted_logger.error if event.get('isError', False) else twisted_logger.info - log_method(text.decode('utf-8')) + log_method = ( + twisted_logger.error + if event.get("isError", False) + else twisted_logger.info + ) + log_method(text.decode("utf-8")) self.log_publisher._originalAddObserver(twistedObserver) class ConfigureStandard(ConfigureBasic): - - def __init__(self, level=None, fmt=None, output=None, logger=None, colors=False, levels_taints_dict=None, force_colors=False, backend_data=None): + def __init__( + self, + level=None, + fmt=None, + output=None, + logger=None, + colors=False, + levels_taints_dict=None, + force_colors=False, + backend_data=None, + ): if fmt is None: fmt = C.LOG_OPT_FORMAT[1] if output is None: output = C.LOG_OPT_OUTPUT[1] - super(ConfigureStandard, self).__init__(level, fmt, output, logger, colors, levels_taints_dict, force_colors, backend_data) + super(ConfigureStandard, self).__init__( + level, + fmt, + output, + logger, + colors, + levels_taints_dict, + force_colors, + backend_data, + ) def preTreatment(self): """We use logging methods directly, instead of using Logger""" import logging + log.getLogger = logging.getLogger log.debug = logging.debug log.info = logging.info @@ -271,7 +340,7 @@ class SatFormatter(logging.Formatter): u"""Formatter which manage SàT specificities""" _format = fmt - _with_profile = '%(profile)s' in fmt + _with_profile = "%(profile)s" in fmt def __init__(self, can_colors=False): super(SatFormatter, self).__init__(self._format) @@ -288,14 +357,14 @@ record.color_start = log.COLOR_START record.color_end = log.COLOR_END else: - record.color_start = record.color_end = '' + record.color_start = record.color_end = "" s = super(SatFormatter, self).format(record) if do_color: s = ConfigureStandard.ansiColors(record.levelname, s) if sys.platform == "android": # FIXME: dirty hack to workaround android encoding issue on log # need to be fixed properly - return s.encode('ascii', 'ignore') + return s.encode("ascii", "ignore") else: return s @@ -308,7 +377,9 @@ self.name_filter = log.FilterName(logger) if logger else None def configureColors(self, colors, force_colors, levels_taints_dict): - super(ConfigureStandard, self).configureColors(colors, force_colors, levels_taints_dict) + super(ConfigureStandard, self).configureColors( + colors, force_colors, levels_taints_dict + ) self.formatterClass.with_colors = colors self.formatterClass.force_colors = force_colors if not colors and force_colors: @@ -323,6 +394,7 @@ def postTreatment(self): import logging + root_logger = logging.getLogger() if len(root_logger.handlers) == 0: for handler, options in log.handlers.items(): @@ -335,14 +407,21 @@ self._addHandler(root_logger, hdlr, can_colors=can_colors) elif handler == C.LOG_OPT_OUTPUT_MEMORY: from logging.handlers import BufferingHandler + class SatMemoryHandler(BufferingHandler): def emit(self, record): super(SatMemoryHandler, self).emit(self.format(record)) + hdlr = SatMemoryHandler(options) - log.handlers[handler] = hdlr # we keep a reference to the handler to read the buffer later + log.handlers[ + handler + ] = ( + hdlr + ) # we keep a reference to the handler to read the buffer later self._addHandler(root_logger, hdlr, can_colors=False) elif handler == C.LOG_OPT_OUTPUT_FILE: import os.path + for path in options: hdlr = logging.FileHandler(os.path.expanduser(path)) self._addHandler(root_logger, hdlr, can_colors=False) @@ -358,13 +437,16 @@ @param size: number of logs to return """ mem_handler = log.handlers[C.LOG_OPT_OUTPUT_MEMORY] - return (log_msg for log_msg in mem_handler.buffer[size if size is None else -size:]) + return ( + log_msg for log_msg in mem_handler.buffer[size if size is None else -size :] + ) log.configure_cls[C.LOG_BACKEND_BASIC] = ConfigureBasic log.configure_cls[C.LOG_BACKEND_TWISTED] = ConfigureTwisted log.configure_cls[C.LOG_BACKEND_STANDARD] = ConfigureStandard + def configure(backend, **options): """Configure logging behaviour @param backend: can be: @@ -375,6 +457,7 @@ """ return log.configure(backend, **options) + def _parseOptions(options): """Parse string options as given in conf or environment variable, and return expected python value @@ -384,11 +467,11 @@ LEVEL = C.LOG_OPT_LEVEL[0] if COLORS in options: - if options[COLORS].lower() in ('1', 'true'): + if options[COLORS].lower() in ("1", "true"): options[COLORS] = True - elif options[COLORS] == 'force': + elif options[COLORS] == "force": options[COLORS] = True - options['force_colors'] = True + options["force_colors"] = True else: options[COLORS] = False if LEVEL in options: @@ -397,6 +480,7 @@ level = C.LOG_LVL_INFO options[LEVEL] = level + def satConfigure(backend=C.LOG_BACKEND_STANDARD, const=None, backend_data=None): """Configure logging system for SàT, can be used by frontends @@ -413,13 +497,18 @@ log.C = const from sat.tools import config import os + log_conf = {} sat_conf = config.parseMainConf() for opt_name, opt_default in C.LOG_OPTIONS(): try: - log_conf[opt_name] = os.environ[''.join((C.ENV_PREFIX, C.LOG_OPT_PREFIX.upper(), opt_name.upper()))] + log_conf[opt_name] = os.environ[ + "".join((C.ENV_PREFIX, C.LOG_OPT_PREFIX.upper(), opt_name.upper())) + ] except KeyError: - log_conf[opt_name] = config.getConfig(sat_conf, C.LOG_OPT_SECTION, C.LOG_OPT_PREFIX + opt_name, opt_default) + log_conf[opt_name] = config.getConfig( + sat_conf, C.LOG_OPT_SECTION, C.LOG_OPT_PREFIX + opt_name, opt_default + ) _parseOptions(log_conf) configure(backend, backend_data=backend_data, **log_conf)
--- a/sat/core/sat_main.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/core/sat_main.py Wed Jun 27 20:14:46 2018 +0200 @@ -27,6 +27,7 @@ from sat.core import xmpp from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) from sat.core.constants import Const as C from sat.memory import memory @@ -43,26 +44,31 @@ import uuid try: - from collections import OrderedDict # only available from python 2.7 + from collections import OrderedDict # only available from python 2.7 except ImportError: from ordereddict import OrderedDict class SAT(service.Service): - 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 = ( + 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 = {} - self.ns_map = {u'x-data': u'jabber:x:data'} # map for short name to whole namespace, - # extended by plugins with registerNamespace + self.ns_map = { + u"x-data": u"jabber:x:data" + } # map for short name to whole namespace, + # extended by plugins with registerNamespace self.memory = memory.Memory(self) - self.trigger = trigger.TriggerManager() # trigger are used to change SàT behaviour + self.trigger = ( + trigger.TriggerManager() + ) # trigger are used to change SàT behaviour - bridge_name = self.memory.getConfig('', 'bridge', 'dbus') + bridge_name = self.memory.getConfig("", "bridge", "dbus") bridge_module = dynamic_import.bridge(bridge_name) if bridge_module is None: @@ -79,28 +85,42 @@ self.bridge.register_method("getFeatures", self.getFeatures) self.bridge.register_method("profileNameGet", self.memory.getProfileName) self.bridge.register_method("profilesListGet", self.memory.getProfilesList) - self.bridge.register_method("getEntityData", lambda jid_, keys, profile: self.memory.getEntityData(jid.JID(jid_), keys, profile)) + self.bridge.register_method( + "getEntityData", + lambda jid_, keys, profile: self.memory.getEntityData( + jid.JID(jid_), keys, profile + ), + ) self.bridge.register_method("getEntitiesData", self.memory._getEntitiesData) self.bridge.register_method("profileCreate", self.memory.createProfile) self.bridge.register_method("asyncDeleteProfile", self.memory.asyncDeleteProfile) self.bridge.register_method("profileStartSession", self.memory.startSession) - self.bridge.register_method("profileIsSessionStarted", self.memory._isSessionStarted) + self.bridge.register_method( + "profileIsSessionStarted", self.memory._isSessionStarted + ) self.bridge.register_method("profileSetDefault", self.memory.profileSetDefault) self.bridge.register_method("connect", self._connect) self.bridge.register_method("disconnect", self.disconnect) self.bridge.register_method("getContacts", self.getContacts) self.bridge.register_method("getContactsFromGroup", self.getContactsFromGroup) self.bridge.register_method("getMainResource", self.memory._getMainResource) - self.bridge.register_method("getPresenceStatuses", self.memory._getPresenceStatuses) + self.bridge.register_method( + "getPresenceStatuses", self.memory._getPresenceStatuses + ) self.bridge.register_method("getWaitingSub", self.memory.getWaitingSub) self.bridge.register_method("messageSend", self._messageSend) self.bridge.register_method("getConfig", self._getConfig) self.bridge.register_method("setParam", self.setParam) self.bridge.register_method("getParamA", self.memory.getStringParamA) self.bridge.register_method("asyncGetParamA", self.memory.asyncGetStringParamA) - self.bridge.register_method("asyncGetParamsValuesFromCategory", self.memory.asyncGetParamsValuesFromCategory) + self.bridge.register_method( + "asyncGetParamsValuesFromCategory", + self.memory.asyncGetParamsValuesFromCategory, + ) self.bridge.register_method("getParamsUI", self.memory.getParamsUI) - self.bridge.register_method("getParamsCategories", self.memory.getParamsCategories) + self.bridge.register_method( + "getParamsCategories", self.memory.getParamsCategories + ) self.bridge.register_method("paramsRegisterApp", self.memory.paramsRegisterApp) self.bridge.register_method("historyGet", self.memory._historyGet) self.bridge.register_method("setPresence", self._setPresence) @@ -135,12 +155,14 @@ def full_version(self): """Return the full version of SàT (with release name and extra data when in development mode)""" version = self.version - if version[-1] == 'D': + if version[-1] == "D": # we are in debug version, we add extra data try: return self._version_cache except AttributeError: - self._version_cache = u"{} « {} » ({})".format(version, C.APP_RELEASE_NAME, utils.getRepositoryData(sat)) + self._version_cache = u"{} « {} » ({})".format( + version, C.APP_RELEASE_NAME, utils.getRepositoryData(sat) + ) return self._version_cache else: return version @@ -158,8 +180,11 @@ ui_contact_list.ContactList(self) ui_profile_manager.ProfileManager(self) except Exception as e: - log.error(_(u"Could not initialize backend: {reason}").format( - reason = str(e).decode('utf-8', 'ignore'))) + log.error( + _(u"Could not initialize backend: {reason}").format( + reason=str(e).decode("utf-8", "ignore") + ) + ) sys.exit(1) self.initialised.callback(None) log.info(_(u"Backend is ready")) @@ -180,43 +205,69 @@ # just use a client, and plugin blacklisting should be possible in sat.conf plugins_path = os.path.dirname(sat.plugins.__file__) plugin_glob = "plugin*." + C.PLUGIN_EXT - plug_lst = [os.path.splitext(plugin)[0] for plugin in map(os.path.basename, glob(os.path.join(plugins_path, plugin_glob)))] + plug_lst = [ + os.path.splitext(plugin)[0] + for plugin in map( + os.path.basename, glob(os.path.join(plugins_path, plugin_glob)) + ) + ] plugins_to_import = {} # plugins we still have to import for plug in plug_lst: - plugin_path = 'sat.plugins.' + plug + plugin_path = "sat.plugins." + plug try: __import__(plugin_path) except exceptions.MissingModule as e: self._unimport_plugin(plugin_path) - log.warning(u"Can't import plugin [{path}] because of an unavailale third party module:\n{msg}".format( - path=plugin_path, msg=e)) + log.warning( + u"Can't import plugin [{path}] because of an unavailale third party module:\n{msg}".format( + path=plugin_path, msg=e + ) + ) continue except exceptions.CancelError as e: - log.info(u"Plugin [{path}] cancelled its own import: {msg}".format(path=plugin_path, msg=e)) + log.info( + u"Plugin [{path}] cancelled its own import: {msg}".format( + path=plugin_path, msg=e + ) + ) self._unimport_plugin(plugin_path) continue except Exception as e: import traceback - log.error(_(u"Can't import plugin [{path}]:\n{error}").format(path=plugin_path, error=traceback.format_exc())) + + log.error( + _(u"Can't import plugin [{path}]:\n{error}").format( + path=plugin_path, error=traceback.format_exc() + ) + ) self._unimport_plugin(plugin_path) continue mod = sys.modules[plugin_path] plugin_info = mod.PLUGIN_INFO - import_name = plugin_info['import_name'] + import_name = plugin_info["import_name"] - plugin_modes = plugin_info[u'modes'] = set(plugin_info.setdefault(u"modes", C.PLUG_MODE_DEFAULT)) + plugin_modes = plugin_info[u"modes"] = set( + plugin_info.setdefault(u"modes", C.PLUG_MODE_DEFAULT) + ) # if the plugin is an entry point, it must work in component mode - if plugin_info[u'type'] == C.PLUG_TYPE_ENTRY_POINT: + if plugin_info[u"type"] == C.PLUG_TYPE_ENTRY_POINT: # if plugin is an entrypoint, we cache it if C.PLUG_MODE_COMPONENT not in plugin_modes: - log.error(_(u"{type} type must be used with {mode} mode, ignoring plugin").format( - type = C.PLUG_TYPE_ENTRY_POINT, mode = C.PLUG_MODE_COMPONENT)) + log.error( + _( + u"{type} type must be used with {mode} mode, ignoring plugin" + ).format(type=C.PLUG_TYPE_ENTRY_POINT, mode=C.PLUG_MODE_COMPONENT) + ) self._unimport_plugin(plugin_path) continue if import_name in plugins_to_import: - log.error(_(u"Name conflict for import name [{import_name}], can't import plugin [{name}]").format(**plugin_info)) + log.error( + _( + u"Name conflict for import name [{import_name}], can't import plugin [{name}]" + ).format(**plugin_info) + ) continue plugins_to_import[import_name] = (plugin_path, mod, plugin_info) while True: @@ -227,7 +278,9 @@ if not plugins_to_import: break - def _import_plugins_from_dict(self, plugins_to_import, import_name=None, optional=False): + def _import_plugins_from_dict( + self, plugins_to_import, import_name=None, optional=False + ): """Recursively import and their dependencies in the right order @param plugins_to_import(dict): key=import_name and values=(plugin_path, module, plugin_info) @@ -235,14 +288,16 @@ @param optional(bool): if False and plugin is not found, an ImportError exception is raised """ if import_name in self.plugins: - log.debug(u'Plugin {} already imported, passing'.format(import_name)) + log.debug(u"Plugin {} already imported, passing".format(import_name)) return if not import_name: import_name, (plugin_path, mod, plugin_info) = plugins_to_import.popitem() else: if not import_name in plugins_to_import: if optional: - log.warning(_(u"Recommended plugin not found: {}").format(import_name)) + log.warning( + _(u"Recommended plugin not found: {}").format(import_name) + ) return msg = u"Dependency not found: {}".format(import_name) log.error(msg) @@ -252,21 +307,33 @@ recommendations = plugin_info.setdefault("recommendations", []) for to_import in dependencies + recommendations: if to_import not in self.plugins: - log.debug(u'Recursively import dependency of [%s]: [%s]' % (import_name, to_import)) + log.debug( + u"Recursively import dependency of [%s]: [%s]" + % (import_name, to_import) + ) try: - self._import_plugins_from_dict(plugins_to_import, to_import, to_import not in dependencies) + self._import_plugins_from_dict( + plugins_to_import, to_import, to_import not in dependencies + ) except ImportError as e: - log.warning(_(u"Can't import plugin {name}: {error}").format(name=plugin_info['name'], error=e)) + log.warning( + _(u"Can't import plugin {name}: {error}").format( + name=plugin_info["name"], error=e + ) + ) if optional: return raise e - log.info("importing plugin: {}".format(plugin_info['name'])) + log.info("importing plugin: {}".format(plugin_info["name"])) # we instanciate the plugin here try: - self.plugins[import_name] = getattr(mod, plugin_info['main'])(self) + self.plugins[import_name] = getattr(mod, plugin_info["main"])(self) except Exception as e: - log.warning(u'Error while loading plugin "{name}", ignoring it: {error}' - .format(name=plugin_info['name'], error=e)) + log.warning( + u'Error while loading plugin "{name}", ignoring it: {error}'.format( + name=plugin_info["name"], error=e + ) + ) if optional: return raise ImportError(u"Error during initiation") @@ -276,7 +343,7 @@ self.plugins[import_name].is_handler = False # we keep metadata as a Class attribute self.plugins[import_name]._info = plugin_info - #TODO: test xmppclient presence and register handler parent + # TODO: test xmppclient presence and register handler parent def pluginsUnload(self): """Call unload method on every loaded plugin, if exists @@ -296,11 +363,11 @@ defers_list.append(defer.maybeDeferred(unload)) return defers_list - def _connect(self, profile_key, password='', options=None): + def _connect(self, profile_key, password="", options=None): profile = self.memory.getProfileName(profile_key) return self.connect(profile, password, options) - def connect(self, profile, password='', options=None, max_retries=C.XMPP_MAX_RETRIES): + def connect(self, profile, password="", options=None, max_retries=C.XMPP_MAX_RETRIES): """Connect a profile (i.e. connect client.component to XMPP server) Retrieve the individual parameters, authenticate the profile @@ -316,7 +383,8 @@ @raise exceptions.PasswordError: Profile password is wrong """ if options is None: - options={} + options = {} + def connectProfile(dummy=None): if self.isConnected(profile): log.info(_("already connected !")) @@ -375,15 +443,19 @@ features.append(features_d) d_list = defer.DeferredList(features) + def buildFeatures(result, import_names): assert len(result) == len(import_names) ret = {} - for name, (success, data) in zip (import_names, result): + for name, (success, data) in zip(import_names, result): if success: ret[name] = data else: - log.warning(u"Error while getting features for {name}: {failure}".format( - name=name, failure=data)) + log.warning( + u"Error while getting features for {name}: {failure}".format( + name=name, failure=data + ) + ) ret[name] = {} return ret @@ -392,6 +464,7 @@ def getContacts(self, profile_key): client = self.getClient(profile_key) + def got_roster(dummy): ret = [] for item in client.roster.getItems(): # we get all items for client's roster @@ -467,14 +540,14 @@ @return: list of clients """ if not profile_key: - raise exceptions.DataError(_(u'profile_key must not be empty')) + raise exceptions.DataError(_(u"profile_key must not be empty")) try: profile = self.memory.getProfileName(profile_key, True) except exceptions.ProfileUnknownError: return [] if profile == C.PROF_KEY_ALL: return self.profiles.values() - elif profile[0] == '@': # only profile keys can start with "@" + elif profile[0] == "@": # only profile keys can start with "@" raise exceptions.ProfileKeyUnknown return [self.profiles[profile]] @@ -485,7 +558,7 @@ @param name: name of the option @return: unicode representation of the option """ - return unicode(self.memory.getConfig(section, name, '')) + return unicode(self.memory.getConfig(section, name, "")) def logErrback(self, failure_): """generic errback logging @@ -495,12 +568,12 @@ log.error(_(u"Unexpected error: {}".format(failure_))) return failure_ - # namespaces + # namespaces def registerNamespace(self, short_name, namespace): """associate a namespace to a short name""" if short_name in self.ns_map: - raise exceptions.ConflictError(u'this short name is already used') + raise exceptions.ConflictError(u"this short name is already used") self.ns_map[short_name] = namespace def getNamespaces(self): @@ -509,10 +582,7 @@ def getSessionInfos(self, profile_key): """compile interesting data on current profile session""" client = self.getClient(profile_key) - data = { - "jid": client.jid.full(), - "started": unicode(int(client.started)), - } + data = {"jid": client.jid.full(), "started": unicode(int(client.started))} return defer.succeed(data) # local dirs @@ -531,11 +601,11 @@ """ # FIXME: component and profile are parsed with **kwargs because of python 2 limitations # once moved to python 3, this can be fixed - component = kwargs.pop('component', False) - profile = kwargs.pop('profile', True) + component = kwargs.pop("component", False) + profile = kwargs.pop("profile", True) assert not kwargs - path_elts = [self.memory.getConfig('', 'local_dir')] + path_elts = [self.memory.getConfig("", "local_dir")] if component: path_elts.append(C.COMPONENTS_DIR) path_elts.append(regex.pathEscape(dir_name)) @@ -561,7 +631,7 @@ """ profile = self.memory.getProfileName(profile_key) if not profile: - log.error(_('asking connection status for a non-existant profile')) + log.error(_("asking connection status for a non-existant profile")) raise exceptions.ProfileUnknownError(profile_key) if profile not in self.profiles: return False @@ -569,28 +639,47 @@ ## XMPP methods ## - def _messageSend(self, to_jid_s, message, subject=None, mess_type='auto', extra=None, profile_key=C.PROF_KEY_NONE): + def _messageSend( + self, + to_jid_s, + message, + subject=None, + mess_type="auto", + extra=None, + profile_key=C.PROF_KEY_NONE, + ): client = self.getClient(profile_key) to_jid = jid.JID(to_jid_s) - #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 - return client.sendMessage(to_jid, message, subject, mess_type, {unicode(key): unicode(value) for key, value in extra.items()}) + # 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 + return client.sendMessage( + to_jid, + message, + subject, + mess_type, + {unicode(key): unicode(value) for key, value in extra.items()}, + ) def _setPresence(self, to="", show="", statuses=None, profile_key=C.PROF_KEY_NONE): return self.setPresence(jid.JID(to) if to else None, show, statuses, profile_key) - def setPresence(self, to_jid=None, show="", statuses=None, profile_key=C.PROF_KEY_NONE): + def setPresence( + self, to_jid=None, show="", statuses=None, profile_key=C.PROF_KEY_NONE + ): """Send our presence information""" if statuses is None: statuses = {} profile = self.memory.getProfileName(profile_key) assert profile - priority = int(self.memory.getParamA("Priority", "Connection", profile_key=profile)) + priority = int( + self.memory.getParamA("Priority", "Connection", profile_key=profile) + ) self.profiles[profile].presence.available(to_jid, show, statuses, priority) - #XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not broadcasted to generating resource) - if '' in statuses: - statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop('') - self.bridge.presenceUpdate(self.profiles[profile].jid.full(), show, - int(priority), statuses, profile) + # XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not broadcasted to generating resource) + if "" in statuses: + statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop("") + self.bridge.presenceUpdate( + self.profiles[profile].jid.full(), show, int(priority), statuses, profile + ) def subscription(self, subs_type, raw_jid, profile_key): """Called to manage subscription @@ -600,7 +689,10 @@ profile = self.memory.getProfileName(profile_key) assert profile to_jid = jid.JID(raw_jid) - log.debug(_(u'subsciption request [%(subs_type)s] for %(jid)s') % {'subs_type': subs_type, 'jid': to_jid.full()}) + log.debug( + _(u"subsciption request [%(subs_type)s] for %(jid)s") + % {"subs_type": subs_type, "jid": to_jid.full()} + ) if subs_type == "subscribe": self.profiles[profile].presence.subscribe(to_jid) elif subs_type == "subscribed": @@ -671,12 +763,41 @@ def findFeaturesSet(self, *args, **kwargs): return self.memory.disco.findFeaturesSet(*args, **kwargs) - def _findByFeatures(self, namespaces, identities, bare_jids, service, roster, own_jid, local_device, profile_key): + def _findByFeatures( + self, + namespaces, + identities, + bare_jids, + service, + roster, + own_jid, + local_device, + profile_key, + ): client = self.getClient(profile_key) - return self.findByFeatures(client, namespaces, identities, bare_jids, service, roster, own_jid, local_device) + return self.findByFeatures( + client, + namespaces, + identities, + bare_jids, + service, + roster, + own_jid, + local_device, + ) @defer.inlineCallbacks - def findByFeatures(self, client, namespaces, identities=None, bare_jids=False, service=True, roster=True, own_jid=True, local_device=False): + def findByFeatures( + self, + client, + namespaces, + identities=None, + bare_jids=False, + service=True, + roster=True, + own_jid=True, + local_device=False, + ): """retrieve all services or contacts managing a set a features @param namespaces(list[unicode]): features which must be handled @@ -697,7 +818,9 @@ if not identities: identities = None if not namespaces and not identities: - raise exceptions.DataError("at least one namespace or one identity must be set") + raise exceptions.DataError( + "at least one namespace or one identity must be set" + ) found_service = {} found_own = {} found_roster = {} @@ -705,9 +828,14 @@ services_jids = yield self.findFeaturesSet(client, namespaces) for service_jid in services_jids: infos = yield self.getDiscoInfos(client, service_jid) - if identities is not None and not set(infos.identities.keys()).issuperset(identities): + if identities is not None and not set(infos.identities.keys()).issuperset( + identities + ): continue - found_identities = [(cat, type_, name or u'') for (cat, type_), name in infos.identities.iteritems()] + found_identities = [ + (cat, type_, name or u"") + for (cat, type_), name in infos.identities.iteritems() + ] found_service[service_jid.full()] = found_identities jids = [] @@ -716,8 +844,10 @@ if own_jid: jids.append(client.jid.userhostJID()) - for found, jids in ((found_own, [client.jid.userhostJID()]), - (found_roster, client.roster.getJids())): + for found, jids in ( + (found_own, [client.jid.userhostJID()]), + (found_roster, client.roster.getJids()), + ): for jid_ in jids: if jid_.resource: if bare_jids: @@ -737,9 +867,14 @@ continue infos = yield self.getDiscoInfos(client, full_jid) if infos.features.issuperset(namespaces): - if identities is not None and not set(infos.identities.keys()).issuperset(identities): + if identities is not None and not set( + infos.identities.keys() + ).issuperset(identities): continue - found_identities = [(cat, type_, name or u'') for (cat, type_), name in infos.identities.iteritems()] + found_identities = [ + (cat, type_, name or u"") + for (cat, type_), name in infos.identities.iteritems() + ] found[full_jid.full()] = found_identities defer.returnValue((found_service, found_own, found_roster)) @@ -750,7 +885,13 @@ log.debug(u"Killing action {} for timeout".format(keep_id)) client.actions[keep_id] - def actionNew(self, action_data, security_limit=C.NO_SECURITY_LIMIT, keep_id=None, profile=C.PROF_KEY_NONE): + def actionNew( + self, + action_data, + security_limit=C.NO_SECURITY_LIMIT, + keep_id=None, + profile=C.PROF_KEY_NONE, + ): """Shortcut to bridge.actionNew which generate and id and keep for retrieval @param action_data(dict): action data (see bridge documentation) @@ -763,7 +904,7 @@ id_ = unicode(uuid.uuid4()) if keep_id is not None: client = self.getClient(profile) - action_timer = reactor.callLater(60*30, self._killAction, keep_id, client) + action_timer = reactor.callLater(60 * 30, self._killAction, keep_id, client) client.actions[keep_id] = (action_data, id_, security_limit, action_timer) self.bridge.actionNew(action_data, id_, security_limit, profile) @@ -776,7 +917,9 @@ client = self.getClient(profile) return [action_tuple[:-1] for action_tuple in client.actions.itervalues()] - def registerProgressCb(self, progress_id, callback, metadata=None, profile=C.PROF_KEY_NONE): + def registerProgressCb( + self, progress_id, callback, metadata=None, profile=C.PROF_KEY_NONE + ): """Register a callback called when progress is requested for id""" if metadata is None: metadata = {} @@ -795,7 +938,7 @@ def _progressGet(self, progress_id, profile): data = self.progressGet(progress_id, profile) - return {k: unicode(v) for k,v in data.iteritems()} + return {k: unicode(v) for k, v in data.iteritems()} def progressGet(self, progress_id, profile): """Return a dict with progress information @@ -837,7 +980,10 @@ profile = client.profile progress_dict = {} progress_all[profile] = progress_dict - for progress_id, (dummy, progress_metadata) in client._progress_cb.iteritems(): + for ( + progress_id, + (dummy, progress_metadata), + ) in client._progress_cb.iteritems(): progress_dict[progress_id] = progress_metadata return progress_all @@ -870,7 +1016,7 @@ 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) + callback_id = kwargs.pop("force_id", None) if callback_id is None: callback_id = str(uuid.uuid4()) else: @@ -878,12 +1024,14 @@ raise exceptions.ConflictError(_(u"id already registered")) self._cb_map[callback_id] = (callback, args, kwargs) - if "one_shot" in kwargs: # One Shot callback are removed after 30 min + if "one_shot" in kwargs: # One Shot callback are removed after 30 min + def purgeCallback(): try: self.removeCallback(callback_id) except KeyError: pass + reactor.callLater(1800, purgeCallback) return callback_id @@ -906,14 +1054,16 @@ - C.BOOL_TRUE - C.BOOL_FALSE """ - # FIXME: security limit need to be checked here + # 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(_(u'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 @@ -922,7 +1072,7 @@ except KeyError: pass else: - action_tuple[-1].cancel() # the last item is the action timer + action_tuple[-1].cancel() # the last item is the action timer del client.actions[callback_id] try: @@ -933,17 +1083,20 @@ if kwargs.get("with_data", False): if data is None: raise exceptions.DataError("Required data for this callback is missing") - args,kwargs=list(args)[:],kwargs.copy() # we don't want to modify the original (kw)args + args, kwargs = ( + list(args)[:], + kwargs.copy(), + ) # we don't want to modify the original (kw)args args.insert(0, data) kwargs["profile"] = profile del kwargs["with_data"] - if kwargs.pop('one_shot', False): + if kwargs.pop("one_shot", False): self.removeCallback(callback_id) return defer.maybeDeferred(callback, *args, **kwargs) - #Menus management + # Menus management def _getMenuCanonicalPath(self, path): """give canonical form of path @@ -954,7 +1107,14 @@ """ 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): + 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(iterable[unicode]): path to go to the menu (category/subcategory/.../item) (e.g.: ("File", "Open")) @@ -989,34 +1149,40 @@ callback, args, kwargs = self._cb_map[callback_id] except KeyError: raise exceptions.DataError("Unknown callback id") - kwargs["with_data"] = True # we have to be sure that we use extra data + kwargs["with_data"] = True # we have to be sure that we use extra data else: raise exceptions.DataError("Unknown callback type") for menu_data in self._menus.itervalues(): - if menu_data['path'] == path and menu_data['type'] == type_: - raise exceptions.ConflictError(_("A menu with the same path and type already exists")) + if menu_data["path"] == path and menu_data["type"] == type_: + raise exceptions.ConflictError( + _("A menu with the same path and type already exists") + ) 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)) + 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_ - } + 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 - def getMenus(self, language='', security_limit=C.NO_SECURITY_LIMIT): + def getMenus(self, language="", security_limit=C.NO_SECURITY_LIMIT): """Return all menus registered @param language: language used for translation, or empty string for default @@ -1032,24 +1198,36 @@ """ ret = [] for menu_id, menu_data in self._menus.iteritems(): - type_ = menu_data['type'] - path = menu_data['path'] - menu_security_limit = menu_data['security_limit'] - if security_limit!=C.NO_SECURITY_LIMIT and (menu_security_limit==C.NO_SECURITY_LIMIT or menu_security_limit>security_limit): + type_ = menu_data["type"] + path = menu_data["path"] + menu_security_limit = menu_data["security_limit"] + if security_limit != C.NO_SECURITY_LIMIT and ( + menu_security_limit == C.NO_SECURITY_LIMIT + or menu_security_limit > security_limit + ): continue languageSwitch(language) path_i18n = [_(elt) for elt in path] languageSwitch() - extra = {} # TODO: manage extra data like icon + extra = {} # TODO: manage extra data like icon ret.append((menu_id, type_, path, path_i18n, extra)) return ret - def _launchMenu(self, menu_type, path, data=None, security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE): + 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): + 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 @@ -1064,11 +1242,14 @@ 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)) + 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=''): + def getMenuHelp(self, menu_id, language=""): """return the help string of the menu @param menu_id: id of the menu (same as callback_id) @@ -1081,6 +1262,6 @@ except KeyError: raise exceptions.DataError("Trying to access an unknown menu") languageSwitch(language) - help_string = _(menu_data['help_string']) + help_string = _(menu_data["help_string"]) languageSwitch() return help_string
--- a/sat/core/xmpp.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/core/xmpp.py Wed Jun 27 20:14:46 2018 +0200 @@ -31,6 +31,7 @@ from wokkel import component from wokkel import delay from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from zope.interface import implements @@ -53,10 +54,10 @@ self.profile = profile self.host_app = host_app self.cache = cache.Cache(host_app, profile) - self._mess_id_uid = {} # map from message id to uid used in history. Key: (full_jid,message_id) Value: uid + self._mess_id_uid = {} # map from message id to uid used in history. Key: (full_jid,message_id) Value: uid self.conn_deferred = defer.Deferred() self._progress_cb = {} # callback called when a progress is requested (key = progress id) - self.actions = {} # used to keep track of actions for retrieval (key = action_id) + self.actions = {} # used to keep track of actions for retrieval (key = action_id) ## initialisation ## @@ -117,24 +118,37 @@ # but client should not be deleted except if session is finished (independently of connection/deconnection # try: - port = int(host.memory.getParamA(C.FORCE_PORT_PARAM, "Connection", profile_key=profile)) + port = int( + host.memory.getParamA( + C.FORCE_PORT_PARAM, "Connection", profile_key=profile + ) + ) except ValueError: log.debug(_("Can't parse port value, using default value")) - port = None # will use default value 5222 or be retrieved from a DNS SRV record + port = ( + None + ) # will use default value 5222 or be retrieved from a DNS SRV record - password = yield host.memory.asyncGetParamA("Password", "Connection", profile_key=profile) - entity = host.profiles[profile] = cls(host, profile, + password = yield host.memory.asyncGetParamA( + "Password", "Connection", profile_key=profile + ) + entity = host.profiles[profile] = cls( + host, + profile, jid.JID(host.memory.getParamA("JabberID", "Connection", profile_key=profile)), - password, host.memory.getParamA(C.FORCE_SERVER_PARAM, "Connection", profile_key=profile) or None, - port, max_retries) + password, + host.memory.getParamA(C.FORCE_SERVER_PARAM, "Connection", profile_key=profile) + or None, + port, + max_retries, + ) entity._createSubProtocols() entity.fallBack = SatFallbackHandler(host) entity.fallBack.setHandlerParent(entity) - entity.versionHandler = SatVersionHandler(C.APP_NAME_FULL, - host.full_version) + entity.versionHandler = SatVersionHandler(C.APP_NAME_FULL, host.full_version) entity.versionHandler.setHandlerParent(entity) entity.identityHandler = SatIdentityHandler() @@ -162,10 +176,17 @@ log.error(_(u"Plugins initialisation error")) for idx, (success, result) in enumerate(results): if not success: - log.error(u"error (plugin %(name)s): %(failure)s" % - {'name': plugin_conn_cb[idx][0]._info['import_name'], 'failure': result}) + log.error( + u"error (plugin %(name)s): %(failure)s" + % { + "name": plugin_conn_cb[idx][0]._info["import_name"], + "failure": result, + } + ) - yield list_d.addCallback(logPluginResults) # FIXME: we should have a timeout here, and a way to know if a plugin freeze + yield list_d.addCallback( + logPluginResults + ) # FIXME: we should have a timeout here, and a way to know if a plugin freeze # TODO: mesure launch time of each plugin def getConnectionDeferred(self): @@ -190,9 +211,13 @@ self._connected.addCallback(self._disconnectionCb) self._connected.addErrback(self._disconnectionEb) - log.info(_(u"********** [{profile}] CONNECTED **********").format(profile=self.profile)) + log.info( + _(u"********** [{profile}] CONNECTED **********").format(profile=self.profile) + ) self.streamInitialized() - self.host_app.bridge.connected(self.profile, unicode(self.jid)) # we send the signal to the clients + self.host_app.bridge.connected( + self.profile, unicode(self.jid) + ) # we send the signal to the clients def _finish_connection(self, dummy): self.conn_deferred.callback(None) @@ -200,7 +225,9 @@ def streamInitialized(self): """Called after _authd""" log.debug(_(u"XML stream is initialized")) - self.keep_alife = task.LoopingCall(self.xmlstream.send, " ") # Needed to avoid disconnection (specially with openfire) + self.keep_alife = task.LoopingCall( + self.xmlstream.send, " " + ) # Needed to avoid disconnection (specially with openfire) self.keep_alife.start(C.XMPP_KEEP_ALIFE) self.disco = SatDiscoProtocol(self) @@ -215,7 +242,12 @@ disco_d.addCallback(self._finish_connection) def initializationFailed(self, reason): - log.error(_(u"ERROR: XMPP connection failed for profile '%(profile)s': %(reason)s" % {'profile': self.profile, 'reason': reason})) + log.error( + _( + u"ERROR: XMPP connection failed for profile '%(profile)s': %(reason)s" + % {"profile": self.profile, "reason": reason} + ) + ) self.conn_deferred.errback(reason.value) try: super(SatXMPPEntity, self).initializationFailed(reason) @@ -231,14 +263,24 @@ except AttributeError: log.debug(_("No keep_alife")) if self._connected is not None: - self.host_app.bridge.disconnected(self.profile) # we send the signal to the clients + self.host_app.bridge.disconnected( + self.profile + ) # we send the signal to the clients self._connected.callback(None) - self.host_app.purgeEntity(self.profile) # and we remove references to this client - log.info(_(u"********** [{profile}] DISCONNECTED **********").format(profile=self.profile)) + self.host_app.purgeEntity( + self.profile + ) # and we remove references to this client + log.info( + _(u"********** [{profile}] DISCONNECTED **********").format( + profile=self.profile + ) + ) if not self.conn_deferred.called: # FIXME: real error is not gotten here (e.g. if jid is not know by Prosody, # we should have the real error) - self.conn_deferred.errback(error.StreamError(u"Server unexpectedly closed the connection")) + self.conn_deferred.errback( + error.StreamError(u"Server unexpectedly closed the connection") + ) @defer.inlineCallbacks def _cleanConnection(self, dummy): @@ -265,7 +307,7 @@ ## sending ## - def IQ(self, type_=u'set', timeout=60): + def IQ(self, type_=u"set", timeout=60): """shortcut to create an IQ element managing deferred @param type_(unicode): IQ type ('set' or 'get') @@ -301,26 +343,28 @@ - extra @return (dict) message data """ - data['xml'] = message_elt = domish.Element((None, 'message')) + data["xml"] = message_elt = domish.Element((None, "message")) message_elt["to"] = data["to"].full() - message_elt["from"] = data['from'].full() + message_elt["from"] = data["from"].full() message_elt["type"] = data["type"] - if data['uid']: # key must be present but can be set to '' - # by a plugin to avoid id on purpose - message_elt['id'] = data['uid'] + if data["uid"]: # key must be present but can be set to '' + # by a plugin to avoid id on purpose + message_elt["id"] = data["uid"] for lang, subject in data["subject"].iteritems(): subject_elt = message_elt.addElement("subject", content=subject) if lang: - subject_elt[(C.NS_XML, 'lang')] = lang + subject_elt[(C.NS_XML, "lang")] = lang for lang, message in data["message"].iteritems(): body_elt = message_elt.addElement("body", content=message) if lang: - body_elt[(C.NS_XML, 'lang')] = lang + body_elt[(C.NS_XML, "lang")] = lang try: - thread = data['extra']['thread'] + thread = data["extra"]["thread"] except KeyError: - if 'thread_parent' in data['extra']: - raise exceptions.InternalError(u"thread_parent found while there is not associated thread") + if "thread_parent" in data["extra"]: + raise exceptions.InternalError( + u"thread_parent found while there is not associated thread" + ) else: thread_elt = message_elt.addElement("thread", content=thread) try: @@ -336,7 +380,16 @@ """ raise NotImplementedError - def sendMessage(self, to_jid, message, subject=None, mess_type='auto', extra=None, uid=None, no_trigger=False): + def sendMessage( + self, + to_jid, + message, + subject=None, + mess_type="auto", + extra=None, + uid=None, + no_trigger=False, + ): """Send a message to an entity @param to_jid(jid.JID): destinee of the message @@ -371,18 +424,26 @@ "extra": extra, "timestamp": time.time(), } - pre_xml_treatments = defer.Deferred() # XXX: plugin can add their pre XML treatments to this deferred - post_xml_treatments = defer.Deferred() # XXX: plugin can add their post XML treatments to this deferred + pre_xml_treatments = ( + defer.Deferred() + ) # XXX: plugin can add their pre XML treatments to this deferred + post_xml_treatments = ( + defer.Deferred() + ) # XXX: plugin can add their post XML treatments to this deferred if data["type"] == C.MESS_TYPE_AUTO: # we try to guess the type if data["subject"]: data["type"] = C.MESS_TYPE_NORMAL - elif not data["to"].resource: # if to JID has a resource, the type is not 'groupchat' + elif not data[ + "to" + ].resource: # if to JID has a resource, the type is not 'groupchat' # we may have a groupchat message, we check if the we know this jid try: - entity_type = self.host_app.memory.getEntityData(data["to"], ['type'], self.profile)["type"] - #FIXME: should entity_type manage resources ? + entity_type = self.host_app.memory.getEntityData( + data["to"], ["type"], self.profile + )["type"] + # FIXME: should entity_type manage resources ? except (exceptions.UnknownEntityError, KeyError): entity_type = "contact" @@ -397,19 +458,33 @@ # FIXME: send_only is used by libervia's OTR plugin to avoid # the triggers from frontend, and no_trigger do the same # thing internally, this could be unified - send_only = data['extra'].get('send_only', False) + send_only = data["extra"].get("send_only", False) if not no_trigger and not send_only: - if not self.host_app.trigger.point("sendMessage" + self.trigger_suffix, self, data, pre_xml_treatments, post_xml_treatments): + if not self.host_app.trigger.point( + "sendMessage" + self.trigger_suffix, + self, + data, + pre_xml_treatments, + post_xml_treatments, + ): return defer.succeed(None) - log.debug(_(u"Sending message (type {type}, to {to})").format(type=data["type"], to=to_jid.full())) + log.debug( + _(u"Sending message (type {type}, to {to})").format( + type=data["type"], to=to_jid.full() + ) + ) pre_xml_treatments.addCallback(lambda dummy: self.generateMessageXML(data)) pre_xml_treatments.chainDeferred(post_xml_treatments) post_xml_treatments.addCallback(self.sendMessageData) if send_only: - log.debug(_("Triggers, storage and echo have been inhibited by the 'send_only' parameter")) + log.debug( + _( + "Triggers, storage and echo have been inhibited by the 'send_only' parameter" + ) + ) else: self.addPostXmlCallbacks(post_xml_treatments) post_xml_treatments.addErrback(self._cancelErrorTrap) @@ -430,10 +505,12 @@ if data[u"type"] != C.MESS_TYPE_GROUPCHAT: # we don't add groupchat message to history, as we get them back # and they will be added then - if data[u'message'] or data[u'subject']: # we need a message to store + if data[u"message"] or data[u"subject"]: # we need a message to store self.host_app.memory.addToHistory(self, data) else: - log.warning(u"No message found") # empty body should be managed by plugins before this point + log.warning( + u"No message found" + ) # empty body should be managed by plugins before this point return data def messageSendToBridge(self, data): @@ -445,11 +522,23 @@ if data[u"type"] != C.MESS_TYPE_GROUPCHAT: # we don't send groupchat message to bridge, as we get them back # and they will be added the - if data[u'message'] or data[u'subject']: # we need a message to send something + if ( + data[u"message"] or data[u"subject"] + ): # we need a message to send something # We send back the message, so all frontends are aware of it - self.host_app.bridge.messageNew(data[u'uid'], data[u'timestamp'], data[u'from'].full(), data[u'to'].full(), data[u'message'], data[u'subject'], data[u'type'], data[u'extra'], profile=self.profile) + self.host_app.bridge.messageNew( + data[u"uid"], + data[u"timestamp"], + data[u"from"].full(), + data[u"to"].full(), + data[u"message"], + data[u"subject"], + data[u"type"], + data[u"extra"], + profile=self.profile, + ) else: - log.warning(_(u"No message found")) + log.warning(_(u"No message found")) return data @@ -458,7 +547,16 @@ trigger_suffix = "" is_component = False - def __init__(self, host_app, profile, user_jid, password, host=None, port=C.XMPP_C2S_PORT, max_retries=C.XMPP_MAX_RETRIES): + def __init__( + self, + host_app, + profile, + user_jid, + password, + host=None, + port=C.XMPP_C2S_PORT, + max_retries=C.XMPP_MAX_RETRIES, + ): # XXX: DNS SRV records are checked when the host is not specified. # If no SRV record is found, the host is directly extracted from the JID. self.started = time.time() @@ -482,25 +580,30 @@ if isinstance(host_data, basestring): host = host_data elif isinstance(host_data, dict): - if u'host' in host_data: - host = host_data[u'host'] - if u'port' in host_data: - port = host_data[u'port'] + if u"host" in host_data: + host = host_data[u"host"] + if u"port" in host_data: + port = host_data[u"port"] else: - log.warning(_(u"invalid data used for host: {data}").format(data=host_data)) + log.warning( + _(u"invalid data used for host: {data}").format(data=host_data) + ) host_data = None if host_data is not None: - log.info(u"using {host}:{port} for host {host_ori} as requested in config".format( - host_ori = user_jid.host, - host = host, - port = port)) + log.info( + u"using {host}:{port} for host {host_ori} as requested in config".format( + host_ori=user_jid.host, host=host, port=port + ) + ) - wokkel_client.XMPPClient.__init__(self, user_jid, password, host or None, port or C.XMPP_C2S_PORT) + wokkel_client.XMPPClient.__init__( + self, user_jid, password, host or None, port or C.XMPP_C2S_PORT + ) SatXMPPEntity.__init__(self, host_app, profile, max_retries) def _getPluginsList(self): for p in self.host_app.plugins.itervalues(): - if C.PLUG_MODE_CLIENT in p._info[u'modes']: + if C.PLUG_MODE_CLIENT in p._info[u"modes"]: yield p def _createSubProtocols(self): @@ -531,9 +634,9 @@ # (out of band transmission for instance). # e2e should have a priority of 0 here, and out of band transmission # a lower priority - # FIXME: trigger not used yet, can be uncommented when e2e full stanza encryption is implemented - # if not self.host_app.trigger.point("send", self, obj): - # return + # FIXME: trigger not used yet, can be uncommented when e2e full stanza encryption is implemented + # if not self.host_app.trigger.point("send", self, obj): + # return super(SatXMPPClient, self).send(obj) def sendMessageData(self, mess_data): @@ -549,7 +652,7 @@ # This is intented for e2e encryption which doesn't do full stanza encryption (e.g. OTR) # This trigger point can't cancel the method self.host_app.trigger.point("sendMessageData", self, mess_data) - self.send(mess_data[u'xml']) + self.send(mess_data[u"xml"]) return mess_data def feedback(self, to_jid, message): @@ -560,15 +663,17 @@ @param to_jid(jid.JID): destinee jid @param message(unicode): message to send to frontends """ - self.host_app.bridge.messageNew(uid=unicode(uuid.uuid4()), - timestamp=time.time(), - from_jid=self.jid.full(), - to_jid=to_jid.full(), - message={u'': message}, - subject={}, - mess_type=C.MESS_TYPE_INFO, - extra={}, - profile=self.profile) + self.host_app.bridge.messageNew( + uid=unicode(uuid.uuid4()), + timestamp=time.time(), + from_jid=self.jid.full(), + to_jid=to_jid.full(), + message={u"": message}, + subject={}, + mess_type=C.MESS_TYPE_INFO, + extra={}, + profile=self.profile, + ) def _finish_connection(self, dummy): self.roster.requestRoster() @@ -583,12 +688,26 @@ An entry point plugin is launched after component is connected. Component need to instantiate MessageProtocol itself """ + implements(iwokkel.IDisco) - trigger_suffix = "Component" # used for to distinguish some trigger points set in SatXMPPEntity + trigger_suffix = ( + "Component" + ) # used for to distinguish some trigger points set in SatXMPPEntity is_component = True - sendHistory = False # XXX: set to True from entry plugin to keep messages in history for received messages + sendHistory = ( + False + ) # XXX: set to True from entry plugin to keep messages in history for received messages - def __init__(self, host_app, profile, component_jid, password, host=None, port=None, max_retries=C.XMPP_MAX_RETRIES): + def __init__( + self, + host_app, + profile, + component_jid, + password, + host=None, + port=None, + max_retries=C.XMPP_MAX_RETRIES, + ): self.started = time.time() if port is None: port = C.XMPP_COMPONENT_PORT @@ -598,15 +717,18 @@ try: self.entry_plugin = host_app.plugins[entry_point] except KeyError: - raise exceptions.NotFound(_(u"The requested entry point ({entry_point}) is not available").format( - entry_point = entry_point)) + raise exceptions.NotFound( + _(u"The requested entry point ({entry_point}) is not available").format( + entry_point=entry_point + ) + ) self.identities = [disco.DiscoIdentity(u"component", u"generic", C.APP_NAME)] # jid is set automatically on bind by Twisted for Client, but not for Component self.jid = component_jid if host is None: try: - host = component_jid.host.split(u'.', 1)[1] + host = component_jid.host.split(u".", 1)[1] except IndexError: raise ValueError(u"Can't guess host from jid, please specify a host") # XXX: component.Component expect unicode jid, while Client expect jid.JID. @@ -628,14 +750,18 @@ @raise InternalError: one of the plugin is not handling components @raise KeyError: one plugin should be present in self.host_app.plugins but it is not """ - if C.PLUG_MODE_COMPONENT not in current._info[u'modes']: + if C.PLUG_MODE_COMPONENT not in current._info[u"modes"]: if not required: return else: - log.error(_(u"Plugin {current_name} is needed for {entry_name}, but it doesn't handle component mode").format( - current_name = current._info[u'import_name'], - entry_name = self.entry_plugin._info[u'import_name'] - )) + log.error( + _( + u"Plugin {current_name} is needed for {entry_name}, but it doesn't handle component mode" + ).format( + current_name=current._info[u"import_name"], + entry_name=self.entry_plugin._info[u"import_name"], + ) + ) raise exceptions.InternalError(_(u"invalid plugin mode")) for import_name in current._info.get(C.PI_DEPENDENCIES, []): @@ -651,7 +777,7 @@ dep = self.host_app.plugins[import_name] except KeyError: continue - self._buildDependencies(dep, plugins, required = False) + self._buildDependencies(dep, plugins, required=False) if current not in plugins: # current can be required for several plugins and so @@ -680,7 +806,6 @@ class SatMessageProtocol(xmppim.MessageProtocol): - def __init__(self, host): xmppim.MessageProtocol.__init__(self) self.host = host @@ -697,52 +822,60 @@ message = {} subject = {} extra = {} - data = {"from": jid.JID(message_elt['from']), - "to": jid.JID(message_elt['to']), - "uid": message_elt.getAttribute('uid', unicode(uuid.uuid4())), # XXX: uid is not a standard attribute but may be added by plugins - "message": message, - "subject": subject, - "type": message_elt.getAttribute('type', 'normal'), - "extra": extra} + data = { + "from": jid.JID(message_elt["from"]), + "to": jid.JID(message_elt["to"]), + "uid": message_elt.getAttribute( + "uid", unicode(uuid.uuid4()) + ), # XXX: uid is not a standard attribute but may be added by plugins + "message": message, + "subject": subject, + "type": message_elt.getAttribute("type", "normal"), + "extra": extra, + } if client is not None: try: - data['stanza_id'] = message_elt['id'] + data["stanza_id"] = message_elt["id"] except KeyError: pass else: - client._mess_id_uid[(data['from'], data['stanza_id'])] = data['uid'] + client._mess_id_uid[(data["from"], data["stanza_id"])] = data["uid"] # message - for e in message_elt.elements(C.NS_CLIENT, 'body'): - message[e.getAttribute((C.NS_XML,'lang'),'')] = unicode(e) + for e in message_elt.elements(C.NS_CLIENT, "body"): + message[e.getAttribute((C.NS_XML, "lang"), "")] = unicode(e) # subject - for e in message_elt.elements(C.NS_CLIENT, 'subject'): - subject[e.getAttribute((C.NS_XML, 'lang'),'')] = unicode(e) + for e in message_elt.elements(C.NS_CLIENT, "subject"): + subject[e.getAttribute((C.NS_XML, "lang"), "")] = unicode(e) # delay and timestamp try: - delay_elt = message_elt.elements(delay.NS_DELAY, 'delay').next() + delay_elt = message_elt.elements(delay.NS_DELAY, "delay").next() except StopIteration: - data['timestamp'] = time.time() + data["timestamp"] = time.time() else: parsed_delay = delay.Delay.fromElement(delay_elt) - data['timestamp'] = calendar.timegm(parsed_delay.stamp.utctimetuple()) - data['received_timestamp'] = unicode(time.time()) + data["timestamp"] = calendar.timegm(parsed_delay.stamp.utctimetuple()) + data["received_timestamp"] = unicode(time.time()) if parsed_delay.sender: - data['delay_sender'] = parsed_delay.sender.full() + data["delay_sender"] = parsed_delay.sender.full() return data def onMessage(self, message_elt): # TODO: handle threads client = self.parent - if not 'from' in message_elt.attributes: - message_elt['from'] = client.jid.host - log.debug(_(u"got message from: {from_}").format(from_=message_elt['from'])) - post_treat = defer.Deferred() # XXX: plugin can add their treatments to this deferred + if not "from" in message_elt.attributes: + message_elt["from"] = client.jid.host + log.debug(_(u"got message from: {from_}").format(from_=message_elt["from"])) + post_treat = ( + defer.Deferred() + ) # XXX: plugin can add their treatments to this deferred - if not self.host.trigger.point("MessageReceived", client, message_elt, post_treat): + if not self.host.trigger.point( + "MessageReceived", client, message_elt, post_treat + ): return data = self.parseMessage(message_elt, client) @@ -754,25 +887,35 @@ post_treat.callback(data) def skipEmptyMessage(self, data): - if not data['message'] and not data['extra'] and not data['subject']: + if not data["message"] and not data["extra"] and not data["subject"]: raise failure.Failure(exceptions.CancelError("Cancelled empty message")) return data def addToHistory(self, data, client): - if data.pop(u'history', None) == C.HISTORY_SKIP: - log.info(u'history is skipped as requested') - data[u'extra'][u'history'] = C.HISTORY_SKIP + if data.pop(u"history", None) == C.HISTORY_SKIP: + log.info(u"history is skipped as requested") + data[u"extra"][u"history"] = C.HISTORY_SKIP else: return self.host.memory.addToHistory(client, data) def bridgeSignal(self, dummy, client, data): try: - data['extra']['received_timestamp'] = data['received_timestamp'] - data['extra']['delay_sender'] = data['delay_sender'] + data["extra"]["received_timestamp"] = data["received_timestamp"] + data["extra"]["delay_sender"] = data["delay_sender"] except KeyError: pass if data is not None: - self.host.bridge.messageNew(data['uid'], data['timestamp'], data['from'].full(), data['to'].full(), data['message'], data['subject'], data['type'], data['extra'], profile=client.profile) + self.host.bridge.messageNew( + data["uid"], + data["timestamp"], + data["from"].full(), + data["to"].full(), + data["message"], + data["subject"], + data["type"], + data["extra"], + profile=client.profile, + ) return data def cancelErrorTrap(self, failure_): @@ -781,26 +924,29 @@ class SatRosterProtocol(xmppim.RosterClientProtocol): - def __init__(self, host): xmppim.RosterClientProtocol.__init__(self) self.host = host - self.got_roster = defer.Deferred() # called when roster is received and ready - #XXX: the two following dicts keep a local copy of the roster + self.got_roster = defer.Deferred() # called when roster is received and ready + # XXX: the two following dicts keep a local copy of the roster self._groups = {} # map from groups to jids: key=group value=set of jids self._jids = None # map from jids to RosterItem: key=jid value=RosterItem def rosterCb(self, roster): - assert roster is not None # FIXME: must be managed with roster versioning + assert roster is not None # FIXME: must be managed with roster versioning self._groups.clear() self._jids = roster for item in roster.itervalues(): if not item.subscriptionTo and not item.subscriptionFrom and not item.ask: - #XXX: current behaviour: we don't want contact in our roster list + # XXX: current behaviour: we don't want contact in our roster list # if there is no presence subscription # may change in the future - log.info(u"Removing contact {} from roster because there is no presence subscription".format(item.jid)) - self.removeItem(item.entity) # FIXME: to be checked + log.info( + u"Removing contact {} from roster because there is no presence subscription".format( + item.jid + ) + ) + self.removeItem(item.entity) # FIXME: to be checked else: self._registerItem(item) @@ -812,10 +958,16 @@ """ log.debug(u"registering item: {}".format(item.entity.full())) if item.entity.resource: - 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.") + 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: if not item.subscriptionFrom: - log.info(_(u"There's no subscription between you and [{}]!").format(item.entity.full())) + log.info( + _(u"There's no subscription between you and [{}]!").format( + item.entity.full() + ) + ) else: log.info(_(u"You are not subscribed to [{}]!").format(item.entity.full())) if not item.subscriptionFrom: @@ -843,16 +995,17 @@ @param item: RosterItem @return: dictionary of attributes """ - item_attr = {'to': unicode(item.subscriptionTo), - 'from': unicode(item.subscriptionFrom), - 'ask': unicode(item.ask) - } + item_attr = { + "to": unicode(item.subscriptionTo), + "from": unicode(item.subscriptionFrom), + "ask": unicode(item.ask), + } if item.name: - item_attr['name'] = item.name + item_attr["name"] = item.name return item_attr def setReceived(self, request): - #TODO: implement roster versioning (cf RFC 6121 §2.6) + # TODO: implement roster versioning (cf RFC 6121 §2.6) item = request.item try: # update the cache for the groups the contact has been removed from left_groups = set(self._jids[item.entity].groups).difference(item.groups) @@ -865,7 +1018,9 @@ pass # no previous item registration (or it's been cleared) self._jids[item.entity] = item self._registerItem(item) - self.host.bridge.newContact(item.entity.full(), self.getAttributes(item), item.groups, self.parent.profile) + self.host.bridge.newContact( + item.entity.full(), self.getAttributes(item), item.groups, self.parent.profile + ) def removeReceived(self, request): entity = request.item.entity @@ -875,7 +1030,11 @@ try: item = self._jids.pop(entity) except KeyError: - log.error(u"Received a roster remove event for an item not in cache ({})".format(entity)) + log.error( + u"Received a roster remove event for an item not in cache ({})".format( + entity + ) + ) return for group in item.groups: try: @@ -884,8 +1043,10 @@ if not jids_set: del self._groups[group] except KeyError: - log.warning(u"there is no cache for the group [%(group)s] of the removed roster item [%(jid)s]" % - {"group": group, "jid": entity}) + log.warning( + u"there is no cache for the group [%(group)s] of the removed roster item [%(jid)s]" + % {"group": group, "jid": entity} + ) # then we send the bridge signal self.host.bridge.contactDeleted(entity.full(), self.parent.profile) @@ -939,7 +1100,7 @@ @return (set(jid.JID)): set of selected jids """ if type_ == C.ALL and groups is not None: - raise ValueError('groups must not be set for {} type'.format(C.ALL)) + raise ValueError("groups must not be set for {} type".format(C.ALL)) if type_ == C.ALL: return set(self.getJids()) @@ -949,7 +1110,7 @@ jids.update(self.getJidsFromGroup(group)) return jids else: - raise ValueError(u'Unexpected type_ {}'.format(type_)) + raise ValueError(u"Unexpected type_ {}".format(type_)) def getNick(self, entity_jid): """Return a nick name for an entity @@ -965,7 +1126,6 @@ class SatPresenceProtocol(xmppim.PresenceClientProtocol): - def __init__(self, host): xmppim.PresenceClientProtocol.__init__(self) self.host = host @@ -977,7 +1137,17 @@ presence_d.addCallback(lambda __: super(SatPresenceProtocol, self).send(obj)) def availableReceived(self, entity, show=None, statuses=None, priority=0): - log.debug(_(u"presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)") % {'entity': entity, C.PRESENCE_SHOW: show, C.PRESENCE_STATUSES: statuses, C.PRESENCE_PRIORITY: priority}) + log.debug( + _( + u"presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)" + ) + % { + "entity": entity, + C.PRESENCE_SHOW: show, + C.PRESENCE_STATUSES: statuses, + C.PRESENCE_PRIORITY: priority, + } + ) if not statuses: statuses = {} @@ -985,20 +1155,25 @@ if None in statuses: # we only want string keys statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop(None) - if not self.host.trigger.point("presenceReceived", entity, show, priority, statuses, self.parent.profile): + if not self.host.trigger.point( + "presenceReceived", entity, show, priority, statuses, self.parent.profile + ): return - self.host.memory.setPresenceStatus(entity, show or "", - int(priority), statuses, - self.parent.profile) + self.host.memory.setPresenceStatus( + entity, show or "", int(priority), statuses, self.parent.profile + ) # now it's time to notify frontends - self.host.bridge.presenceUpdate(entity.full(), show or "", - int(priority), statuses, - self.parent.profile) + self.host.bridge.presenceUpdate( + entity.full(), show or "", int(priority), statuses, self.parent.profile + ) def unavailableReceived(self, entity, statuses=None): - log.debug(_(u"presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)") % {'entity': entity, C.PRESENCE_STATUSES: statuses}) + log.debug( + _(u"presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)") + % {"entity": entity, C.PRESENCE_STATUSES: statuses} + ) if not statuses: statuses = {} @@ -1006,21 +1181,33 @@ if None in statuses: # we only want string keys statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop(None) - if not self.host.trigger.point("presenceReceived", entity, "unavailable", 0, statuses, self.parent.profile): + if not self.host.trigger.point( + "presenceReceived", entity, "unavailable", 0, statuses, self.parent.profile + ): return # now it's time to notify frontends # if the entity is not known yet in this session or is already unavailable, there is no need to send an unavailable signal try: - presence = self.host.memory.getEntityDatum(entity, "presence", self.parent.profile) + presence = self.host.memory.getEntityDatum( + entity, "presence", self.parent.profile + ) except (KeyError, exceptions.UnknownEntityError): # the entity has not been seen yet in this session pass else: if presence.show != C.PRESENCE_UNAVAILABLE: - self.host.bridge.presenceUpdate(entity.full(), C.PRESENCE_UNAVAILABLE, 0, statuses, self.parent.profile) + self.host.bridge.presenceUpdate( + entity.full(), + C.PRESENCE_UNAVAILABLE, + 0, + statuses, + self.parent.profile, + ) - self.host.memory.setPresenceStatus(entity, C.PRESENCE_UNAVAILABLE, 0, statuses, self.parent.profile) + self.host.memory.setPresenceStatus( + entity, C.PRESENCE_UNAVAILABLE, 0, statuses, self.parent.profile + ) def available(self, entity=None, show=None, statuses=None, priority=None): """Set a presence and statuses. @@ -1032,7 +1219,11 @@ """ if priority is None: try: - priority = int(self.host.memory.getParamA("Priority", "Connection", profile_key=self.parent.profile)) + priority = int( + self.host.memory.getParamA( + "Priority", "Connection", profile_key=self.parent.profile + ) + ) except ValueError: priority = 0 @@ -1048,7 +1239,7 @@ # ... before switching back if None in statuses: - statuses['default'] = statuses.pop(None) + statuses["default"] = statuses.pop(None) if not self.host.trigger.point("presence_available", presence_elt, self.parent): return @@ -1060,7 +1251,9 @@ xmppim.PresenceClientProtocol.subscribed(self, entity) self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile) item = self.parent.roster.getItem(entity) - if not item or not item.subscriptionTo: # we automatically subscribe to 'to' presence + if ( + not item or not item.subscriptionTo + ): # we automatically subscribe to 'to' presence log.debug(_('sending automatic "from" subscription request')) self.subscribe(entity) @@ -1070,11 +1263,11 @@ def subscribedReceived(self, entity): log.debug(_(u"subscription approved for [%s]") % entity.userhost()) - self.host.bridge.subscribe('subscribed', entity.userhost(), self.parent.profile) + self.host.bridge.subscribe("subscribed", entity.userhost(), self.parent.profile) def unsubscribedReceived(self, entity): log.debug(_(u"unsubscription confirmed for [%s]") % entity.userhost()) - self.host.bridge.subscribe('unsubscribed', entity.userhost(), self.parent.profile) + self.host.bridge.subscribe("unsubscribed", entity.userhost(), self.parent.profile) @defer.inlineCallbacks def subscribeReceived(self, entity): @@ -1083,11 +1276,15 @@ item = self.parent.roster.getItem(entity) if item and item.subscriptionTo: # We automatically accept subscription if we are already subscribed to contact presence - log.debug(_('sending automatic subscription acceptance')) + log.debug(_("sending automatic subscription acceptance")) self.subscribed(entity) else: - self.host.memory.addWaitingSub('subscribe', entity.userhost(), self.parent.profile) - self.host.bridge.subscribe('subscribe', entity.userhost(), self.parent.profile) + self.host.memory.addWaitingSub( + "subscribe", entity.userhost(), self.parent.profile + ) + self.host.bridge.subscribe( + "subscribe", entity.userhost(), self.parent.profile + ) @defer.inlineCallbacks def unsubscribeReceived(self, entity): @@ -1095,9 +1292,9 @@ yield self.parent.roster.got_roster item = self.parent.roster.getItem(entity) if item and item.subscriptionFrom: # we automatically remove contact - log.debug(_('automatic contact deletion')) + log.debug(_("automatic contact deletion")) self.host.delContact(entity, self.parent.profile) - self.host.bridge.subscribe('unsubscribe', entity.userhost(), self.parent.profile) + self.host.bridge.subscribe("unsubscribe", entity.userhost(), self.parent.profile) class SatDiscoProtocol(disco.DiscoClientProtocol): @@ -1117,9 +1314,8 @@ class SatVersionHandler(generic.VersionHandler): - def getDiscoInfo(self, requestor, target, node): - #XXX: We need to work around wokkel's behaviour (namespace not added if there is a + # XXX: We need to work around wokkel's behaviour (namespace not added if there is a # node) as it cause issues with XEP-0115 & PEP (XEP-0163): there is a node when server # ask for disco info, and not when we generate the key, so the hash is used with different # disco features, and when the server (seen on ejabberd) generate its own hash for security check @@ -1131,11 +1327,12 @@ """ Manage disco Identity of SàT. """ - #TODO: dynamic identity update (see docstring). Note that a XMPP entity can have several identities + + # TODO: dynamic identity update (see docstring). Note that a XMPP entity can have several identities implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return self.parent.identities - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/memory/cache.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/memory/cache.py Wed Jun 27 20:14:46 2018 +0200 @@ -18,6 +18,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.core.log import getLogger + log = getLogger(__name__) from sat.tools.common import regex from sat.core import exceptions @@ -27,7 +28,7 @@ import os.path import time -DEFAULT_EXT = '.raw' +DEFAULT_EXT = ".raw" class Cache(object): @@ -39,11 +40,11 @@ if None, the cache will be common for all profiles """ self.profile = profile - path_elts = [host.memory.getConfig('', 'local_dir'), C.CACHE_DIR] + path_elts = [host.memory.getConfig("", "local_dir"), C.CACHE_DIR] if profile: - path_elts.extend([u'profiles',regex.pathEscape(profile)]) + path_elts.extend([u"profiles", regex.pathEscape(profile)]) else: - path_elts.append(u'common') + path_elts.append(u"common") self.cache_dir = os.path.join(*path_elts) if not os.path.exists(self.cache_dir): @@ -54,8 +55,10 @@ @param filename(unicode): cached file name (cache data or actual file) """ - if not filename or u'/' in filename: - log.error(u"invalid char found in file name, hack attempt? name:{}".format(filename)) + if not filename or u"/" in filename: + log.error( + u"invalid char found in file name, hack attempt? name:{}".format(filename) + ) raise exceptions.DataError(u"Invalid char found") return os.path.join(self.cache_dir, filename) @@ -76,26 +79,27 @@ return None try: - with open(cache_url, 'rb') as f: + with open(cache_url, "rb") as f: cache_data = pickle.load(f) except IOError: log.warning(u"can't read cache at {}".format(cache_url)) return None except pickle.UnpicklingError: - log.warning(u'invalid cache found at {}'.format(cache_url)) + log.warning(u"invalid cache found at {}".format(cache_url)) return None try: - eol = cache_data['eol'] + eol = cache_data["eol"] except KeyError: - log.warning(u'no End Of Life found for cached file {}'.format(uid)) + log.warning(u"no End Of Life found for cached file {}".format(uid)) eol = 0 if eol < time.time(): - log.debug(u"removing expired cache (expired for {}s)".format( - time.time() - eol)) + log.debug( + u"removing expired cache (expired for {}s)".format(time.time() - eol) + ) return None - cache_data['path'] = self.getPath(cache_data['filename']) + cache_data["path"] = self.getPath(cache_data["filename"]) return cache_data def getFilePath(self, uid): @@ -107,7 +111,7 @@ """ metadata = self.getMetadata(uid) if metadata is not None: - return metadata['path'] + return metadata["path"] def cacheData(self, source, uid, mime_type=None, max_age=None, filename=None): """create cache metadata and file object to use for actual data @@ -130,24 +134,27 @@ if mime_type: ext = mimetypes.guess_extension(mime_type, strict=False) if ext is None: - log.warning(u"can't find extension for MIME type {}".format(mime_type)) + log.warning( + u"can't find extension for MIME type {}".format(mime_type) + ) ext = DEFAULT_EXT - elif ext == u'.jpe': - ext = u'.jpg' + elif ext == u".jpe": + ext = u".jpg" else: ext = DEFAULT_EXT mime_type = None filename = uid + ext if max_age is None: max_age = C.DEFAULT_MAX_AGE - cache_data = {u'source': source, - u'filename': filename, - u'eol': int(time.time()) + max_age, - u'mime_type': mime_type, - } + cache_data = { + u"source": source, + u"filename": filename, + u"eol": int(time.time()) + max_age, + u"mime_type": mime_type, + } file_path = self.getPath(filename) - with open(cache_url, 'wb') as f: + with open(cache_url, "wb") as f: pickle.dump(cache_data, f, protocol=2) - return open(file_path, 'wb') + return open(file_path, "wb")
--- a/sat/memory/crypto.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/memory/crypto.py Wed Jun 27 20:14:46 2018 +0200 @@ -47,13 +47,17 @@ @param leave_empty (bool): if True, empty text will be returned "as is" @return: Deferred: base-64 encoded str """ - if leave_empty and text == '': + if leave_empty and text == "": return succeed(text) iv = BlockCipher.getRandomKey() - key = key.encode('utf-8') - key = key[:BlockCipher.MAX_KEY_SIZE] if len(key) >= BlockCipher.MAX_KEY_SIZE else BlockCipher.pad(key) + key = key.encode("utf-8") + key = ( + key[: BlockCipher.MAX_KEY_SIZE] + if len(key) >= BlockCipher.MAX_KEY_SIZE + else BlockCipher.pad(key) + ) cipher = AES.new(key, AES.MODE_CFB, iv) - d = deferToThread(cipher.encrypt, BlockCipher.pad(text.encode('utf-8'))) + d = deferToThread(cipher.encrypt, BlockCipher.pad(text.encode("utf-8"))) d.addCallback(lambda ciphertext: b64encode(iv + ciphertext)) return d @@ -68,12 +72,19 @@ @param leave_empty (bool): if True, empty ciphertext will be returned "as is" @return: Deferred: str or None if the password could not be decrypted """ - if leave_empty and ciphertext == '': - return succeed('') + if leave_empty and ciphertext == "": + return succeed("") ciphertext = b64decode(ciphertext) - iv, ciphertext = ciphertext[:BlockCipher.IV_SIZE], ciphertext[BlockCipher.IV_SIZE:] - key = key.encode('utf-8') - key = key[:BlockCipher.MAX_KEY_SIZE] if len(key) >= BlockCipher.MAX_KEY_SIZE else BlockCipher.pad(key) + iv, ciphertext = ( + ciphertext[: BlockCipher.IV_SIZE], + ciphertext[BlockCipher.IV_SIZE :], + ) + key = key.encode("utf-8") + key = ( + key[: BlockCipher.MAX_KEY_SIZE] + if len(key) >= BlockCipher.MAX_KEY_SIZE + else BlockCipher.pad(key) + ) cipher = AES.new(key, AES.MODE_CFB, iv) d = deferToThread(cipher.decrypt, ciphertext) d.addCallback(lambda text: BlockCipher.unpad(text)) @@ -81,7 +92,7 @@ # a decrypted empty value and a decryption failure... both return # the empty value. Fortunately, we detect empty passwords beforehand # thanks to the "leave_empty" parameter which is used by default. - d.addCallback(lambda text: text.decode('utf-8') if text else None) + d.addCallback(lambda text: text.decode("utf-8") if text else None) return d @classmethod @@ -108,7 +119,7 @@ @classmethod def unpad(self, s): """Method from http://stackoverflow.com/a/12525165""" - return s[0:-ord(s[-1])] + return s[0 : -ord(s[-1])] class PasswordHasher(object): @@ -124,9 +135,13 @@ @param leave_empty (bool): if True, empty password will be returned "as is" @return: Deferred: base-64 encoded str """ - if leave_empty and password == '': + if leave_empty and password == "": return succeed(password) - salt = b64decode(salt)[:PasswordHasher.SALT_LEN] if salt else urandom(PasswordHasher.SALT_LEN) + salt = ( + b64decode(salt)[: PasswordHasher.SALT_LEN] + if salt + else urandom(PasswordHasher.SALT_LEN) + ) d = deferToThread(PBKDF2, password, salt) d.addCallback(lambda hashed: b64encode(salt + hashed)) return d @@ -139,7 +154,7 @@ @param hashed (str): the hash of the password @return: Deferred: boolean """ - leave_empty = hashed == '' + leave_empty = hashed == "" d = PasswordHasher.hash(attempt, hashed, leave_empty) d.addCallback(lambda hashed_attempt: hashed_attempt == hashed) return d
--- a/sat/memory/disco.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/memory/disco.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.protocols.jabber import jid from twisted.words.protocols.jabber.error import StanzaError @@ -35,7 +36,8 @@ TIMEOUT = 15 -CAP_HASH_ERROR = 'ERROR' +CAP_HASH_ERROR = "ERROR" + class HashGenerationError(Exception): pass @@ -46,10 +48,10 @@ def __init__(self, identity, lang=None): assert isinstance(identity, disco.DiscoIdentity) - self.category = identity.category.encode('utf-8') - self.idType = identity.type.encode('utf-8') - self.name = identity.name.encode('utf-8') if identity.name else '' - self.lang = lang.encode('utf-8') if lang is not None else '' + self.category = identity.category.encode("utf-8") + self.idType = identity.type.encode("utf-8") + self.name = identity.name.encode("utf-8") if identity.name else "" + self.lang = lang.encode("utf-8") if lang is not None else "" def __str__(self): return "%s/%s/%s/%s" % (self.category, self.idType, self.lang, self.name) @@ -63,8 +65,8 @@ def __init__(self, persistent): self.hashes = { - CAP_HASH_ERROR: disco.DiscoInfo(), # used when we can't get disco infos - } + CAP_HASH_ERROR: disco.DiscoInfo() # used when we can't get disco infos + } self.persistent = persistent def __getitem__(self, key): @@ -86,12 +88,16 @@ element = xml_tools.ElementParser()(xml) disco_info = disco.DiscoInfo.fromElement(element) if not disco_info.features and not disco_info.identities: - log.warning(_(u"no feature/identity found in disco element (hash: {cap_hash}), ignoring: {xml}").format( - cap_hash=hash_, xml=xml)) + log.warning( + _( + u"no feature/identity found in disco element (hash: {cap_hash}), ignoring: {xml}" + ).format(cap_hash=hash_, xml=xml) + ) else: self.hashes[hash_] = disco_info log.info(u"Disco hashes loaded") + d = self.persistent.load() d.addCallback(fillHashes) return d @@ -110,7 +116,7 @@ return self.hashes.load() @defer.inlineCallbacks - def hasFeature(self, client, feature, jid_=None, node=u''): + def hasFeature(self, client, feature, jid_=None, node=u""): """Tell if an entity has the required feature @param feature: feature namespace @@ -122,7 +128,7 @@ defer.returnValue(feature in disco_infos.features) @defer.inlineCallbacks - def checkFeature(self, client, feature, jid_=None, node=u''): + def checkFeature(self, client, feature, jid_=None, node=u""): """Like hasFeature, but raise an exception is feature is not Found @param feature: feature namespace @@ -136,7 +142,7 @@ raise failure.Failure(exceptions.FeatureNotFound) @defer.inlineCallbacks - def checkFeatures(self, client, features, jid_=None, identity=None, node=u''): + def checkFeatures(self, client, features, jid_=None, identity=None, node=u""): """Like checkFeature, but check several features at once, and check also identity @param features(iterable[unicode]): features to check @@ -153,7 +159,7 @@ if identity is not None and identity not in disco_infos.identities: raise failure.Failure(exceptions.FeatureNotFound()) - def getInfos(self, client, jid_=None, node=u'', use_cache=True): + def getInfos(self, client, jid_=None, node=u"", use_cache=True): """get disco infos from jid_, filling capability hash if needed @param jid_: jid of the target, or None for profile's server @@ -167,14 +173,19 @@ if not use_cache: # we ignore cache, so we pretend we haven't found it raise KeyError - cap_hash = self.host.memory.getEntityData(jid_, [C.ENTITY_CAP_HASH], client.profile)[C.ENTITY_CAP_HASH] + cap_hash = self.host.memory.getEntityData( + jid_, [C.ENTITY_CAP_HASH], client.profile + )[C.ENTITY_CAP_HASH] except (KeyError, exceptions.UnknownEntityError): # capability hash is not available, we'll compute one def infosCb(disco_infos): cap_hash = self.generateHash(disco_infos) self.hashes[cap_hash] = disco_infos - self.host.memory.updateEntityData(jid_, C.ENTITY_CAP_HASH, cap_hash, profile_key=client.profile) + self.host.memory.updateEntityData( + jid_, C.ENTITY_CAP_HASH, cap_hash, profile_key=client.profile + ) return disco_infos + def infosEb(fail): if fail.check(defer.CancelledError): reason = u"request time-out" @@ -183,10 +194,17 @@ reason = unicode(fail.value) except AttributeError: reason = unicode(fail) - log.warning(u"Error while requesting disco infos from {jid}: {reason}".format(jid=jid_.full(), reason=reason)) - self.host.memory.updateEntityData(jid_, C.ENTITY_CAP_HASH, CAP_HASH_ERROR, profile_key=client.profile) + log.warning( + u"Error while requesting disco infos from {jid}: {reason}".format( + jid=jid_.full(), reason=reason + ) + ) + self.host.memory.updateEntityData( + jid_, C.ENTITY_CAP_HASH, CAP_HASH_ERROR, profile_key=client.profile + ) disco_infos = self.hashes[CAP_HASH_ERROR] return disco_infos + d = client.disco.requestInfo(jid_, nodeIdentifier=node) d.addCallback(infosCb) d.addErrback(infosEb) @@ -196,7 +214,7 @@ return defer.succeed(disco_infos) @defer.inlineCallbacks - def getItems(self, client, jid_=None, node=u'', use_cache=True): + def getItems(self, client, jid_=None, node=u"", use_cache=True): """get disco items from jid_, cache them for our own server @param jid_(jid.JID): jid of the target, or None for profile's server @@ -211,7 +229,9 @@ if jid_ == server_jid and not node: # we cache items only for our own server and if node is not set try: - items = self.host.memory.getEntityData(jid_, ["DISCO_ITEMS"], client.profile)["DISCO_ITEMS"] + items = self.host.memory.getEntityData( + jid_, ["DISCO_ITEMS"], client.profile + )["DISCO_ITEMS"] log.debug(u"[%s] disco items are in cache" % jid_.full()) if not use_cache: # we ignore cache, so we pretend we haven't found it @@ -219,22 +239,28 @@ except (KeyError, exceptions.UnknownEntityError): log.debug(u"Caching [%s] disco items" % jid_.full()) items = yield client.disco.requestItems(jid_, nodeIdentifier=node) - self.host.memory.updateEntityData(jid_, "DISCO_ITEMS", items, profile_key=client.profile) + self.host.memory.updateEntityData( + jid_, "DISCO_ITEMS", items, profile_key=client.profile + ) else: try: items = yield client.disco.requestItems(jid_, nodeIdentifier=node) except StanzaError as e: - log.warning(u"Error while requesting items for {jid}: {reason}" - .format(jid=jid_.full(), reason=e.condition)) + log.warning( + u"Error while requesting items for {jid}: {reason}".format( + jid=jid_.full(), reason=e.condition + ) + ) items = disco.DiscoItems() defer.returnValue(items) - def _infosEb(self, failure_, entity_jid): failure_.trap(StanzaError) - log.warning(_(u"Error while requesting [%(jid)s]: %(error)s") % {'jid': entity_jid.full(), - 'error': failure_.getErrorMessage()}) + log.warning( + _(u"Error while requesting [%(jid)s]: %(error)s") + % {"jid": entity_jid.full(), "error": failure_.getErrorMessage()} + ) def findServiceEntity(self, client, category, type_, jid_=None): """Helper method to find first available entity from findServiceEntities @@ -265,14 +291,18 @@ defers_list = [] for item in items: info_d = self.getInfos(client, item.entity) - info_d.addCallbacks(infosCb, self._infosEb, [item.entity], None, [item.entity]) + info_d.addCallbacks( + infosCb, self._infosEb, [item.entity], None, [item.entity] + ) defers_list.append(info_d) return defer.DeferredList(defers_list) d = self.getItems(client, jid_) d.addCallback(gotItems) d.addCallback(lambda dummy: found_entities) - reactor.callLater(TIMEOUT, d.cancel) # FIXME: one bad service make a general timeout + reactor.callLater( + TIMEOUT, d.cancel + ) # FIXME: one bad service make a general timeout return d def findFeaturesSet(self, client, features, identity=None, jid_=None): @@ -291,7 +321,7 @@ def infosCb(infos, entity): if entity is None: - log.warning(_(u'received an item without jid')) + log.warning(_(u"received an item without jid")) return if identity is not None and identity not in infos.identities: return @@ -309,7 +339,9 @@ d = self.getItems(client, jid_) d.addCallback(gotItems) d.addCallback(lambda dummy: found_entities) - reactor.callLater(TIMEOUT, d.cancel) # FIXME: one bad service make a general timeout + reactor.callLater( + TIMEOUT, d.cancel + ) # FIXME: one bad service make a general timeout return d def generateHash(self, services): @@ -320,25 +352,35 @@ """ s = [] - byte_identities = [ByteIdentity(service) for service in services if isinstance(service, disco.DiscoIdentity)] # FIXME: lang must be managed here + byte_identities = [ + ByteIdentity(service) + for service in services + if isinstance(service, disco.DiscoIdentity) + ] # FIXME: lang must be managed here byte_identities.sort(key=lambda i: i.lang) byte_identities.sort(key=lambda i: i.idType) byte_identities.sort(key=lambda i: i.category) for identity in byte_identities: s.append(str(identity)) - s.append('<') - byte_features = [service.encode('utf-8') for service in services if isinstance(service, disco.DiscoFeature)] + s.append("<") + byte_features = [ + service.encode("utf-8") + for service in services + if isinstance(service, disco.DiscoFeature) + ] byte_features.sort() # XXX: the default sort has the same behaviour as the requested RFC 4790 i;octet sort for feature in byte_features: s.append(feature) - s.append('<') - #TODO: manage XEP-0128 data form here - cap_hash = b64encode(sha1(''.join(s)).digest()) - log.debug(_(u'Capability hash generated: [%s]') % cap_hash) + s.append("<") + # TODO: manage XEP-0128 data form here + cap_hash = b64encode(sha1("".join(s)).digest()) + log.debug(_(u"Capability hash generated: [%s]") % cap_hash) return cap_hash @defer.inlineCallbacks - def _discoInfos(self, entity_jid_s, node=u'', use_cache=True, profile_key=C.PROF_KEY_NONE): + def _discoInfos( + self, entity_jid_s, node=u"", use_cache=True, profile_key=C.PROF_KEY_NONE + ): """ Discovery method for the bridge @param entity_jid_s: entity we want to discover @param use_cache(bool): if True, use cached data if available @@ -353,8 +395,8 @@ for form_type, form in disco_infos.extensions.items(): fields = [] for field in form.fieldList: - data = {'type': field.fieldType} - for attr in ('var', 'label', 'desc'): + data = {"type": field.fieldType} + for attr in ("var", "label", "desc"): value = getattr(field, attr) if value is not None: data[attr] = value @@ -364,9 +406,16 @@ extensions[form_type or ""] = fields - defer.returnValue((disco_infos.features, - [(cat, type_, name or '') for (cat, type_), name in disco_infos.identities.items()], - extensions)) + defer.returnValue( + ( + disco_infos.features, + [ + (cat, type_, name or "") + for (cat, type_), name in disco_infos.identities.items() + ], + extensions, + ) + ) def items2tuples(self, disco_items): """convert disco items to tuple of strings @@ -378,10 +427,12 @@ if not item.entity: log.warning(_(u"invalid item (no jid)")) continue - yield (item.entity.full(), item.nodeIdentifier or '', item.name or '') + yield (item.entity.full(), item.nodeIdentifier or "", item.name or "") @defer.inlineCallbacks - def _discoItems(self, entity_jid_s, node=u'', use_cache=True, profile_key=C.PROF_KEY_NONE): + def _discoItems( + self, entity_jid_s, node=u"", use_cache=True, profile_key=C.PROF_KEY_NONE + ): """ Discovery method for the bridge @param entity_jid_s: entity we want to discover
--- a/sat/memory/memory.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/memory/memory.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.log import getLogger + log = getLogger(__name__) import os.path @@ -44,11 +45,13 @@ import time -PresenceTuple = namedtuple("PresenceTuple", ('show', 'priority', 'statuses')) +PresenceTuple = namedtuple("PresenceTuple", ("show", "priority", "statuses")) MSG_NO_SESSION = "Session id doesn't exist or is finished" + class Sessions(object): """Sessions are data associated to key used for a temporary moment, with optional profile checking.""" + DEFAULT_TIMEOUT = 600 def __init__(self, timeout=None, resettable_timeout=True): @@ -72,11 +75,15 @@ if session_id is None: session_id = str(uuid4()) elif session_id in self._sessions: - raise exceptions.ConflictError(u"Session id {} is already used".format(session_id)) + raise exceptions.ConflictError( + u"Session id {} is already used".format(session_id) + ) timer = reactor.callLater(self.timeout, self._purgeSession, session_id) if session_data is None: session_data = {} - self._sessions[session_id] = (timer, session_data) if profile is None else (timer, session_data, profile) + self._sessions[session_id] = ( + (timer, session_data) if profile is None else (timer, session_data, profile) + ) return session_id, session_data def _purgeSession(self, session_id): @@ -91,7 +98,12 @@ # if the session is time-outed, the timer has been called pass del self._sessions[session_id] - log.debug(u"Session {} purged{}".format(session_id, u' (profile {})'.format(profile) if profile is not None else u'')) + log.debug( + u"Session {} purged{}".format( + session_id, + u" (profile {})".format(profile) if profile is not None else u"", + ) + ) def __len__(self): return len(self._sessions) @@ -103,7 +115,9 @@ try: timer, session_data, profile_set = self._sessions[session_id] except ValueError: - raise exceptions.InternalError("You need to use __getitem__ when profile is not set") + raise exceptions.InternalError( + "You need to use __getitem__ when profile is not set" + ) except KeyError: raise failure.Failure(KeyError(MSG_NO_SESSION)) if profile_set != profile: @@ -116,7 +130,9 @@ try: timer, session_data = self._sessions[session_id] except ValueError: - raise exceptions.InternalError("You need to use profileGet instead of __getitem__ when profile is set") + raise exceptions.InternalError( + "You need to use profileGet instead of __getitem__ when profile is set" + ) except KeyError: raise failure.Failure(KeyError(MSG_NO_SESSION)) if self.resettable_timeout: @@ -169,8 +185,12 @@ """ ids = self._profileGetAllIds(profile) if len(ids) > 1: - raise exceptions.InternalError('profileGetUnique has been used but more than one session has been found!') - return self.profileGet(ids[0], profile) if len(ids) == 1 else None # XXX: timeout might be reset + raise exceptions.InternalError( + "profileGetUnique has been used but more than one session has been found!" + ) + return ( + self.profileGet(ids[0], profile) if len(ids) == 1 else None + ) # XXX: timeout might be reset def profileDelUnique(self, profile): """Delete the unique session that is associated to the given profile. @@ -180,7 +200,9 @@ """ ids = self._profileGetAllIds(profile) if len(ids) > 1: - raise exceptions.InternalError('profileDelUnique has been used but more than one session has been found!') + raise exceptions.InternalError( + "profileDelUnique has been used but more than one session has been found!" + ) if len(ids) == 1: del self._sessions[ids[0]] @@ -194,7 +216,9 @@ ProfileSessions.__init__(self, timeout, resettable_timeout=False) def _purgeSession(self, session_id): - log.debug("FIXME: PasswordSessions should ask for the profile password after the session expired") + log.debug( + "FIXME: PasswordSessions should ask for the profile password after the session expired" + ) # XXX: tmp update code, will be removed in the future @@ -211,16 +235,20 @@ except: pass # file is readable but its structure if wrong try: - current_value = user_config.get('DEFAULT', 'local_dir') + current_value = user_config.get("DEFAULT", "local_dir") except (NoOptionError, NoSectionError): - current_value = '' + current_value = "" if current_value: return # nothing to do - old_default = '~/.sat' - if os.path.isfile(os.path.expanduser(old_default) + '/' + C.SAVEFILE_DATABASE): + old_default = "~/.sat" + if os.path.isfile(os.path.expanduser(old_default) + "/" + C.SAVEFILE_DATABASE): if not silent: - log.warning(_(u"A database has been found in the default local_dir for previous versions (< 0.5)")) - tools_config.fixConfigOption('', 'local_dir', old_default, silent) + log.warning( + _( + u"A database has been found in the default local_dir for previous versions (< 0.5)" + ) + ) + tools_config.fixConfigOption("", "local_dir", old_default, silent) class Memory(object): @@ -230,17 +258,19 @@ log.info(_("Memory manager init")) self.initialized = defer.Deferred() self.host = host - self._entities_cache = {} # XXX: keep presence/last resource/other data in cache - # /!\ an entity is not necessarily in roster - # main key is bare jid, value is a dict - # where main key is resource, or None for bare jid - self._key_signals = set() # key which need a signal to frontends when updated + self._entities_cache = {} # XXX: keep presence/last resource/other data in cache + # /!\ an entity is not necessarily in roster + # main key is bare jid, value is a dict + # where main key is resource, or None for bare jid + self._key_signals = set() # key which need a signal to frontends when updated self.subscriptions = {} self.auth_sessions = PasswordSessions() # remember the authenticated profiles self.disco = Discovery(host) fixLocalDir(False) # XXX: tmp update code, will be removed in the future self.config = tools_config.parseMainConf() - database_file = os.path.expanduser(os.path.join(self.getConfig('', 'local_dir'), C.SAVEFILE_DATABASE)) + database_file = os.path.expanduser( + os.path.join(self.getConfig("", "local_dir"), C.SAVEFILE_DATABASE) + ) self.storage = SqliteStorage(database_file, host.version) PersistentDict.storage = self.storage self.params = Params(host, self.storage) @@ -290,7 +320,7 @@ """ if not filename: return False - #TODO: need to encrypt files (at least passwords !) and set permissions + # TODO: need to encrypt files (at least passwords !) and set permissions filename = os.path.expanduser(filename) try: self.params.save_xml(filename) @@ -302,7 +332,7 @@ def load(self): """Load parameters and all memory things from db""" - #parameters data + # parameters data return self.params.loadGenParams() def loadIndividualParams(self, profile): @@ -340,7 +370,9 @@ session_d = self._entities_cache[profile] except KeyError: # else we do request the params - session_d = self._entities_cache[profile] = self.loadIndividualParams(profile) + session_d = self._entities_cache[profile] = self.loadIndividualParams( + profile + ) session_d.addCallback(createSession) finally: return session_d @@ -396,12 +428,20 @@ def check_result(result): if not result: - log.warning(u'Authentication failure of profile {}'.format(profile)) - raise failure.Failure(exceptions.PasswordError(u"The provided profile password doesn't match.")) - if not session_data: # avoid to create two profile sessions when password if specified + log.warning(u"Authentication failure of profile {}".format(profile)) + raise failure.Failure( + exceptions.PasswordError( + u"The provided profile password doesn't match." + ) + ) + if ( + not session_data + ): # avoid to create two profile sessions when password if specified return self.newAuthSession(password, profile) - d = self.asyncGetParamA(C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile) + d = self.asyncGetParamA( + C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile + ) d.addCallback(lambda sat_cipher: PasswordHasher.verify(password, sat_cipher)) return d.addCallback(check_result) @@ -414,10 +454,13 @@ @param profile: %(doc_profile)s @return: a deferred None value """ + def gotPersonalKey(personal_key): """Create the session for this profile and store the personal key""" - self.auth_sessions.newSession({C.MEMORY_CRYPTO_KEY: personal_key}, profile=profile) - log.debug(u'auth session created for profile %s' % profile) + self.auth_sessions.newSession( + {C.MEMORY_CRYPTO_KEY: personal_key}, profile=profile + ) + log.debug(u"auth session created for profile %s" % profile) d = PersistentDict(C.MEMORY_CRYPTO_NAMESPACE, profile).load() d.addCallback(lambda data: BlockCipher.decrypt(key, data[C.MEMORY_CRYPTO_KEY])) @@ -431,7 +474,12 @@ try: del self._entities_cache[profile] except KeyError: - log.error(_(u"Trying to purge roster status cache for a profile not in memory: [%s]") % profile) + log.error( + _( + u"Trying to purge roster status cache for a profile not in memory: [%s]" + ) + % profile + ) def getProfilesList(self, clients=True, components=False): """retrieve profiles list @@ -472,7 +520,7 @@ # we want to be sure that the profile exists profile = self.getProfileName(profile) - self.memory_data['Profile_default'] = profile + self.memory_data["Profile_default"] = profile def createProfile(self, name, password, component=None): """Create a new profile @@ -486,9 +534,9 @@ """ if not name: raise ValueError(u"Empty profile name") - if name[0] == '@': + if name[0] == "@": raise ValueError(u"A profile name can't start with a '@'") - if '\n' in name: + if "\n" in name: raise ValueError(u"A profile name can't contain line feed ('\\n')") if name in self._entities_cache: @@ -496,19 +544,28 @@ if component: if not component in self.host.plugins: - raise exceptions.NotFound(_(u"Can't find component {component} entry point".format( - component = component))) + raise exceptions.NotFound( + _( + u"Can't find component {component} entry point".format( + component=component + ) + ) + ) # FIXME: PLUGIN_INFO is not currently accessible after import, but type shoul be tested here - # if self.host.plugins[component].PLUGIN_INFO[u"type"] != C.PLUG_TYPE_ENTRY_POINT: - # raise ValueError(_(u"Plugin {component} is not an entry point !".format( - # component = component))) + # if self.host.plugins[component].PLUGIN_INFO[u"type"] != C.PLUG_TYPE_ENTRY_POINT: + # raise ValueError(_(u"Plugin {component} is not an entry point !".format( + # component = component))) d = self.params.createProfile(name, component) def initPersonalKey(dummy): # be sure to call this after checking that the profile doesn't exist yet - personal_key = BlockCipher.getRandomKey(base64=True) # generated once for all and saved in a PersistentDict - self.auth_sessions.newSession({C.MEMORY_CRYPTO_KEY: personal_key}, profile=name) # will be encrypted by setParam + personal_key = BlockCipher.getRandomKey( + base64=True + ) # generated once for all and saved in a PersistentDict + self.auth_sessions.newSession( + {C.MEMORY_CRYPTO_KEY: personal_key}, profile=name + ) # will be encrypted by setParam def startFakeSession(dummy): # avoid ProfileNotConnected exception in setParam @@ -521,7 +578,11 @@ d.addCallback(initPersonalKey) d.addCallback(startFakeSession) - d.addCallback(lambda dummy: self.setParam(C.PROFILE_PASS_PATH[1], password, C.PROFILE_PASS_PATH[0], profile_key=name)) + d.addCallback( + lambda dummy: self.setParam( + C.PROFILE_PASS_PATH[1], password, C.PROFILE_PASS_PATH[0], profile_key=name + ) + ) d.addCallback(stopFakeSession) d.addCallback(lambda dummy: self.auth_sessions.profileDelUnique(name)) return d @@ -534,12 +595,14 @@ To be used for direct calls only (not through the bridge). @return: a Deferred instance """ + def cleanMemory(dummy): self.auth_sessions.profileDelUnique(name) try: del self._entities_cache[name] except KeyError: pass + d = self.params.asyncDeleteProfile(name, force) d.addCallback(cleanMemory) return d @@ -567,10 +630,28 @@ def addToHistory(self, client, data): return self.storage.addToHistory(data, client.profile) - def _historyGet(self, from_jid_s, to_jid_s, limit=C.HISTORY_LIMIT_NONE, between=True, filters=None, profile=C.PROF_KEY_NONE): - return self.historyGet(jid.JID(from_jid_s), jid.JID(to_jid_s), limit, between, filters, profile) + def _historyGet( + self, + from_jid_s, + to_jid_s, + limit=C.HISTORY_LIMIT_NONE, + between=True, + filters=None, + profile=C.PROF_KEY_NONE, + ): + return self.historyGet( + jid.JID(from_jid_s), jid.JID(to_jid_s), limit, between, filters, profile + ) - def historyGet(self, from_jid, to_jid, limit=C.HISTORY_LIMIT_NONE, between=True, filters=None, profile=C.PROF_KEY_NONE): + def historyGet( + self, + from_jid, + to_jid, + limit=C.HISTORY_LIMIT_NONE, + between=True, + filters=None, + profile=C.PROF_KEY_NONE, + ): """Retrieve messages in history @param from_jid (JID): source JID (full, or bare for catchall) @@ -586,7 +667,7 @@ """ assert profile != C.PROF_KEY_NONE if limit == C.HISTORY_LIMIT_DEFAULT: - limit = int(self.getParamA(C.HISTORY_LIMIT, 'General', profile_key=profile)) + limit = int(self.getParamA(C.HISTORY_LIMIT, "General", profile_key=profile)) elif limit == C.HISTORY_LIMIT_NONE: limit = None if limit == 0: @@ -597,7 +678,7 @@ def _getPresenceStatuses(self, profile_key): ret = self.getPresenceStatuses(profile_key) - return {entity.full():data for entity, data in ret.iteritems()} + return {entity.full(): data for entity, data in ret.iteritems()} def getPresenceStatuses(self, profile_key): """Get all the presence statuses of a profile @@ -617,7 +698,9 @@ presence_data = self.getEntityDatum(full_jid, "presence", profile_key) except KeyError: continue - entities_presence.setdefault(entity_jid, {})[resource or ''] = presence_data + entities_presence.setdefault(entity_jid, {})[ + resource or "" + ] = presence_data return entities_presence @@ -631,7 +714,9 @@ @param profile_key: %(doc_profile_key)s """ presence_data = PresenceTuple(show, priority, statuses) - self.updateEntityData(entity_jid, "presence", presence_data, profile_key=profile_key) + self.updateEntityData( + entity_jid, "presence", presence_data, profile_key=profile_key + ) if entity_jid.resource and show != C.PRESENCE_UNAVAILABLE: # If a resource is available, bare jid should not have presence information try: @@ -657,13 +742,17 @@ """ # FIXME: is there a need to keep cache data for resources which are not connected anymore? if entity_jid.resource: - raise ValueError("getAllResources must be used with a bare jid (got {})".format(entity_jid)) + raise ValueError( + "getAllResources must be used with a bare jid (got {})".format(entity_jid) + ) profile_cache = self._getProfileCache(client) try: entity_data = profile_cache[entity_jid.userhostJID()] except KeyError: - raise exceptions.UnknownEntityError(u"Entity {} not in cache".format(entity_jid)) - resources= set(entity_data.keys()) + raise exceptions.UnknownEntityError( + u"Entity {} not in cache".format(entity_jid) + ) + resources = set(entity_data.keys()) resources.discard(None) return resources @@ -701,7 +790,9 @@ @return (unicode): main resource or None """ if entity_jid.resource: - raise ValueError("getMainResource must be used with a bare jid (got {})".format(entity_jid)) + raise ValueError( + "getMainResource must be used with a bare jid (got {})".format(entity_jid) + ) try: if self.host.plugins["XEP-0045"].isJoinedRoom(client, entity_jid): return None # MUC rooms have no main resource @@ -766,7 +857,9 @@ full_jid.resource = resource yield full_jid - def updateEntityData(self, entity_jid, key, value, silent=False, profile_key=C.PROF_KEY_NONE): + def updateEntityData( + self, entity_jid, key, value, silent=False, profile_key=C.PROF_KEY_NONE + ): """Set a misc data for an entity If key was registered with setSignalOnUpdate, a signal will be sent to frontends @@ -780,19 +873,27 @@ client = self.host.getClient(profile_key) profile_cache = self._getProfileCache(client) if entity_jid in (C.ENTITY_ALL_RESOURCES, C.ENTITY_ALL): - entities = self.getAllEntitiesIter(client, entity_jid==C.ENTITY_ALL) + entities = self.getAllEntitiesIter(client, entity_jid == C.ENTITY_ALL) else: entities = (entity_jid,) for jid_ in entities: - entity_data = profile_cache.setdefault(jid_.userhostJID(),{}).setdefault(jid_.resource, {}) + entity_data = profile_cache.setdefault(jid_.userhostJID(), {}).setdefault( + jid_.resource, {} + ) entity_data[key] = value if key in self._key_signals and not silent: if not isinstance(value, basestring): - log.error(u"Setting a non string value ({}) for a key ({}) which has a signal flag".format(value, key)) + log.error( + u"Setting a non string value ({}) for a key ({}) which has a signal flag".format( + value, key + ) + ) else: - self.host.bridge.entityDataUpdated(jid_.full(), key, value, self.getProfileName(profile_key)) + self.host.bridge.entityDataUpdated( + jid_.full(), key, value, self.getProfileName(profile_key) + ) def delEntityDatum(self, entity_jid, key, profile_key): """Delete a data for an entity @@ -808,7 +909,7 @@ client = self.host.getClient(profile_key) profile_cache = self._getProfileCache(client) if entity_jid in (C.ENTITY_ALL_RESOURCES, C.ENTITY_ALL): - entities = self.getAllEntitiesIter(client, entity_jid==C.ENTITY_ALL) + entities = self.getAllEntitiesIter(client, entity_jid == C.ENTITY_ALL) else: entities = (entity_jid,) @@ -816,17 +917,21 @@ try: entity_data = profile_cache[jid_.userhostJID()][jid_.resource] except KeyError: - raise exceptions.UnknownEntityError(u"Entity {} not in cache".format(jid_)) + raise exceptions.UnknownEntityError( + u"Entity {} not in cache".format(jid_) + ) try: del entity_data[key] except KeyError as e: if entity_jid in (C.ENTITY_ALL_RESOURCES, C.ENTITY_ALL): - continue # we ignore KeyError when deleting keys from several entities + continue # we ignore KeyError when deleting keys from several entities else: raise e def _getEntitiesData(self, entities_jids, keys_list, profile_key): - ret = self.getEntitiesData([jid.JID(jid_) for jid_ in entities_jids], keys_list, profile_key) + ret = self.getEntitiesData( + [jid.JID(jid_) for jid_ in entities_jids], keys_list, profile_key + ) return {jid_.full(): data for jid_, data in ret.iteritems()} def getEntitiesData(self, entities_jids, keys_list=None, profile_key=C.PROF_KEY_NONE): @@ -843,6 +948,7 @@ @raise exceptions.UnknownEntityError: if entity is not in cache """ + def fillEntityData(entity_cache_data): entity_data = {} if keys_list is None: @@ -861,7 +967,9 @@ if entities_jids: for entity in entities_jids: try: - entity_cache_data = profile_cache[entity.userhostJID()][entity.resource] + entity_cache_data = profile_cache[entity.userhostJID()][ + entity.resource + ] except KeyError: continue ret_data[entity.full()] = fillEntityData(entity_cache_data, keys_list) @@ -891,7 +999,11 @@ try: entity_data = profile_cache[entity_jid.userhostJID()][entity_jid.resource] except KeyError: - raise exceptions.UnknownEntityError(u"Entity {} not in cache (was requesting {})".format(entity_jid, keys_list)) + raise exceptions.UnknownEntityError( + u"Entity {} not in cache (was requesting {})".format( + entity_jid, keys_list + ) + ) if keys_list is None: return entity_data @@ -910,7 +1022,9 @@ """ return self.getEntityData(entity_jid, (key,), profile_key)[key] - def delEntityCache(self, entity_jid, delete_all_resources=True, profile_key=C.PROF_KEY_NONE): + def delEntityCache( + self, entity_jid, delete_all_resources=True, profile_key=C.PROF_KEY_NONE + ): """Remove all cached data for entity @param entity_jid: JID of the entity to delete @@ -928,12 +1042,16 @@ try: del profile_cache[entity_jid] except KeyError: - raise exceptions.UnknownEntityError(u"Entity {} not in cache".format(entity_jid)) + raise exceptions.UnknownEntityError( + u"Entity {} not in cache".format(entity_jid) + ) else: try: del profile_cache[entity_jid.userhostJID()][entity_jid.resource] except KeyError: - raise exceptions.UnknownEntityError(u"Entity {} not in cache".format(entity_jid)) + raise exceptions.UnknownEntityError( + u"Entity {} not in cache".format(entity_jid) + ) ## Encryption ## @@ -947,9 +1065,14 @@ @return: the deferred encrypted value """ try: - personal_key = self.auth_sessions.profileGetUnique(profile)[C.MEMORY_CRYPTO_KEY] + personal_key = self.auth_sessions.profileGetUnique(profile)[ + C.MEMORY_CRYPTO_KEY + ] except TypeError: - raise exceptions.InternalError(_('Trying to encrypt a value for %s while the personal key is undefined!') % profile) + raise exceptions.InternalError( + _("Trying to encrypt a value for %s while the personal key is undefined!") + % profile + ) return BlockCipher.encrypt(personal_key, value) def decryptValue(self, value, profile): @@ -962,9 +1085,14 @@ @return: the deferred decrypted value """ try: - personal_key = self.auth_sessions.profileGetUnique(profile)[C.MEMORY_CRYPTO_KEY] + personal_key = self.auth_sessions.profileGetUnique(profile)[ + C.MEMORY_CRYPTO_KEY + ] except TypeError: - raise exceptions.InternalError(_('Trying to decrypt a value for %s while the personal key is undefined!') % profile) + raise exceptions.InternalError( + _("Trying to decrypt a value for %s while the personal key is undefined!") + % profile + ) return BlockCipher.decrypt(personal_key, value) def encryptPersonalData(self, data_key, data_value, crypto_key, profile): @@ -987,8 +1115,10 @@ return d.addCallback(cb) def done(dummy): - log.debug(_(u'Personal data (%(ns)s, %(key)s) has been successfuly encrypted') % - {'ns': C.MEMORY_CRYPTO_NAMESPACE, 'key': data_key}) + log.debug( + _(u"Personal data (%(ns)s, %(key)s) has been successfuly encrypted") + % {"ns": C.MEMORY_CRYPTO_NAMESPACE, "key": data_key} + ) d = PersistentDict(C.MEMORY_CRYPTO_NAMESPACE, profile).load() return d.addCallback(gotIndMemory).addCallback(done) @@ -1014,7 +1144,7 @@ """Called to get a list of currently waiting subscription requests""" profile = self.getProfileName(profile_key) if not profile: - log.error(_('Asking waiting subscriptions for a non-existant profile')) + log.error(_("Asking waiting subscriptions for a non-existant profile")) return {} if profile not in self.subscriptions: return {} @@ -1029,28 +1159,59 @@ def getParamA(self, name, category, attr="value", profile_key=C.PROF_KEY_NONE): return self.params.getParamA(name, category, attr, profile_key=profile_key) - def asyncGetParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE): - return self.params.asyncGetParamA(name, category, attr, security_limit, profile_key) + def asyncGetParamA( + self, + name, + category, + attr="value", + security_limit=C.NO_SECURITY_LIMIT, + profile_key=C.PROF_KEY_NONE, + ): + return self.params.asyncGetParamA( + name, category, attr, security_limit, profile_key + ) - def asyncGetParamsValuesFromCategory(self, category, security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE): - return self.params.asyncGetParamsValuesFromCategory(category, security_limit, profile_key) + def asyncGetParamsValuesFromCategory( + self, category, security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE + ): + return self.params.asyncGetParamsValuesFromCategory( + category, security_limit, profile_key + ) - def asyncGetStringParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE): - return self.params.asyncGetStringParamA(name, category, attr, security_limit, profile_key) + def asyncGetStringParamA( + self, + name, + category, + attr="value", + security_limit=C.NO_SECURITY_LIMIT, + profile_key=C.PROF_KEY_NONE, + ): + return self.params.asyncGetStringParamA( + name, category, attr, security_limit, profile_key + ) - def getParamsUI(self, security_limit=C.NO_SECURITY_LIMIT, app='', profile_key=C.PROF_KEY_NONE): + def getParamsUI( + self, security_limit=C.NO_SECURITY_LIMIT, app="", profile_key=C.PROF_KEY_NONE + ): return self.params.getParamsUI(security_limit, app, profile_key) def getParamsCategories(self): return self.params.getParamsCategories() - def setParam(self, name, value, category, security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE): + def setParam( + self, + name, + value, + category, + security_limit=C.NO_SECURITY_LIMIT, + profile_key=C.PROF_KEY_NONE, + ): return self.params.setParam(name, value, category, security_limit, profile_key) def updateParams(self, xml): return self.params.updateParams(xml) - def paramsRegisterApp(self, xml, security_limit=C.NO_SECURITY_LIMIT, app=''): + def paramsRegisterApp(self, xml, security_limit=C.NO_SECURITY_LIMIT, app=""): return self.params.paramsRegisterApp(xml, security_limit, app) def setDefault(self, name, category, callback, errback=None): @@ -1073,25 +1234,25 @@ if peer_jid is None and perms_to_check is None: return peer_jid = peer_jid.userhostJID() - if peer_jid == file_data['owner']: + if peer_jid == file_data["owner"]: # the owner has all rights return if not C.ACCESS_PERMS.issuperset(perms_to_check): - raise exceptions.InternalError(_(u'invalid permission')) + raise exceptions.InternalError(_(u"invalid permission")) for perm in perms_to_check: # we check each perm and raise PermissionError as soon as one condition is not valid # we must never return here, we only return after the loop if nothing was blocking the access try: - perm_data = file_data[u'access'][perm] - perm_type = perm_data[u'type'] + perm_data = file_data[u"access"][perm] + perm_type = perm_data[u"type"] except KeyError: raise failure.Failure(exceptions.PermissionError()) if perm_type == C.ACCESS_TYPE_PUBLIC: continue elif perm_type == C.ACCESS_TYPE_WHITELIST: try: - jids = perm_data[u'jids'] + jids = perm_data[u"jids"] except KeyError: raise failure.Failure(exceptions.PermissionError()) if peer_jid.full() in jids: @@ -1099,7 +1260,9 @@ else: raise failure.Failure(exceptions.PermissionError()) else: - raise exceptions.InternalError(_(u'unknown access type: {type}').format(type=perm_type)) + raise exceptions.InternalError( + _(u"unknown access type: {type}").format(type=perm_type) + ) @defer.inlineCallbacks def checkPermissionToRoot(self, client, file_data, peer_jid, perms_to_check): @@ -1107,17 +1270,21 @@ current = file_data while True: self.checkFilePermission(current, peer_jid, perms_to_check) - parent = current[u'parent'] + parent = current[u"parent"] if not parent: break - files_data = yield self.getFile(self, client, peer_jid=None, file_id=parent, perms_to_check=None) + files_data = yield self.getFile( + self, client, peer_jid=None, file_id=parent, perms_to_check=None + ) try: current = files_data[0] except IndexError: - raise exceptions.DataError(u'Missing parent') + raise exceptions.DataError(u"Missing parent") @defer.inlineCallbacks - def _getParentDir(self, client, path, parent, namespace, owner, peer_jid, perms_to_check): + def _getParentDir( + self, client, path, parent, namespace, owner, peer_jid, perms_to_check + ): """Retrieve parent node from a path, or last existing directory each directory of the path will be retrieved, until the last existing one @@ -1128,32 +1295,59 @@ """ # if path is set, we have to retrieve parent directory of the file(s) from it if parent is not None: - raise exceptions.ConflictError(_(u"You can't use path and parent at the same time")) - path_elts = filter(None, path.split(u'/')) - if {u'..', u'.'}.intersection(path_elts): + raise exceptions.ConflictError( + _(u"You can't use path and parent at the same time") + ) + path_elts = filter(None, path.split(u"/")) + if {u"..", u"."}.intersection(path_elts): raise ValueError(_(u'".." or "." can\'t be used in path')) # we retrieve all directories from path until we get the parent container # non existing directories will be created - parent = u'' + parent = u"" for idx, path_elt in enumerate(path_elts): - directories = yield self.storage.getFiles(client, parent=parent, type_=C.FILE_TYPE_DIRECTORY, - name=path_elt, namespace=namespace, owner=owner) + directories = yield self.storage.getFiles( + client, + parent=parent, + type_=C.FILE_TYPE_DIRECTORY, + name=path_elt, + namespace=namespace, + owner=owner, + ) if not directories: defer.returnValue((parent, path_elts[idx:])) # from this point, directories don't exist anymore, we have to create them elif len(directories) > 1: - raise exceptions.InternalError(_(u"Several directories found, this should not happen")) + raise exceptions.InternalError( + _(u"Several directories found, this should not happen") + ) else: directory = directories[0] self.checkFilePermission(directory, peer_jid, perms_to_check) - parent = directory[u'id'] + parent = directory[u"id"] defer.returnValue((parent, [])) @defer.inlineCallbacks - def getFiles(self, client, peer_jid, file_id=None, version=None, parent=None, path=None, type_=None, - file_hash=None, hash_algo=None, name=None, namespace=None, mime_type=None, - owner=None, access=None, projection=None, unique=False, perms_to_check=(C.ACCESS_PERM_READ,)): + def getFiles( + self, + client, + peer_jid, + file_id=None, + version=None, + parent=None, + path=None, + type_=None, + file_hash=None, + hash_algo=None, + name=None, + namespace=None, + mime_type=None, + owner=None, + access=None, + projection=None, + unique=False, + perms_to_check=(C.ACCESS_PERM_READ,), + ): """retrieve files with with given filters @param peer_jid(jid.JID, None): jid trying to access the file @@ -1180,12 +1374,16 @@ on the path """ if peer_jid is None and perms_to_check or perms_to_check is None and peer_jid: - raise exceptions.InternalError('if you want to disable permission check, both peer_jid and perms_to_check must be None') + raise exceptions.InternalError( + "if you want to disable permission check, both peer_jid and perms_to_check must be None" + ) if owner is not None: owner = owner.userhostJID() if path is not None: # permission are checked by _getParentDir - parent, remaining_path_elts = yield self._getParentDir(client, path, parent, namespace, owner, peer_jid, perms_to_check) + parent, remaining_path_elts = yield self._getParentDir( + client, path, parent, namespace, owner, peer_jid, perms_to_check + ) if remaining_path_elts: # if we have remaining path elements, # the parent directory is not found @@ -1197,16 +1395,30 @@ try: parent_data = parent_data[0] except IndexError: - raise exceptions.DataError(u'mising parent') - yield self.checkPermissionToRoot(client, parent_data, peer_jid, perms_to_check) + raise exceptions.DataError(u"mising parent") + yield self.checkPermissionToRoot( + client, parent_data, peer_jid, perms_to_check + ) - files = yield self.storage.getFiles(client, file_id=file_id, version=version, parent=parent, type_=type_, - file_hash=file_hash, hash_algo=hash_algo, name=name, namespace=namespace, - mime_type=mime_type, owner=owner, access=access, - projection=projection, unique=unique) + files = yield self.storage.getFiles( + client, + file_id=file_id, + version=version, + parent=parent, + type_=type_, + file_hash=file_hash, + hash_algo=hash_algo, + name=name, + namespace=namespace, + mime_type=mime_type, + owner=owner, + access=access, + projection=projection, + unique=unique, + ) if peer_jid: - # if permission are checked, we must remove all file tha use can't access + # if permission are checked, we must remove all file tha use can't access to_remove = [] for file_data in files: try: @@ -1218,10 +1430,28 @@ defer.returnValue(files) @defer.inlineCallbacks - def setFile(self, client, name, file_id=None, version=u'', parent=None, path=None, - type_=C.FILE_TYPE_FILE, file_hash=None, hash_algo=None, size=None, namespace=None, - mime_type=None, created=None, modified=None, owner=None, access=None, extra=None, - peer_jid = None, perms_to_check=(C.ACCESS_PERM_WRITE,)): + def setFile( + self, + client, + name, + file_id=None, + version=u"", + parent=None, + path=None, + type_=C.FILE_TYPE_FILE, + file_hash=None, + hash_algo=None, + size=None, + namespace=None, + mime_type=None, + created=None, + modified=None, + owner=None, + access=None, + extra=None, + peer_jid=None, + perms_to_check=(C.ACCESS_PERM_WRITE,), + ): """set a file metadata @param name(unicode): basename of the file @@ -1258,12 +1488,17 @@ if None, permission will no be checked (peer_jid must be None too in this case) @param profile(unicode): profile owning the file """ - if '/' in name: + if "/" in name: raise ValueError('name must not contain a slash ("/")') if file_id is None: file_id = shortuuid.uuid() - if file_hash is not None and hash_algo is None or hash_algo is not None and file_hash is None: - raise ValueError('file_hash and hash_algo must be set at the same time') + if ( + file_hash is not None + and hash_algo is None + or hash_algo is not None + and file_hash is None + ): + raise ValueError("file_hash and hash_algo must be set at the same time") if mime_type is None: mime_type, file_encoding = mimetypes.guess_type(name) if created is None: @@ -1271,31 +1506,56 @@ if namespace is not None: namespace = namespace.strip() or None if type_ == C.FILE_TYPE_DIRECTORY: - if any(version, file_hash, size, mime_type): - raise ValueError(u"version, file_hash, size and mime_type can't be set for a directory") + if any(version, file_hash, size, mime_type): + raise ValueError( + u"version, file_hash, size and mime_type can't be set for a directory" + ) if owner is not None: owner = owner.userhostJID() if path is not None: # _getParentDir will check permissions if peer_jid is set, so we use owner - parent, remaining_path_elts = yield self._getParentDir(client, path, parent, namespace, owner, owner, perms_to_check) + parent, remaining_path_elts = yield self._getParentDir( + client, path, parent, namespace, owner, owner, perms_to_check + ) # if remaining directories don't exist, we have to create them for new_dir in remaining_path_elts: new_dir_id = shortuuid.uuid() - yield self.storage.setFile(client, name=new_dir, file_id=new_dir_id, version=u'', parent=parent, - type_=C.FILE_TYPE_DIRECTORY, namespace=namespace, - created=time.time(), - owner=owner, - access=access, extra={}) + yield self.storage.setFile( + client, + name=new_dir, + file_id=new_dir_id, + version=u"", + parent=parent, + type_=C.FILE_TYPE_DIRECTORY, + namespace=namespace, + created=time.time(), + owner=owner, + access=access, + extra={}, + ) parent = new_dir_id elif parent is None: - parent = u'' + parent = u"" - yield self.storage.setFile(client, file_id=file_id, version=version, parent=parent, type_=type_, - file_hash=file_hash, hash_algo=hash_algo, name=name, size=size, - namespace=namespace, mime_type=mime_type, created=created, modified=modified, - owner=owner, - access=access, extra=extra) + yield self.storage.setFile( + client, + file_id=file_id, + version=version, + parent=parent, + type_=type_, + file_hash=file_hash, + hash_algo=hash_algo, + name=name, + size=size, + namespace=namespace, + mime_type=mime_type, + created=created, + modified=modified, + owner=owner, + access=access, + extra=extra, + ) def fileUpdate(self, file_id, column, update_cb): """update a file column taking care of race condition @@ -1318,7 +1578,9 @@ @return (bool): True if entity is available """ if not entity_jid.resource: - return bool(self.getAvailableResources(client, entity_jid)) # is any resource is available, entity is available + return bool( + self.getAvailableResources(client, entity_jid) + ) # is any resource is available, entity is available try: presence_data = self.getEntityDatum(entity_jid, "presence", client.profile) except KeyError:
--- a/sat/memory/params.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/memory/params.py Wed Jun 27 20:14:46 2018 +0200 @@ -24,6 +24,7 @@ from sat.memory.crypto import BlockCipher, PasswordHasher from xml.dom import minidom, NotFoundErr from sat.core.log import getLogger + log = getLogger(__name__) from twisted.internet import defer from twisted.python.failure import Failure @@ -43,17 +44,18 @@ @return (generator[domish.Element]): <jid/> elements """ for jid_ in jids: - jid_elt = domish.Element((None, 'jid')) + jid_elt = domish.Element((None, "jid")) jid_elt.addContent(jid_.full()) yield jid_elt class Params(object): """This class manage parameters with xml""" + ### TODO: add desciption in params - #TODO: when priority is changed, a new presence stanza must be emitted - #TODO: int type (Priority should be int instead of string) + # TODO: when priority is changed, a new presence stanza must be emitted + # TODO: int type (Priority should be int instead of string) default_xml = u""" <params> <general> @@ -77,23 +79,23 @@ </individual> </params> """ % { - 'category_general': D_("General"), - 'category_connection': D_("Connection"), - 'history_param': C.HISTORY_LIMIT, - 'history_label': D_('Chat history limit'), - 'show_offline_contacts': C.SHOW_OFFLINE_CONTACTS, - 'show_offline_contacts_label': D_('Show offline contacts'), - 'show_empty_groups': C.SHOW_EMPTY_GROUPS, - 'show_empty_groups_label': D_('Show empty groups'), - 'force_server_param': C.FORCE_SERVER_PARAM, - 'force_port_param': C.FORCE_PORT_PARAM, - 'new_account_label': D_("Register new account"), - 'autoconnect_label': D_('Connect on frontend startup'), - 'autodisconnect_label': D_('Disconnect on frontend closure'), + "category_general": D_("General"), + "category_connection": D_("Connection"), + "history_param": C.HISTORY_LIMIT, + "history_label": D_("Chat history limit"), + "show_offline_contacts": C.SHOW_OFFLINE_CONTACTS, + "show_offline_contacts_label": D_("Show offline contacts"), + "show_empty_groups": C.SHOW_EMPTY_GROUPS, + "show_empty_groups_label": D_("Show empty groups"), + "force_server_param": C.FORCE_SERVER_PARAM, + "force_port_param": C.FORCE_PORT_PARAM, + "new_account_label": D_("Register new account"), + "autoconnect_label": D_("Connect on frontend startup"), + "autodisconnect_label": D_("Disconnect on frontend closure"), } def load_default_params(self): - self.dom = minidom.parseString(Params.default_xml.encode('utf-8')) + self.dom = minidom.parseString(Params.default_xml.encode("utf-8")) def _mergeParams(self, source_node, dest_node): """Look for every node in source_node and recursively copy them to dest if they don't exists""" @@ -102,8 +104,9 @@ ret = {} for child in children: if child.nodeType == child.ELEMENT_NODE: - ret[(child.tagName, child.getAttribute('name'))] = child + ret[(child.tagName, child.getAttribute("name"))] = child return ret + source_map = getNodesMap(source_node.childNodes) dest_map = getNodesMap(dest_node.childNodes) source_set = set(source_map.keys()) @@ -120,7 +123,7 @@ def load_xml(self, xml_file): """Load parameters template from xml file""" self.dom = minidom.parse(xml_file) - default_dom = minidom.parseString(Params.default_xml.encode('utf-8')) + default_dom = minidom.parseString(Params.default_xml.encode("utf-8")) self._mergeParams(default_dom.documentElement, self.dom.documentElement) def loadGenParams(self): @@ -140,7 +143,9 @@ """ if cache is None: self.params[profile] = {} - return self.storage.loadIndParams(self.params[profile] if cache is None else cache, profile) + return self.storage.loadIndParams( + self.params[profile] if cache is None else cache, profile + ) def purgeProfile(self, profile): """Remove cache data of a profile @@ -150,12 +155,14 @@ try: del self.params[profile] except KeyError: - log.error(_(u"Trying to purge cache of a profile not in memory: [%s]") % profile) + log.error( + _(u"Trying to purge cache of a profile not in memory: [%s]") % profile + ) def save_xml(self, filename): """Save parameters template to xml file""" - with open(filename, 'wb') as xml_file: - xml_file.write(self.dom.toxml('utf-8')) + with open(filename, "wb") as xml_file: + xml_file.write(self.dom.toxml("utf-8")) def __init__(self, host, storage): log.debug("Parameters init") @@ -174,7 +181,7 @@ @return: a Deferred instance """ if self.storage.hasProfile(profile): - log.info(_('The profile name already exists')) + log.info(_("The profile name already exists")) return defer.fail(Failure(exceptions.ConflictError)) if not self.host.trigger.point("ProfileCreation", profile): return defer.fail(Failure(exceptions.CancelError)) @@ -189,7 +196,7 @@ @return: a Deferred instance """ if not self.storage.hasProfile(profile): - log.info(_('Trying to delete an unknown profile')) + log.info(_("Trying to delete an unknown profile")) return defer.fail(Failure(exceptions.ProfileUnknownError(profile))) if self.host.isConnected(profile): if force: @@ -210,22 +217,26 @@ @raise exceptions.ProfileUnknownError: profile doesn't exists @raise exceptions.ProfileNotSetError: if C.PROF_KEY_NONE is used """ - if profile_key == '@DEFAULT@': - default = self.host.memory.memory_data.get('Profile_default') + if profile_key == "@DEFAULT@": + default = self.host.memory.memory_data.get("Profile_default") if not default: - log.info(_('No default profile, returning first one')) + log.info(_("No default profile, returning first one")) try: - default = self.host.memory.memory_data['Profile_default'] = self.storage.getProfilesList()[0] + default = self.host.memory.memory_data[ + "Profile_default" + ] = self.storage.getProfilesList()[0] except IndexError: - log.info(_('No profile exist yet')) + log.info(_("No profile exist yet")) raise exceptions.ProfileUnknownError(profile_key) - return default # FIXME: temporary, must use real default value, and fallback to first one if it doesn't exists + return ( + default + ) # FIXME: temporary, must use real default value, and fallback to first one if it doesn't exists elif profile_key == C.PROF_KEY_NONE: raise exceptions.ProfileNotSetError elif return_profile_keys and profile_key in [C.PROF_KEY_ALL]: - return profile_key # this value must be managed by the caller + return profile_key # this value must be managed by the caller if not self.storage.hasProfile(profile_key): - log.error(_(u'Trying to access an unknown profile (%s)') % profile_key) + log.error(_(u"Trying to access an unknown profile (%s)") % profile_key) raise exceptions.ProfileUnknownError(profile_key) return profile_key @@ -239,12 +250,12 @@ """ for node in parent.childNodes: if node.nodeName == tag and node.getAttribute("name") == name: - #the node already exists + # the node already exists return node - #the node is new + # the node is new return None - def updateParams(self, xml, security_limit=C.NO_SECURITY_LIMIT, app=''): + def updateParams(self, xml, security_limit=C.NO_SECURITY_LIMIT, app=""): """import xml in parameters, update if the param already exists If security_limit is specified and greater than -1, the parameters @@ -254,7 +265,7 @@ @param app: name of the frontend registering the parameters or empty value """ # TODO: should word with domish.Element - src_parent = minidom.parseString(xml.encode('utf-8')).documentElement + src_parent = minidom.parseString(xml.encode("utf-8")).documentElement def pre_process_app_node(src_parent, security_limit, app): """Parameters that are registered from a frontend must be checked""" @@ -264,17 +275,23 @@ to_remove.append(type_node) # accept individual parameters only continue for cat_node in type_node.childNodes: - if cat_node.nodeName != 'category': + if cat_node.nodeName != "category": to_remove.append(cat_node) continue - to_remove_count = 0 # count the params to be removed from current category + to_remove_count = ( + 0 + ) # count the params to be removed from current category for node in cat_node.childNodes: - if node.nodeName != "param" or not self.checkSecurityLimit(node, security_limit): + if node.nodeName != "param" or not self.checkSecurityLimit( + node, security_limit + ): to_remove.append(node) to_remove_count += 1 continue - node.setAttribute('app', app) - if len(cat_node.childNodes) == to_remove_count: # remove empty category + node.setAttribute("app", app) + if ( + len(cat_node.childNodes) == to_remove_count + ): # remove empty category for dummy in xrange(0, to_remove_count): to_remove.pop() to_remove.append(cat_node) @@ -283,9 +300,11 @@ def import_node(tgt_parent, src_parent): for child in src_parent.childNodes: - if child.nodeName == '#text': + if child.nodeName == "#text": continue - node = self.__get_unique_node(tgt_parent, child.nodeName, child.getAttribute("name")) + node = self.__get_unique_node( + tgt_parent, child.nodeName, child.getAttribute("name") + ) if not node: # The node is new tgt_parent.appendChild(child.cloneNode(True)) else: @@ -310,23 +329,35 @@ @param app: name of the frontend registering the parameters """ if not app: - log.warning(_(u"Trying to register frontends parameters with no specified app: aborted")) + log.warning( + _( + u"Trying to register frontends parameters with no specified app: aborted" + ) + ) return if not hasattr(self, "frontends_cache"): self.frontends_cache = [] if app in self.frontends_cache: - log.debug(_(u"Trying to register twice frontends parameters for %(app)s: aborted" % {"app": app})) + log.debug( + _( + u"Trying to register twice frontends parameters for %(app)s: aborted" + % {"app": app} + ) + ) return self.frontends_cache.append(app) self.updateParams(xml, security_limit, app) - log.debug(u"Frontends parameters registered for %(app)s" % {'app': app}) + log.debug(u"Frontends parameters registered for %(app)s" % {"app": app}) def __default_ok(self, value, name, category): - #FIXME: will not work with individual parameters + # FIXME: will not work with individual parameters self.setParam(name, value, category) def __default_ko(self, failure, name, category): - log.error(_(u"Can't determine default value for [%(category)s/%(name)s]: %(reason)s") % {'category': category, 'name': name, 'reason': str(failure.value)}) + log.error( + _(u"Can't determine default value for [%(category)s/%(name)s]: %(reason)s") + % {"category": category, "name": name, "reason": str(failure.value)} + ) def setDefault(self, name, category, callback, errback=None): """Set default value of parameter @@ -337,19 +368,27 @@ @param callback: must return a string with the value (use deferred if needed) @param errback: must manage the error with args failure, name, category """ - #TODO: send signal param update if value changed - #TODO: manage individual paramaters - log.debug ("setDefault called for %(category)s/%(name)s" % {"category": category, "name": name}) - node = self._getParamNode(name, category, '@ALL@') + # TODO: send signal param update if value changed + # TODO: manage individual paramaters + log.debug( + "setDefault called for %(category)s/%(name)s" + % {"category": category, "name": name} + ) + node = self._getParamNode(name, category, "@ALL@") if not node: - log.error(_(u"Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category}) + log.error( + _( + u"Requested param [%(name)s] in category [%(category)s] doesn't exist !" + ) + % {"name": name, "category": category} + ) return - if node[1].getAttribute('default_cb') == 'yes': + if node[1].getAttribute("default_cb") == "yes": # del node[1].attributes['default_cb'] # default_cb is not used anymore as a flag to know if we have to set the default value, - # and we can still use it later e.g. to call a generic setDefault method + # and we can still use it later e.g. to call a generic setDefault method value = self._getParam(category, name, C.GENERAL) - if value is None: # no value set by the user: we have the default value - log.debug ("Default value to set, using callback") + if value is None: # no value set by the user: we have the default value + log.debug("Default value to set, using callback") d = defer.maybeDeferred(callback) d.addCallback(self.__default_ok, name, category) d.addErrback(errback or self.__default_ko, name, category) @@ -364,30 +403,61 @@ @param value: user defined value @return: value (can be str, bool, int, list, None) """ - if attr == 'value': - value_to_use = value if value is not None else node.getAttribute(attr) # we use value (user defined) if it exist, else we use node's default value - if node.getAttribute('type') == 'bool': + if attr == "value": + value_to_use = ( + value if value is not None else node.getAttribute(attr) + ) # we use value (user defined) if it exist, else we use node's default value + if node.getAttribute("type") == "bool": return C.bool(value_to_use) - if node.getAttribute('type') == 'int': + if node.getAttribute("type") == "int": return int(value_to_use) - elif node.getAttribute('type') == 'list': - if not value_to_use: # no user defined value, take default value from the XML - options = [option for option in node.childNodes if option.nodeName == 'option'] - selected = [option for option in options if option.getAttribute('selected') == 'true'] - cat, param = node.parentNode.getAttribute('name'), node.getAttribute('name') + elif node.getAttribute("type") == "list": + if ( + not value_to_use + ): # no user defined value, take default value from the XML + options = [ + option + for option in node.childNodes + if option.nodeName == "option" + ] + selected = [ + option + for option in options + if option.getAttribute("selected") == "true" + ] + cat, param = ( + node.parentNode.getAttribute("name"), + node.getAttribute("name"), + ) if len(selected) == 1: - value_to_use = selected[0].getAttribute('value') - log.info(_("Unset parameter (%(cat)s, %(param)s) of type list will use the default option '%(value)s'") % - {'cat': cat, 'param': param, 'value': value_to_use}) + value_to_use = selected[0].getAttribute("value") + log.info( + _( + "Unset parameter (%(cat)s, %(param)s) of type list will use the default option '%(value)s'" + ) + % {"cat": cat, "param": param, "value": value_to_use} + ) return value_to_use if len(selected) == 0: - log.error(_(u'Parameter (%(cat)s, %(param)s) of type list has no default option!') % {'cat': cat, 'param': param}) + log.error( + _( + u"Parameter (%(cat)s, %(param)s) of type list has no default option!" + ) + % {"cat": cat, "param": param} + ) else: - log.error(_(u'Parameter (%(cat)s, %(param)s) of type list has more than one default option!') % {'cat': cat, 'param': param}) + log.error( + _( + u"Parameter (%(cat)s, %(param)s) of type list has more than one default option!" + ) + % {"cat": cat, "param": param} + ) raise exceptions.DataError - elif node.getAttribute('type') == 'jids_list': + elif node.getAttribute("type") == "jids_list": if value_to_use: - jids = value_to_use.split('\t') # FIXME: it's not good to use tabs as separator ! + jids = value_to_use.split( + "\t" + ) # FIXME: it's not good to use tabs as separator ! else: # no user defined value, take default value from the XML jids = [getText(jid_) for jid_ in node.getElementsByTagName("jid")] to_delete = [] @@ -395,7 +465,9 @@ try: jids[idx] = jid.JID(value) except (RuntimeError, jid.InvalidFormat, AttributeError): - log.warning(u"Incorrect jid value found in jids list: [{}]".format(value)) + log.warning( + u"Incorrect jid value found in jids list: [{}]".format(value) + ) to_delete.append(value) for value in to_delete: jids.remove(value) @@ -412,8 +484,10 @@ @param value: user defined value @return (unicode, bool, int, list): value to retrieve """ - if attr == 'value' and node.getAttribute('type') == 'password': - raise exceptions.InternalError('To retrieve password values, use _asyncGetAttr instead of _getAttr') + if attr == "value" and node.getAttribute("type") == "password": + raise exceptions.InternalError( + "To retrieve password values, use _asyncGetAttr instead of _getAttr" + ) return self._getAttr_internal(node, attr, value) def _asyncGetAttr(self, node, attr, value, profile=None): @@ -428,19 +502,27 @@ @return (unicode, bool, int, list): Deferred value to retrieve """ value = self._getAttr_internal(node, attr, value) - if attr != 'value' or node.getAttribute('type') != 'password': + if attr != "value" or node.getAttribute("type") != "password": return defer.succeed(value) - param_cat = node.parentNode.getAttribute('name') - param_name = node.getAttribute('name') + param_cat = node.parentNode.getAttribute("name") + param_name = node.getAttribute("name") if ((param_cat, param_name) == C.PROFILE_PASS_PATH) or not value: - return defer.succeed(value) # profile password and empty passwords are returned "as is" + return defer.succeed( + value + ) # profile password and empty passwords are returned "as is" if not profile: - raise exceptions.ProfileNotSetError('The profile is needed to decrypt a password') + raise exceptions.ProfileNotSetError( + "The profile is needed to decrypt a password" + ) d = self.host.memory.decryptValue(value, profile) def gotPlainPassword(password): - if password is None: # empty value means empty password, None means decryption failure - raise exceptions.InternalError(_('The stored password could not be decrypted!')) + if ( + password is None + ): # empty value means empty password, None means decryption failure + raise exceptions.InternalError( + _("The stored password could not be decrypted!") + ) return password return d.addCallback(gotPlainPassword) @@ -455,9 +537,13 @@ def getStringParamA(self, name, category, attr="value", profile_key=C.PROF_KEY_NONE): """ Same as getParamA but for bridge: convert non string value to string """ - return self.__type_to_string(self.getParamA(name, category, attr, profile_key=profile_key)) + return self.__type_to_string( + self.getParamA(name, category, attr, profile_key=profile_key) + ) - def getParamA(self, name, category, attr="value", use_default=True, profile_key=C.PROF_KEY_NONE): + def getParamA( + self, name, category, attr="value", use_default=True, profile_key=C.PROF_KEY_NONE + ): """Helper method to get a specific attribute. /!\ This method would return encrypted password values, @@ -474,15 +560,22 @@ # FIXME: security_limit is not managed here ! node = self._getParamNode(name, category) if not node: - log.error(_(u"Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category}) + log.error( + _( + u"Requested param [%(name)s] in category [%(category)s] doesn't exist !" + ) + % {"name": name, "category": category} + ) raise exceptions.NotFound - if attr == 'value' and node[1].getAttribute('type') == 'password': - raise exceptions.InternalError('To retrieve password values, use asyncGetParamA instead of getParamA') + if attr == "value" and node[1].getAttribute("type") == "password": + raise exceptions.InternalError( + "To retrieve password values, use asyncGetParamA instead of getParamA" + ) if node[0] == C.GENERAL: value = self._getParam(category, name, C.GENERAL) - if value is None and attr=='value' and not use_default: + if value is None and attr == "value" and not use_default: return value return self._getAttr(node[1], attr, value) @@ -490,25 +583,39 @@ profile = self.getProfileName(profile_key) if not profile: - log.error(_('Requesting a param for an non-existant profile')) + log.error(_("Requesting a param for an non-existant profile")) raise exceptions.ProfileUnknownError(profile_key) if profile not in self.params: - log.error(_('Requesting synchronous param for not connected profile')) + log.error(_("Requesting synchronous param for not connected profile")) raise exceptions.ProfileNotConnected(profile) if attr == "value": value = self._getParam(category, name, profile=profile) - if value is None and attr=='value' and not use_default: + if value is None and attr == "value" and not use_default: return value return self._getAttr(node[1], attr, value) - def asyncGetStringParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE): + def asyncGetStringParamA( + self, + name, + category, + attr="value", + security_limit=C.NO_SECURITY_LIMIT, + profile_key=C.PROF_KEY_NONE, + ): d = self.asyncGetParamA(name, category, attr, security_limit, profile_key) d.addCallback(self.__type_to_string) return d - def asyncGetParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE): + def asyncGetParamA( + self, + name, + category, + attr="value", + security_limit=C.NO_SECURITY_LIMIT, + profile_key=C.PROF_KEY_NONE, + ): """Helper method to get a specific attribute. @param name: name of the parameter @@ -519,12 +626,21 @@ """ node = self._getParamNode(name, category) if not node: - log.error(_(u"Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category}) + log.error( + _( + u"Requested param [%(name)s] in category [%(category)s] doesn't exist !" + ) + % {"name": name, "category": category} + ) raise ValueError("Requested param doesn't exist") if not self.checkSecurityLimit(node[1], security_limit): - log.warning(_(u"Trying to get parameter '%(param)s' in category '%(cat)s' without authorization!!!" - % {'param': name, 'cat': category})) + log.warning( + _( + u"Trying to get parameter '%(param)s' in category '%(cat)s' without authorization!!!" + % {"param": name, "cat": category} + ) + ) raise exceptions.PermissionError if node[0] == C.GENERAL: @@ -535,7 +651,9 @@ profile = self.getProfileName(profile_key) if not profile: - raise exceptions.InternalError(_('Requesting a param for a non-existant profile')) + raise exceptions.InternalError( + _("Requesting a param for a non-existant profile") + ) if attr != "value": return defer.succeed(node[1].getAttribute(attr)) @@ -543,9 +661,11 @@ value = self._getParam(category, name, profile=profile) return self._asyncGetAttr(node[1], attr, value, profile) except exceptions.ProfileNotInCacheError: - #We have to ask data to the storage manager + # We have to ask data to the storage manager d = self.storage.getIndParam(category, name, profile) - return d.addCallback(lambda value: self._asyncGetAttr(node[1], attr, value, profile)) + return d.addCallback( + lambda value: self._asyncGetAttr(node[1], attr, value, profile) + ) def asyncGetParamsValuesFromCategory(self, category, security_limit, profile_key): """Get all parameters "attribute" for a category @@ -557,7 +677,7 @@ @param profile_key: %(doc_profile_key)s @return (dict): key: param name, value: param value (converted to string if needed) """ - #TODO: manage category of general type (without existant profile) + # TODO: manage category of general type (without existant profile) profile = self.getProfileName(profile_key) if not profile: log.error(_("Asking params for inexistant profile")) @@ -572,11 +692,20 @@ for category_node in prof_xml.getElementsByTagName("category"): if category_node.getAttribute("name") == category: for param_node in category_node.getElementsByTagName("param"): - name = param_node.getAttribute('name') + name = param_node.getAttribute("name") if not name: - log.warning(u"ignoring attribute without name: {}".format(param_node.toxml())) + log.warning( + u"ignoring attribute without name: {}".format( + param_node.toxml() + ) + ) continue - d = self.asyncGetStringParamA(name, category, security_limit=security_limit, profile_key=profile) + d = self.asyncGetStringParamA( + name, + category, + security_limit=security_limit, + profile_key=profile, + ) d.addCallback(setValue, ret, name) names_d_list.append(d) break @@ -586,10 +715,12 @@ dlist.addCallback(lambda dummy: ret) return ret - d = self._constructProfileXml(security_limit, '', profile) + d = self._constructProfileXml(security_limit, "", profile) return d.addCallback(returnCategoryXml) - def _getParam(self, category, name, type_=C.INDIVIDUAL, cache=None, profile=C.PROF_KEY_NONE): + def _getParam( + self, category, name, type_=C.INDIVIDUAL, cache=None, profile=C.PROF_KEY_NONE + ): """Return the param, or None if it doesn't exist @param category: param category @@ -608,8 +739,10 @@ raise exceptions.ProfileNotSetError if profile in self.params: cache = self.params[profile] # if profile is in main cache, we use it, - # ignoring the temporary cache - elif cache is None: # else we use the temporary cache if it exists, or raise an exception + # ignoring the temporary cache + elif ( + cache is None + ): # else we use the temporary cache if it exists, or raise an exception raise exceptions.ProfileNotInCacheError if (category, name) not in cache: return None @@ -629,11 +762,13 @@ def checkNode(node): """Check the node against security_limit and app""" - return self.checkSecurityLimit(node, security_limit) and self.checkApp(node, app) + return self.checkSecurityLimit(node, security_limit) and self.checkApp( + node, app + ) def constructProfile(ignore, profile_cache): # init the result document - prof_xml = minidom.parseString('<params/>') + prof_xml = minidom.parseString("<params/>") cache = {} for type_node in self.dom.documentElement.childNodes: @@ -641,9 +776,9 @@ continue # we use all params, general and individual for cat_node in type_node.childNodes: - if cat_node.nodeName != 'category': + if cat_node.nodeName != "category": continue - category = cat_node.getAttribute('name') + category = cat_node.getAttribute("name") dest_params = {} # result (merged) params for category if category not in cache: # we make a copy for the new xml @@ -655,7 +790,7 @@ if not checkNode(node): to_remove.append(node) continue - dest_params[node.getAttribute('name')] = node + dest_params[node.getAttribute("name")] = node for node in to_remove: dest_cat.removeChild(node) new_node = True @@ -668,7 +803,7 @@ for param_node in params: # we have to merge new params (we are parsing individual parameters, we have to add them # to the previously parsed general ones) - name = param_node.getAttribute('name') + name = param_node.getAttribute("name") if not checkNode(param_node): continue if name not in dest_params: @@ -676,35 +811,53 @@ dest_params[name] = param_node.cloneNode(True) dest_cat.appendChild(dest_params[name]) - profile_value = self._getParam(category, - name, type_node.nodeName, - cache=profile_cache, profile=profile) + profile_value = self._getParam( + category, + name, + type_node.nodeName, + cache=profile_cache, + profile=profile, + ) if profile_value is not None: # there is a value for this profile, we must change the default - if dest_params[name].getAttribute('type') == 'list': - for option in dest_params[name].getElementsByTagName("option"): - if option.getAttribute('value') == profile_value: - option.setAttribute('selected', 'true') + if dest_params[name].getAttribute("type") == "list": + for option in dest_params[name].getElementsByTagName( + "option" + ): + if option.getAttribute("value") == profile_value: + option.setAttribute("selected", "true") else: try: - option.removeAttribute('selected') + option.removeAttribute("selected") except NotFoundErr: pass - elif dest_params[name].getAttribute('type') == 'jids_list': - jids = profile_value.split('\t') - for jid_elt in dest_params[name].getElementsByTagName("jid"): - dest_params[name].removeChild(jid_elt) # remove all default + elif dest_params[name].getAttribute("type") == "jids_list": + jids = profile_value.split("\t") + for jid_elt in dest_params[name].getElementsByTagName( + "jid" + ): + dest_params[name].removeChild( + jid_elt + ) # remove all default for jid_ in jids: # rebuilt the children with use values try: jid.JID(jid_) - except (RuntimeError, jid.InvalidFormat, AttributeError): - log.warning(u"Incorrect jid value found in jids list: [{}]".format(jid_)) + except ( + RuntimeError, + jid.InvalidFormat, + AttributeError, + ): + log.warning( + u"Incorrect jid value found in jids list: [{}]".format( + jid_ + ) + ) else: - jid_elt = prof_xml.createElement('jid') + jid_elt = prof_xml.createElement("jid") jid_elt.appendChild(prof_xml.createTextNode(jid_)) dest_params[name].appendChild(jid_elt) else: - dest_params[name].setAttribute('value', profile_value) + dest_params[name].setAttribute("value", profile_value) if new_node: prof_xml.documentElement.appendChild(dest_cat) @@ -721,7 +874,7 @@ d = defer.succeed(None) profile_cache = self.params[profile] else: - #profile is not in cache, we load values in a short time cache + # profile is not in cache, we load values in a short time cache profile_cache = {} d = self.loadIndParams(profile, profile_cache) @@ -761,9 +914,11 @@ def returnXML(prof_xml): return_xml = prof_xml.toxml() prof_xml.unlink() - return '\n'.join((line for line in return_xml.split('\n') if line)) + return "\n".join((line for line in return_xml.split("\n") if line)) - return self._constructProfileXml(security_limit, app, profile).addCallback(returnXML) + return self._constructProfileXml(security_limit, app, profile).addCallback( + returnXML + ) def _getParamNode(self, name, category, type_="@ALL@"): # FIXME: is type_ useful ? """Return a node from the param_xml @@ -776,9 +931,14 @@ @return: a tuple (node type, node) or None if not found""" for type_node in self.dom.documentElement.childNodes: - if (((type_ == "@ALL@" or type_ == "@GENERAL@") and type_node.nodeName == C.GENERAL) - or ((type_ == "@ALL@" or type_ == "@INDIVIDUAL@") and type_node.nodeName == C.INDIVIDUAL)): - for node in type_node.getElementsByTagName('category'): + if ( + (type_ == "@ALL@" or type_ == "@GENERAL@") + and type_node.nodeName == C.GENERAL + ) or ( + (type_ == "@ALL@" or type_ == "@INDIVIDUAL@") + and type_node.nodeName == C.INDIVIDUAL + ): + for node in type_node.getElementsByTagName("category"): if node.getAttribute("name") == category: params = node.getElementsByTagName("param") for param in params: @@ -795,7 +955,14 @@ categories.append(cat.getAttribute("name")) return categories - def setParam(self, name, value, category, security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE): + def setParam( + self, + name, + value, + category, + security_limit=C.NO_SECURITY_LIMIT, + profile_key=C.PROF_KEY_NONE, + ): """Set a parameter, return None if the parameter is not in param xml. Parameter of type 'password' that are not the SàT profile password are @@ -813,42 +980,59 @@ if profile_key != C.PROF_KEY_NONE: profile = self.getProfileName(profile_key) if not profile: - log.error(_(u'Trying to set parameter for an unknown profile')) + log.error(_(u"Trying to set parameter for an unknown profile")) raise exceptions.ProfileUnknownError(profile_key) - node = self._getParamNode(name, category, '@ALL@') + node = self._getParamNode(name, category, "@ALL@") if not node: - log.error(_(u'Requesting an unknown parameter (%(category)s/%(name)s)') - % {'category': category, 'name': name}) + log.error( + _(u"Requesting an unknown parameter (%(category)s/%(name)s)") + % {"category": category, "name": name} + ) return defer.succeed(None) if not self.checkSecurityLimit(node[1], security_limit): - log.warning(_(u"Trying to set parameter '%(param)s' in category '%(cat)s' without authorization!!!" - % {'param': name, 'cat': category})) + log.warning( + _( + u"Trying to set parameter '%(param)s' in category '%(cat)s' without authorization!!!" + % {"param": name, "cat": category} + ) + ) return defer.succeed(None) type_ = node[1].getAttribute("type") - if type_ == 'int': + if type_ == "int": if not value: # replace with the default value (which might also be '') value = node[1].getAttribute("value") else: try: int(value) except ValueError: - log.debug(_(u"Trying to set parameter '%(param)s' in category '%(cat)s' with an non-integer value" - % {'param': name, 'cat': category})) + log.debug( + _( + u"Trying to set parameter '%(param)s' in category '%(cat)s' with an non-integer value" + % {"param": name, "cat": category} + ) + ) return defer.succeed(None) if node[1].hasAttribute("constraint"): constraint = node[1].getAttribute("constraint") try: min_, max_ = [int(limit) for limit in constraint.split(";")] except ValueError: - raise exceptions.InternalError("Invalid integer parameter constraint: %s" % constraint) + raise exceptions.InternalError( + "Invalid integer parameter constraint: %s" % constraint + ) value = str(min(max(int(value), min_), max_)) - - log.info(_("Setting parameter (%(category)s, %(name)s) = %(value)s") % - {'category': category, 'name': name, 'value': value if type_ != 'password' else '********'}) + log.info( + _("Setting parameter (%(category)s, %(name)s) = %(value)s") + % { + "category": category, + "name": name, + "value": value if type_ != "password" else "********", + } + ) if node[0] == C.GENERAL: self.params_gen[(category, name)] = value @@ -856,7 +1040,9 @@ for profile in self.storage.getProfilesList(): if self.host.memory.isSessionStarted(profile): self.host.bridge.paramUpdate(name, value, category, profile) - self.host.trigger.point("paramUpdateTrigger", name, value, category, node[0], profile) + self.host.trigger.point( + "paramUpdateTrigger", name, value, category, node[0], profile + ) return defer.succeed(None) assert node[0] == C.INDIVIDUAL @@ -867,16 +1053,24 @@ return defer.succeed(None) elif type_ == "password": try: - personal_key = self.host.memory.auth_sessions.profileGetUnique(profile)[C.MEMORY_CRYPTO_KEY] + personal_key = self.host.memory.auth_sessions.profileGetUnique(profile)[ + C.MEMORY_CRYPTO_KEY + ] except TypeError: - raise exceptions.InternalError(_('Trying to encrypt a password while the personal key is undefined!')) + raise exceptions.InternalError( + _("Trying to encrypt a password while the personal key is undefined!") + ) if (category, name) == C.PROFILE_PASS_PATH: # using 'value' as the encryption key to encrypt another encryption key... could be confusing! - d = self.host.memory.encryptPersonalData(data_key=C.MEMORY_CRYPTO_KEY, - data_value=personal_key, - crypto_key=value, - profile=profile) - d.addCallback(lambda dummy: PasswordHasher.hash(value)) # profile password is hashed (empty value stays empty) + d = self.host.memory.encryptPersonalData( + data_key=C.MEMORY_CRYPTO_KEY, + data_value=personal_key, + crypto_key=value, + profile=profile, + ) + d.addCallback( + lambda dummy: PasswordHasher.hash(value) + ) # profile password is hashed (empty value stays empty) elif value: # other non empty passwords are encrypted with the personal key d = BlockCipher.encrypt(personal_key, value) else: @@ -888,7 +1082,9 @@ if self.host.memory.isSessionStarted(profile): self.params[profile][(category, name)] = value self.host.bridge.paramUpdate(name, value, category, profile) - self.host.trigger.point("paramUpdateTrigger", name, value, category, node[0], profile) + self.host.trigger.point( + "paramUpdateTrigger", name, value, category, node[0], profile + ) return self.storage.setIndParam(category, name, value, profile) else: raise exceptions.ProfileNotConnected @@ -912,10 +1108,15 @@ """ ret = {} for type_node in self.dom.documentElement.childNodes: - if (((node_type == "@ALL@" or node_type == "@GENERAL@") and type_node.nodeName == C.GENERAL) or - ((node_type == "@ALL@" or node_type == "@INDIVIDUAL@") and type_node.nodeName == C.INDIVIDUAL)): - for cat_node in type_node.getElementsByTagName('category'): - cat = cat_node.getAttribute('name') + if ( + (node_type == "@ALL@" or node_type == "@GENERAL@") + and type_node.nodeName == C.GENERAL + ) or ( + (node_type == "@ALL@" or node_type == "@INDIVIDUAL@") + and type_node.nodeName == C.INDIVIDUAL + ): + for cat_node in type_node.getElementsByTagName("category"): + cat = cat_node.getAttribute("name") params = cat_node.getElementsByTagName("param") for param in params: if param.getAttribute("type") == attr_type:
--- a/sat/plugins/__init__.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/__init__.py Wed Jun 27 20:14:46 2018 +0200 @@ -2,6 +2,7 @@ # XXX: the Monkey Patch is here and not in src/__init__ to avoir issues with pyjamas compilation import wokkel from sat_tmp.wokkel import pubsub as tmp_pubsub, rsm as tmp_rsm, mam as tmp_mam + wokkel.pubsub = tmp_pubsub wokkel.rsm = tmp_rsm wokkel.mam = tmp_mam
--- a/sat/plugins/plugin_adhoc_dbus.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_adhoc_dbus.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,18 +20,23 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from twisted.internet import defer from wokkel import data_form + try: from lxml import etree except ImportError: - raise exceptions.MissingModule(u"Missing module lxml, please download/install it from http://lxml.de/") + raise exceptions.MissingModule( + u"Missing module lxml, please download/install it from http://lxml.de/" + ) import os.path import uuid import dbus from dbus.mainloop.glib import DBusGMainLoop + DBusGMainLoop(set_as_default=True) FD_NAME = "org.freedesktop.DBus" @@ -39,8 +44,12 @@ INTROSPECT_IFACE = "org.freedesktop.DBus.Introspectable" INTROSPECT_METHOD = "Introspect" -IGNORED_IFACES_START = ('org.freedesktop', 'org.qtproject', 'org.kde.KMainWindow') # commands in interface starting with these values will be ignored -FLAG_LOOP = 'LOOP' +IGNORED_IFACES_START = ( + "org.freedesktop", + "org.qtproject", + "org.kde.KMainWindow", +) # commands in interface starting with these values will be ignored +FLAG_LOOP = "LOOP" PLUGIN_INFO = { C.PI_NAME: "Ad-Hoc Commands - D-Bus", @@ -50,21 +59,25 @@ C.PI_DEPENDENCIES: ["XEP-0050"], C.PI_MAIN: "AdHocDBus", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Add D-Bus management to Ad-Hoc commands""") + C.PI_DESCRIPTION: _("""Add D-Bus management to Ad-Hoc commands"""), } class AdHocDBus(object): - def __init__(self, host): log.info(_("plugin Ad-Hoc D-Bus initialization")) self.host = host - host.bridge.addMethod("adHocDBusAddAuto", ".plugin", in_sign='sasasasasasass', out_sign='(sa(sss))', - method=self._adHocDBusAddAuto, - async=True) + host.bridge.addMethod( + "adHocDBusAddAuto", + ".plugin", + in_sign="sasasasasasass", + out_sign="(sa(sss))", + method=self._adHocDBusAddAuto, + async=True, + ) self.session_bus = dbus.SessionBus() self.fd_object = self.session_bus.get_object(FD_NAME, FD_PATH, introspect=False) - self.XEP_0050 = host.plugins['XEP-0050'] + self.XEP_0050 = host.plugins["XEP-0050"] def _DBusAsyncCall(self, proxy, method, *args, **kwargs): """ Call a DBus method asynchronously and return a deferred @@ -77,9 +90,9 @@ """ d = defer.Deferred() - interface = kwargs.pop('interface', None) - kwargs['reply_handler'] = lambda ret=None: d.callback(ret) - kwargs['error_handler'] = d.errback + interface = kwargs.pop("interface", None) + kwargs["reply_handler"] = lambda ret=None: d.callback(ret) + kwargs["error_handler"] = d.errback proxy.get_dbus_method(method, dbus_interface=interface)(*args, **kwargs) return d @@ -95,7 +108,9 @@ @return: True if the method is acceptable """ - if method.xpath("arg[@direction='in']"): # we don't accept method with argument for the moment + if method.xpath( + "arg[@direction='in']" + ): # we don't accept method with argument for the moment return False return True @@ -104,30 +119,61 @@ log.debug("introspecting path [%s]" % proxy.object_path) introspect_xml = yield self._DBusIntrospect(proxy) el = etree.fromstring(introspect_xml) - for node in el.iterchildren('node', 'interface'): - if node.tag == 'node': - new_path = os.path.join(proxy.object_path, node.get('name')) - new_proxy = self.session_bus.get_object(bus_name, new_path, introspect=False) + for node in el.iterchildren("node", "interface"): + if node.tag == "node": + new_path = os.path.join(proxy.object_path, node.get("name")) + new_proxy = self.session_bus.get_object( + bus_name, new_path, introspect=False + ) yield self._introspect(methods, bus_name, new_proxy) - elif node.tag == 'interface': - name = node.get('name') + elif node.tag == "interface": + name = node.get("name") if any(name.startswith(ignored) for ignored in IGNORED_IFACES_START): - log.debug('interface [%s] is ignored' % name) + log.debug("interface [%s] is ignored" % name) continue log.debug("introspecting interface [%s]" % name) - for method in node.iterchildren('method'): + for method in node.iterchildren("method"): if self._acceptMethod(method): - method_name = method.get('name') + method_name = method.get("name") log.debug("method accepted: [%s]" % method_name) methods.add((proxy.object_path, name, method_name)) - def _adHocDBusAddAuto(self, prog_name, allowed_jids, allowed_groups, allowed_magics, forbidden_jids, forbidden_groups, flags, profile_key): - return self.adHocDBusAddAuto(prog_name, allowed_jids, allowed_groups, allowed_magics, forbidden_jids, forbidden_groups, flags, profile_key) + def _adHocDBusAddAuto( + self, + prog_name, + allowed_jids, + allowed_groups, + allowed_magics, + forbidden_jids, + forbidden_groups, + flags, + profile_key, + ): + return self.adHocDBusAddAuto( + prog_name, + allowed_jids, + allowed_groups, + allowed_magics, + forbidden_jids, + forbidden_groups, + flags, + profile_key, + ) @defer.inlineCallbacks - def adHocDBusAddAuto(self, prog_name, allowed_jids=None, allowed_groups=None, allowed_magics=None, forbidden_jids=None, forbidden_groups=None, flags=None, profile_key=C.PROF_KEY_NONE): + def adHocDBusAddAuto( + self, + prog_name, + allowed_jids=None, + allowed_groups=None, + allowed_magics=None, + forbidden_jids=None, + forbidden_groups=None, + flags=None, + profile_key=C.PROF_KEY_NONE, + ): bus_names = yield self._DBusListNames() - bus_names = [bus_name for bus_name in bus_names if '.' + prog_name in bus_name] + bus_names = [bus_name for bus_name in bus_names if "." + prog_name in bus_name] if not bus_names: log.info("Can't find any bus for [%s]" % prog_name) defer.returnValue(("", [])) @@ -136,45 +182,62 @@ if bus_name.endswith(prog_name): break log.info("bus name found: [%s]" % bus_name) - proxy = self.session_bus.get_object(bus_name, '/', introspect=False) + proxy = self.session_bus.get_object(bus_name, "/", introspect=False) methods = set() yield self._introspect(methods, bus_name, proxy) if methods: - self._addCommand(prog_name, bus_name, methods, - allowed_jids = allowed_jids, - allowed_groups = allowed_groups, - allowed_magics = allowed_magics, - forbidden_jids = forbidden_jids, - forbidden_groups = forbidden_groups, - flags = flags, - profile_key = profile_key) + self._addCommand( + prog_name, + bus_name, + methods, + allowed_jids=allowed_jids, + allowed_groups=allowed_groups, + allowed_magics=allowed_magics, + forbidden_jids=forbidden_jids, + forbidden_groups=forbidden_groups, + flags=flags, + profile_key=profile_key, + ) defer.returnValue((bus_name, methods)) - - def _addCommand(self, adhoc_name, bus_name, methods, allowed_jids=None, allowed_groups=None, allowed_magics=None, forbidden_jids=None, forbidden_groups=None, flags=None, profile_key=C.PROF_KEY_NONE): + def _addCommand( + self, + adhoc_name, + bus_name, + methods, + allowed_jids=None, + allowed_groups=None, + allowed_magics=None, + forbidden_jids=None, + forbidden_groups=None, + flags=None, + profile_key=C.PROF_KEY_NONE, + ): if flags is None: flags = set() def DBusCallback(command_elt, session_data, action, node, profile): - actions = session_data.setdefault('actions',[]) - names_map = session_data.setdefault('names_map', {}) + actions = session_data.setdefault("actions", []) + names_map = session_data.setdefault("names_map", {}) actions.append(action) if len(actions) == 1: # it's our first request, we ask the desired new status status = self.XEP_0050.STATUS.EXECUTING - form = data_form.Form('form', title=_('Command selection')) + form = data_form.Form("form", title=_("Command selection")) options = [] for path, iface, command in methods: - label = command.rsplit('.',1)[-1] + label = command.rsplit(".", 1)[-1] name = str(uuid.uuid4()) names_map[name] = (path, iface, command) options.append(data_form.Option(name, label)) - field = data_form.Field('list-single', 'command', options=options, required=True) + field = data_form.Field( + "list-single", "command", options=options, required=True + ) form.addField(field) payload = form.toElement() @@ -183,9 +246,9 @@ elif len(actions) == 2: # we should have the answer here try: - x_elt = command_elt.elements(data_form.NS_X_DATA,'x').next() + x_elt = command_elt.elements(data_form.NS_X_DATA, "x").next() answer_form = data_form.Form.fromElement(x_elt) - command = answer_form['command'] + command = answer_form["command"] except (KeyError, StopIteration): raise self.XEP_0050.AdHocError(self.XEP_0050.ERROR.BAD_PAYLOAD) @@ -202,9 +265,11 @@ # We have a loop, so we clear everything and we execute again the command as we had a first call (command_elt is not used, so None is OK) del actions[:] names_map.clear() - return DBusCallback(None, session_data, self.XEP_0050.ACTION.EXECUTE, node, profile) - form = data_form.Form('form', title=_(u'Updated')) - form.addField(data_form.Field('fixed', u'Command sent')) + return DBusCallback( + None, session_data, self.XEP_0050.ACTION.EXECUTE, node, profile + ) + form = data_form.Form("form", title=_(u"Updated")) + form.addField(data_form.Field("fixed", u"Command sent")) status = self.XEP_0050.STATUS.COMPLETED payload = None note = (self.XEP_0050.NOTE.INFO, _(u"Command sent")) @@ -213,10 +278,13 @@ return (payload, status, None, note) - self.XEP_0050.addAdHocCommand(DBusCallback, adhoc_name, - allowed_jids = allowed_jids, - allowed_groups = allowed_groups, - allowed_magics = allowed_magics, - forbidden_jids = forbidden_jids, - forbidden_groups = forbidden_groups, - profile_key = profile_key) + self.XEP_0050.addAdHocCommand( + DBusCallback, + adhoc_name, + allowed_jids=allowed_jids, + allowed_groups=allowed_groups, + allowed_magics=allowed_magics, + forbidden_jids=forbidden_jids, + forbidden_groups=forbidden_groups, + profile_key=profile_key, + )
--- a/sat/plugins/plugin_blog_import.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_blog_import.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.internet import defer from twisted.web import client as web_client @@ -41,33 +42,36 @@ C.PI_DEPENDENCIES: ["IMPORT", "XEP-0060", "XEP-0277", "TEXT-SYNTAXES", "UPLOAD"], C.PI_MAIN: "BlogImportPlugin", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _(u"""Blog import management: -This plugin manage the different blog importers which can register to it, and handle generic importing tasks.""") + C.PI_DESCRIPTION: _( + u"""Blog import management: +This plugin manage the different blog importers which can register to it, and handle generic importing tasks.""" + ), } -OPT_HOST = 'host' -OPT_UPLOAD_IMAGES = 'upload_images' -OPT_UPLOAD_IGNORE_HOST = 'upload_ignore_host' -OPT_IGNORE_TLS = 'ignore_tls_errors' -URL_REDIRECT_PREFIX = 'url_redirect_' +OPT_HOST = "host" +OPT_UPLOAD_IMAGES = "upload_images" +OPT_UPLOAD_IGNORE_HOST = "upload_ignore_host" +OPT_IGNORE_TLS = "ignore_tls_errors" +URL_REDIRECT_PREFIX = "url_redirect_" class BlogImportPlugin(object): BOOL_OPTIONS = (OPT_UPLOAD_IMAGES, OPT_IGNORE_TLS) JSON_OPTIONS = () - OPT_DEFAULTS = {OPT_UPLOAD_IMAGES: True, - OPT_IGNORE_TLS: False} + OPT_DEFAULTS = {OPT_UPLOAD_IMAGES: True, OPT_IGNORE_TLS: False} def __init__(self, host): log.info(_("plugin Blog Import initialization")) self.host = host - self._u = host.plugins['UPLOAD'] - self._p = host.plugins['XEP-0060'] - self._m = host.plugins['XEP-0277'] - self._s = self.host.plugins['TEXT-SYNTAXES'] - host.plugins['IMPORT'].initialize(self, u'blog') + self._u = host.plugins["UPLOAD"] + self._p = host.plugins["XEP-0060"] + self._m = host.plugins["XEP-0277"] + self._s = self.host.plugins["TEXT-SYNTAXES"] + host.plugins["IMPORT"].initialize(self, u"blog") - def importItem(self, client, item_import_data, session, options, return_data, service, node): + def importItem( + self, client, item_import_data, session, options, return_data, service, node + ): """importItem specialized for blog import @param item_import_data(dict): @@ -99,51 +103,58 @@ @param return_data(dict): will contain link between former posts and new items """ - mb_data = item_import_data['blog'] + mb_data = item_import_data["blog"] try: - item_id = mb_data['id'] + item_id = mb_data["id"] except KeyError: - item_id = mb_data['id'] = unicode(shortuuid.uuid()) + item_id = mb_data["id"] = unicode(shortuuid.uuid()) try: # we keep the link between old url and new blog item # so the user can redirect its former blog urls - old_uri = item_import_data['url'] + old_uri = item_import_data["url"] except KeyError: pass else: new_uri = return_data[URL_REDIRECT_PREFIX + old_uri] = self._p.getNodeURI( service if service is not None else client.jid.userhostJID(), node or self._m.namespace, - item_id) - log.info(u"url link from {old} to {new}".format( - old=old_uri, new=new_uri)) + item_id, + ) + log.info(u"url link from {old} to {new}".format(old=old_uri, new=new_uri)) return mb_data @defer.inlineCallbacks def importSubItems(self, client, item_import_data, mb_data, session, options): # comments data - if len(item_import_data['comments']) != 1: + if len(item_import_data["comments"]) != 1: raise NotImplementedError(u"can't manage multiple comment links") - allow_comments = C.bool(mb_data.get('allow_comments', C.BOOL_FALSE)) + allow_comments = C.bool(mb_data.get("allow_comments", C.BOOL_FALSE)) if allow_comments: comments_service = yield self._m.getCommentsService(client) - comments_node = self._m.getCommentsNode(mb_data['id']) - mb_data['comments_service'] = comments_service.full() - mb_data['comments_node'] = comments_node + comments_node = self._m.getCommentsNode(mb_data["id"]) + mb_data["comments_service"] = comments_service.full() + mb_data["comments_node"] = comments_node recurse_kwargs = { - 'items_import_data':item_import_data['comments'][0], - 'service':comments_service, - 'node':comments_node} + "items_import_data": item_import_data["comments"][0], + "service": comments_service, + "node": comments_node, + } defer.returnValue(recurse_kwargs) else: - if item_import_data['comments'][0]: - raise exceptions.DataError(u"allow_comments set to False, but comments are there") + if item_import_data["comments"][0]: + raise exceptions.DataError( + u"allow_comments set to False, but comments are there" + ) defer.returnValue(None) def publishItem(self, client, mb_data, service, node, session): - log.debug(u"uploading item [{id}]: {title}".format(id=mb_data['id'], title=mb_data.get('title',''))) + log.debug( + u"uploading item [{id}]: {title}".format( + id=mb_data["id"], title=mb_data.get("title", "") + ) + ) return self._m.send(client, mb_data, service, node) @defer.inlineCallbacks @@ -161,54 +172,80 @@ return # we want only XHTML content - for prefix in ('content',): # a tuple is use, if title need to be added in the future + for prefix in ( + "content", + ): # a tuple is use, if title need to be added in the future try: - rich = mb_data['{}_rich'.format(prefix)] + rich = mb_data["{}_rich".format(prefix)] except KeyError: pass else: - if '{}_xhtml'.format(prefix) in mb_data: - raise exceptions.DataError(u"importer gave {prefix}_rich and {prefix}_xhtml at the same time, this is not allowed".format(prefix=prefix)) + if "{}_xhtml".format(prefix) in mb_data: + raise exceptions.DataError( + u"importer gave {prefix}_rich and {prefix}_xhtml at the same time, this is not allowed".format( + prefix=prefix + ) + ) # we convert rich syntax to XHTML here, so we can handle filters easily - converted = yield self._s.convert(rich, self._s.getCurrentSyntax(client.profile), safe=False) - mb_data['{}_xhtml'.format(prefix)] = converted - del mb_data['{}_rich'.format(prefix)] + converted = yield self._s.convert( + rich, self._s.getCurrentSyntax(client.profile), safe=False + ) + mb_data["{}_xhtml".format(prefix)] = converted + del mb_data["{}_rich".format(prefix)] try: - mb_data['txt'] + mb_data["txt"] except KeyError: pass else: - if '{}_xhtml'.format(prefix) in mb_data: - log.warning(u"{prefix}_text will be replaced by converted {prefix}_xhtml, so filters can be handled".format(prefix=prefix)) - del mb_data['{}_text'.format(prefix)] + if "{}_xhtml".format(prefix) in mb_data: + log.warning( + u"{prefix}_text will be replaced by converted {prefix}_xhtml, so filters can be handled".format( + prefix=prefix + ) + ) + del mb_data["{}_text".format(prefix)] else: - log.warning(u"importer gave a text {prefix}, blog filters don't work on text {prefix}".format(prefix=prefix)) + log.warning( + u"importer gave a text {prefix}, blog filters don't work on text {prefix}".format( + prefix=prefix + ) + ) return # at this point, we have only XHTML version of content try: - top_elt = xml_tools.ElementParser()(mb_data['content_xhtml'], namespace=C.NS_XHTML) + top_elt = xml_tools.ElementParser()( + mb_data["content_xhtml"], namespace=C.NS_XHTML + ) except domish.ParserError: # we clean the xml and try again our luck - cleaned = yield self._s.cleanXHTML(mb_data['content_xhtml']) + cleaned = yield self._s.cleanXHTML(mb_data["content_xhtml"]) top_elt = xml_tools.ElementParser()(cleaned, namespace=C.NS_XHTML) opt_host = options.get(OPT_HOST) if opt_host: # we normalise the domain parsed_host = urlparse.urlsplit(opt_host) - opt_host = urlparse.urlunsplit((parsed_host.scheme or 'http', parsed_host.netloc or parsed_host.path, '', '', '')) + opt_host = urlparse.urlunsplit( + ( + parsed_host.scheme or "http", + parsed_host.netloc or parsed_host.path, + "", + "", + "", + ) + ) tmp_dir = tempfile.mkdtemp() try: # TODO: would be nice to also update the hyperlinks to these images, e.g. when you have <a href="{url}"><img src="{url}"></a> - for img_elt in xml_tools.findAll(top_elt, names=[u'img']): + for img_elt in xml_tools.findAll(top_elt, names=[u"img"]): yield self.imgFilters(client, img_elt, options, opt_host, tmp_dir) finally: - os.rmdir(tmp_dir) # XXX: tmp_dir should be empty, or something went wrong + os.rmdir(tmp_dir) # XXX: tmp_dir should be empty, or something went wrong # we now replace the content with filtered one - mb_data['content_xhtml'] = top_elt.toXml() + mb_data["content_xhtml"] = top_elt.toXml() @defer.inlineCallbacks def imgFilters(self, client, img_elt, options, opt_host, tmp_dir): @@ -222,15 +259,18 @@ @param tmp_dir(str): path to temp directory """ try: - url = img_elt['src'] - if url[0] == u'/': + url = img_elt["src"] + if url[0] == u"/": if not opt_host: - log.warning(u"host was not specified, we can't deal with src without host ({url}) and have to ignore the following <img/>:\n{xml}" - .format(url=url, xml=img_elt.toXml())) + log.warning( + u"host was not specified, we can't deal with src without host ({url}) and have to ignore the following <img/>:\n{xml}".format( + url=url, xml=img_elt.toXml() + ) + ) return else: url = urlparse.urljoin(opt_host, url) - filename = url.rsplit('/',1)[-1].strip() + filename = url.rsplit("/", 1)[-1].strip() if not filename: raise KeyError except (KeyError, IndexError): @@ -238,7 +278,7 @@ return # we change the url for the normalized one - img_elt['src'] = url + img_elt["src"] = url if options.get(OPT_UPLOAD_IMAGES, False): # upload is requested @@ -250,23 +290,32 @@ # host is the ignored one, we skip parsed_url = urlparse.urlsplit(url) if ignore_host in parsed_url.hostname: - log.info(u"Don't upload image at {url} because of {opt} option".format( - url=url, opt=OPT_UPLOAD_IGNORE_HOST)) + log.info( + u"Don't upload image at {url} because of {opt} option".format( + url=url, opt=OPT_UPLOAD_IGNORE_HOST + ) + ) return # we download images and re-upload them via XMPP - tmp_file = os.path.join(tmp_dir, filename).encode('utf-8') - upload_options = {'ignore_tls_errors': options.get(OPT_IGNORE_TLS, False)} + tmp_file = os.path.join(tmp_dir, filename).encode("utf-8") + upload_options = {"ignore_tls_errors": options.get(OPT_IGNORE_TLS, False)} try: - yield web_client.downloadPage(url.encode('utf-8'), tmp_file) - filename = filename.replace(u'%', u'_') # FIXME: tmp workaround for a bug in prosody http upload - dummy, download_d = yield self._u.upload(client, tmp_file, filename, options=upload_options) + yield web_client.downloadPage(url.encode("utf-8"), tmp_file) + filename = filename.replace( + u"%", u"_" + ) # FIXME: tmp workaround for a bug in prosody http upload + dummy, download_d = yield self._u.upload( + client, tmp_file, filename, options=upload_options + ) download_url = yield download_d except Exception as e: - log.warning(u"can't download image at {url}: {reason}".format(url=url, reason=e)) + log.warning( + u"can't download image at {url}: {reason}".format(url=url, reason=e) + ) else: - img_elt['src'] = download_url + img_elt["src"] = download_url try: os.unlink(tmp_file)
--- a/sat/plugins/plugin_blog_import_dokuwiki.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_blog_import_dokuwiki.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from sat.tools import xml_tools @@ -33,14 +34,19 @@ import re import time import os.path + try: from dokuwiki import DokuWiki, DokuWikiError # this is a new dependency except ImportError: - raise exceptions.MissingModule(u'Missing module dokuwiki, please install it with "pip install dokuwiki"') + raise exceptions.MissingModule( + u'Missing module dokuwiki, please install it with "pip install dokuwiki"' + ) try: from PIL import Image # this is already needed by plugin XEP-0054 except: - raise exceptions.MissingModule(u"Missing module pillow, please download/install it from https://python-pillow.github.io") + raise exceptions.MissingModule( + u"Missing module pillow, please download/install it from https://python-pillow.github.io" + ) PLUGIN_INFO = { C.PI_NAME: "Dokuwiki import", @@ -49,12 +55,13 @@ C.PI_DEPENDENCIES: ["BLOG_IMPORT"], C.PI_MAIN: "DokuwikiImport", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Blog importer for Dokuwiki blog engine.""") + C.PI_DESCRIPTION: _("""Blog importer for Dokuwiki blog engine."""), } SHORT_DESC = D_(u"import posts from Dokuwiki blog engine") -LONG_DESC = D_(u"""This importer handle Dokuwiki blog engine. +LONG_DESC = D_( + u"""This importer handle Dokuwiki blog engine. To use it, you need an admin access to a running Dokuwiki website (local or on the Internet). The importer retrieves the data using @@ -91,15 +98,17 @@ "souliane", and it imports them to sat profile dave's microblog node. Internal Dokuwiki media that were hosted on http://127.0.1.1 are now pointing to http://media.diekulturvermittlung.at. -""") +""" +) DEFAULT_MEDIA_REPO = "" DEFAULT_NAMESPACE = "/" DEFAULT_LIMIT = 100 # you might get a DBUS timeout (no reply) if it lasts too long class Importer(DokuWiki): - - def __init__(self, url, user, passwd, media_repo=DEFAULT_MEDIA_REPO, limit=DEFAULT_LIMIT): + def __init__( + self, url, user, passwd, media_repo=DEFAULT_MEDIA_REPO, limit=DEFAULT_LIMIT + ): """ @param url (unicode): DokuWiki site URL @@ -120,7 +129,7 @@ @param post(dict): parsed post data @return (unicode): post unique item id """ - return unicode(post['id']) + return unicode(post["id"]) def getPostUpdated(self, post): """Return the update date. @@ -128,7 +137,7 @@ @param post(dict): parsed post data @return (unicode): update date """ - return unicode(post['mtime']) + return unicode(post["mtime"]) def getPostPublished(self, post): """Try to parse the date from the message ID, else use "mtime". @@ -179,22 +188,23 @@ # title = content.split("\n")[0].strip(u"\ufeff= ") # build the extra data dictionary - mb_data = {"id": id_, - "published": published, - "updated": updated, - "author": profile_jid.user, - # "content": content, # when passed, it is displayed in Libervia instead of content_xhtml - "content_xhtml": content_xhtml, - # "title": title, - "allow_comments": "true", - } + mb_data = { + "id": id_, + "published": published, + "updated": updated, + "author": profile_jid.user, + # "content": content, # when passed, it is displayed in Libervia instead of content_xhtml + "content_xhtml": content_xhtml, + # "title": title, + "allow_comments": "true", + } # find out if the message access is public or restricted namespace = id_.split(":")[0] if namespace and namespace.lower() not in ("public", "/"): mb_data["group"] = namespace # roster group must exist - self.posts_data[id_] = {'blog': mb_data, 'comments':[[]]} + self.posts_data[id_] = {"blog": mb_data, "comments": [[]]} def process(self, client, namespace=DEFAULT_NAMESPACE): """Process a namespace or a single page. @@ -206,7 +216,9 @@ try: pages_list = self.pages.list(namespace) except DokuWikiError: - log.warning('Could not list Dokuwiki pages: please turn the "display_errors" setting to "Off" in the php.ini of the webserver hosting DokuWiki.') + log.warning( + 'Could not list Dokuwiki pages: please turn the "display_errors" setting to "Off" in the php.ini of the webserver hosting DokuWiki.' + ) return if not pages_list: # namespace is actually a page? @@ -220,7 +232,7 @@ for page in pages_list: self.processPost(page, profile_jid) count += 1 - if count >= self.limit : + if count >= self.limit: break return (self.posts_data.itervalues(), len(self.posts_data)) @@ -334,14 +346,12 @@ log.error("Cannot create DokuWiki media thumbnail %s" % dest) - class DokuwikiImport(object): - def __init__(self, host): log.info(_("plugin Dokuwiki Import initialization")) self.host = host - self._blog_import = host.plugins['BLOG_IMPORT'] - self._blog_import.register('dokuwiki', self.DkImport, SHORT_DESC, LONG_DESC) + self._blog_import = host.plugins["BLOG_IMPORT"] + self._blog_import.register("dokuwiki", self.DkImport, SHORT_DESC, LONG_DESC) def DkImport(self, client, location, options=None): """Import from DokuWiki to PubSub @@ -367,14 +377,22 @@ try: media_repo = options["media_repo"] if opt_upload_images: - options[self._blog_import.OPT_UPLOAD_IMAGES] = False # force using --no-images-upload - info_msg = _("DokuWiki media files will be *downloaded* to {temp_dir} - to finish the import you have to upload them *manually* to {media_repo}") + options[ + self._blog_import.OPT_UPLOAD_IMAGES + ] = False # force using --no-images-upload + info_msg = _( + "DokuWiki media files will be *downloaded* to {temp_dir} - to finish the import you have to upload them *manually* to {media_repo}" + ) except KeyError: media_repo = DEFAULT_MEDIA_REPO if opt_upload_images: - info_msg = _("DokuWiki media files will be *uploaded* to the XMPP server. Hyperlinks to these media may not been updated though.") + info_msg = _( + "DokuWiki media files will be *uploaded* to the XMPP server. Hyperlinks to these media may not been updated though." + ) else: - info_msg = _("DokuWiki media files will *stay* on {location} - some of them may be protected by DokuWiki ACL and will not be accessible.") + info_msg = _( + "DokuWiki media files will *stay* on {location} - some of them may be protected by DokuWiki ACL and will not be accessible." + ) try: namespace = options["namespace"] @@ -386,7 +404,11 @@ limit = DEFAULT_LIMIT dk_importer = Importer(location, user, passwd, media_repo, limit) - info_msg = info_msg.format(temp_dir=dk_importer.temp_dir, media_repo=media_repo, location=location) - self.host.actionNew({'xmlui': xml_tools.note(info_msg).toXml()}, profile=client.profile) + info_msg = info_msg.format( + temp_dir=dk_importer.temp_dir, media_repo=media_repo, location=location + ) + self.host.actionNew( + {"xmlui": xml_tools.note(info_msg).toXml()}, profile=client.profile + ) d = threads.deferToThread(dk_importer.process, client, namespace) return d
--- a/sat/plugins/plugin_blog_import_dotclear.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_blog_import_dotclear.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from sat.tools.common import data_format @@ -38,12 +39,13 @@ C.PI_DEPENDENCIES: ["BLOG_IMPORT"], C.PI_MAIN: "DotclearImport", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Blog importer for Dotclear blog engine.""") + C.PI_DESCRIPTION: _("""Blog importer for Dotclear blog engine."""), } SHORT_DESC = D_(u"import posts from Dotclear blog engine") -LONG_DESC = D_(u"""This importer handle Dotclear blog engine. +LONG_DESC = D_( + u"""This importer handle Dotclear blog engine. To use it, you'll need to export your blog to a flat file. You must go in your admin interface and select Plugins/Maintenance then Backup. @@ -51,15 +53,20 @@ Depending on your configuration, your may need to use Import/Export plugin and export as a flat file. location: you must use the absolute path to your backup for the location parameter -""") +""" +) POST_ID_PREFIX = u"sat_dc_" -KNOWN_DATA_TYPES = ('link', 'setting', 'post', 'meta', 'media', 'post_media', 'comment', 'captcha') -ESCAPE_MAP = { - 'r': u'\r', - 'n': u'\n', - '"': u'"', - '\\': u'\\', - } +KNOWN_DATA_TYPES = ( + "link", + "setting", + "post", + "meta", + "media", + "post_media", + "comment", + "captcha", +) +ESCAPE_MAP = {"r": u"\r", "n": u"\n", '"': u'"', "\\": u"\\"} class DotclearParser(object): @@ -76,7 +83,13 @@ @param post(dict): parsed post data @return (unicode): post unique item id """ - return u"{}_{}_{}_{}:{}".format(POST_ID_PREFIX, post['blog_id'], post['user_id'], post['post_id'], post['post_url']) + return u"{}_{}_{}_{}:{}".format( + POST_ID_PREFIX, + post["blog_id"], + post["user_id"], + post["post_id"], + post["post_url"], + ) def getCommentId(self, comment): """Return a unique and constant comment id @@ -84,9 +97,9 @@ @param comment(dict): parsed comment @return (unicode): comment unique comment id """ - post_id = comment['post_id'] - parent_item_id = self.posts_data[post_id]['blog']['id'] - return u"{}_comment_{}".format(parent_item_id, comment['comment_id']) + post_id = comment["post_id"] + parent_item_id = self.posts_data[post_id]["blog"]["id"] + return u"{}_comment_{}".format(parent_item_id, comment["comment_id"]) def getTime(self, data, key): """Parse time as given by dotclear, with timezone handling @@ -112,18 +125,18 @@ if char == '"': # we have reached the end of this field, # we try to parse a new one - yield u''.join(buf) + yield u"".join(buf) buf = [] idx += 1 try: separator = fields_data[idx] except IndexError: return - if separator != u',': + if separator != u",": raise exceptions.ParsingError("Field separator was expeceted") idx += 1 - break # we have a new field - elif char == u'\\': + break # we have a new field + elif char == u"\\": idx += 1 try: char = ESCAPE_MAP[fields_data[idx]] @@ -139,55 +152,65 @@ def postHandler(self, headers, data, index): post = self.parseFields(headers, data) - log.debug(u'({}) post found: {}'.format(index, post['post_title'])) - mb_data = {'id': self.getPostId(post), - 'published': self.getTime(post, 'post_creadt'), - 'updated': self.getTime(post, 'post_upddt'), - 'author': post['user_id'], # there use info are not in the archive - # TODO: option to specify user info - 'content_xhtml': u"{}{}".format(post['post_content_xhtml'], post['post_excerpt_xhtml']), - 'title': post['post_title'], - 'allow_comments': C.boolConst(bool(int(post['post_open_comment']))), - } - self.posts_data[post['post_id']] = {'blog': mb_data, 'comments':[[]], 'url': u'/post/{}'.format(post['post_url'])} + log.debug(u"({}) post found: {}".format(index, post["post_title"])) + mb_data = { + "id": self.getPostId(post), + "published": self.getTime(post, "post_creadt"), + "updated": self.getTime(post, "post_upddt"), + "author": post["user_id"], # there use info are not in the archive + # TODO: option to specify user info + "content_xhtml": u"{}{}".format( + post["post_content_xhtml"], post["post_excerpt_xhtml"] + ), + "title": post["post_title"], + "allow_comments": C.boolConst(bool(int(post["post_open_comment"]))), + } + self.posts_data[post["post_id"]] = { + "blog": mb_data, + "comments": [[]], + "url": u"/post/{}".format(post["post_url"]), + } def metaHandler(self, headers, data, index): meta = self.parseFields(headers, data) - if meta['meta_type'] == 'tag': - tags = self.tags.setdefault(meta['post_id'], set()) - tags.add(meta['meta_id']) + if meta["meta_type"] == "tag": + tags = self.tags.setdefault(meta["post_id"], set()) + tags.add(meta["meta_id"]) def metaFinishedHandler(self): for post_id, tags in self.tags.iteritems(): - data_format.iter2dict('tag', tags, self.posts_data[post_id]['blog']) + data_format.iter2dict("tag", tags, self.posts_data[post_id]["blog"]) del self.tags def commentHandler(self, headers, data, index): comment = self.parseFields(headers, data) - if comment['comment_site']: + if comment["comment_site"]: # we don't use atom:uri because it's used for jid in XMPP content = u'{}\n<hr>\n<a href="{}">author website</a>'.format( - comment['comment_content'], - cgi.escape(comment['comment_site']).replace('"', u'%22')) + comment["comment_content"], + cgi.escape(comment["comment_site"]).replace('"', u"%22"), + ) else: - content = comment['comment_content'] - mb_data = {'id': self.getCommentId(comment), - 'published': self.getTime(comment, 'comment_dt'), - 'updated': self.getTime(comment, 'comment_upddt'), - 'author': comment['comment_author'], - # we don't keep email addresses to avoid the author to be spammed - # (they would be available publicly else) - # 'author_email': comment['comment_email'], - 'content_xhtml': content, - } - self.posts_data[comment['post_id']]['comments'][0].append( - {'blog': mb_data, 'comments': [[]]}) + content = comment["comment_content"] + mb_data = { + "id": self.getCommentId(comment), + "published": self.getTime(comment, "comment_dt"), + "updated": self.getTime(comment, "comment_upddt"), + "author": comment["comment_author"], + # we don't keep email addresses to avoid the author to be spammed + # (they would be available publicly else) + # 'author_email': comment['comment_email'], + "content_xhtml": content, + } + self.posts_data[comment["post_id"]]["comments"][0].append( + {"blog": mb_data, "comments": [[]]} + ) def parse(self, db_path): with open(db_path) as f: - signature = f.readline().decode('utf-8') + signature = f.readline().decode("utf-8") try: - version = signature.split('|')[1] + version = signature.split("|")[1] except IndexError: version = None log.debug(u"Dotclear version: {}".format(version)) @@ -195,20 +218,20 @@ data_headers = None index = None while True: - buf = f.readline().decode('utf-8') + buf = f.readline().decode("utf-8") if not buf: break - if buf.startswith('['): - header = buf.split(' ', 1) + if buf.startswith("["): + header = buf.split(" ", 1) data_type = header[0][1:] if data_type not in KNOWN_DATA_TYPES: log.warning(u"unkown data type: {}".format(data_type)) index = 0 try: - data_headers = header[1].split(',') + data_headers = header[1].split(",") # we need to remove the ']' from the last header last_header = data_headers[-1] - data_headers[-1] = last_header[:last_header.rfind(']')] + data_headers[-1] = last_header[: last_header.rfind("]")] except IndexError: log.warning(u"Can't read data)") else: @@ -217,7 +240,9 @@ buf = buf.strip() if not buf and data_type in KNOWN_DATA_TYPES: try: - finished_handler = getattr(self, '{}FinishedHandler'.format(data_type)) + finished_handler = getattr( + self, "{}FinishedHandler".format(data_type) + ) except AttributeError: pass else: @@ -227,7 +252,7 @@ continue assert data_type try: - fields_handler = getattr(self, '{}Handler'.format(data_type)) + fields_handler = getattr(self, "{}Handler".format(data_type)) except AttributeError: pass else: @@ -237,15 +262,18 @@ class DotclearImport(object): - def __init__(self, host): log.info(_("plugin Dotclear Import initialization")) self.host = host - host.plugins['BLOG_IMPORT'].register('dotclear', self.DcImport, SHORT_DESC, LONG_DESC) + host.plugins["BLOG_IMPORT"].register( + "dotclear", self.DcImport, SHORT_DESC, LONG_DESC + ) def DcImport(self, client, location, options=None): if not os.path.isabs(location): - raise exceptions.DataError(u"An absolute path to backup data need to be given as location") + raise exceptions.DataError( + u"An absolute path to backup data need to be given as location" + ) dc_parser = DotclearParser() d = threads.deferToThread(dc_parser.parse, location) return d
--- a/sat/plugins/plugin_comp_file_sharing.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_comp_file_sharing.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core.constants import Const as C from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) from sat.tools.common import regex from sat.tools.common import uri @@ -41,29 +42,38 @@ C.PI_MODES: [C.PLUG_MODE_COMPONENT], C.PI_TYPE: C.PLUG_TYPE_ENTRY_POINT, C.PI_PROTOCOLS: [], - C.PI_DEPENDENCIES: ["FILE", "XEP-0231", "XEP-0234", "XEP-0260", "XEP-0261", "XEP-0264", "XEP-0329"], + C.PI_DEPENDENCIES: [ + "FILE", + "XEP-0231", + "XEP-0234", + "XEP-0260", + "XEP-0261", + "XEP-0264", + "XEP-0329", + ], C.PI_RECOMMENDATIONS: [], C.PI_MAIN: "FileSharing", C.PI_HANDLER: C.BOOL_TRUE, - C.PI_DESCRIPTION: _(u"""Component hosting and sharing files""") + C.PI_DESCRIPTION: _(u"""Component hosting and sharing files"""), } -HASH_ALGO = u'sha-256' -NS_COMMENTS = 'org.salut-a-toi.comments' -COMMENT_NODE_PREFIX = 'org.salut-a-toi.file_comments/' +HASH_ALGO = u"sha-256" +NS_COMMENTS = "org.salut-a-toi.comments" +COMMENT_NODE_PREFIX = "org.salut-a-toi.file_comments/" class FileSharing(object): - def __init__(self, host): log.info(_(u"File Sharing initialization")) self.host = host - self._f = host.plugins['FILE'] - self._jf = host.plugins['XEP-0234'] - self._h = host.plugins['XEP-0300'] - self._t = host.plugins['XEP-0264'] + self._f = host.plugins["FILE"] + self._jf = host.plugins["XEP-0234"] + self._h = host.plugins["XEP-0300"] + self._t = host.plugins["XEP-0264"] host.trigger.add("FILE_getDestDir", self._getDestDirTrigger) - host.trigger.add("XEP-0234_fileSendingRequest", self._fileSendingRequestTrigger, priority=1000) + host.trigger.add( + "XEP-0234_fileSendingRequest", self._fileSendingRequestTrigger, priority=1000 + ) host.trigger.add("XEP-0234_buildFileElement", self._addFileComments) host.trigger.add("XEP-0234_parseFileElement", self._getFileComments) host.trigger.add("XEP-0329_compGetFilesFromNode", self._addCommentsData) @@ -74,9 +84,10 @@ def profileConnected(self, client): path = client.file_tmp_dir = os.path.join( - self.host.memory.getConfig('', 'local_dir'), + self.host.memory.getConfig("", "local_dir"), C.FILES_TMP_DIR, - regex.pathEscape(client.profile)) + regex.pathEscape(client.profile), + ) if not os.path.exists(path): os.makedirs(path) @@ -87,155 +98,195 @@ on file is received, this method create hash/thumbnails if necessary move the file to the right location, and create metadata entry in database """ - name = file_data[u'name'] + name = file_data[u"name"] extra = {} - if file_data[u'hash_algo'] == HASH_ALGO: + if file_data[u"hash_algo"] == HASH_ALGO: log.debug(_(u"Reusing already generated hash")) - file_hash = file_data[u'hash_hasher'].hexdigest() + file_hash = file_data[u"hash_hasher"].hexdigest() else: hasher = self._h.getHasher(HASH_ALGO) - with open('file_path') as f: + with open("file_path") as f: file_hash = yield self._h.calculateHash(f, hasher) final_path = os.path.join(self.files_path, file_hash) if os.path.isfile(final_path): - log.debug(u"file [{file_hash}] already exists, we can remove temporary one".format(file_hash = file_hash)) + log.debug( + u"file [{file_hash}] already exists, we can remove temporary one".format( + file_hash=file_hash + ) + ) os.unlink(file_path) else: os.rename(file_path, final_path) - log.debug(u"file [{file_hash}] moved to {files_path}".format(file_hash=file_hash, files_path=self.files_path)) + log.debug( + u"file [{file_hash}] moved to {files_path}".format( + file_hash=file_hash, files_path=self.files_path + ) + ) - mime_type = file_data.get(u'mime_type') - if not mime_type or mime_type == u'application/octet-stream': + mime_type = file_data.get(u"mime_type") + if not mime_type or mime_type == u"application/octet-stream": mime_type = mimetypes.guess_type(name)[0] - if mime_type is not None and mime_type.startswith(u'image'): + if mime_type is not None and mime_type.startswith(u"image"): thumbnails = extra.setdefault(C.KEY_THUMBNAILS, []) for max_thumb_size in (self._t.SIZE_SMALL, self._t.SIZE_MEDIUM): try: - thumb_size, thumb_id = yield self._t.generateThumbnail(final_path, - max_thumb_size, - # we keep thumbnails for 6 months - 60*60*24*31*6) + thumb_size, thumb_id = yield self._t.generateThumbnail( + final_path, + max_thumb_size, + # we keep thumbnails for 6 months + 60 * 60 * 24 * 31 * 6, + ) except Exception as e: log.warning(_(u"Can't create thumbnail: {reason}").format(reason=e)) break - thumbnails.append({u'id': thumb_id, u'size': thumb_size}) + thumbnails.append({u"id": thumb_id, u"size": thumb_size}) - self.host.memory.setFile(client, - name=name, - version=u'', - file_hash=file_hash, - hash_algo=HASH_ALGO, - size=file_data[u'size'], - path=file_data.get(u'path'), - namespace=file_data.get(u'namespace'), - mime_type=mime_type, - owner=peer_jid, - extra=extra) + self.host.memory.setFile( + client, + name=name, + version=u"", + file_hash=file_hash, + hash_algo=HASH_ALGO, + size=file_data[u"size"], + path=file_data.get(u"path"), + namespace=file_data.get(u"namespace"), + mime_type=mime_type, + owner=peer_jid, + extra=extra, + ) - def _getDestDirTrigger(self, client, peer_jid, transfer_data, file_data, stream_object): + def _getDestDirTrigger( + self, client, peer_jid, transfer_data, file_data, stream_object + ): """This trigger accept file sending request, and store file locally""" if not client.is_component: return True, None assert stream_object - assert 'stream_object' not in transfer_data + assert "stream_object" not in transfer_data assert C.KEY_PROGRESS_ID in file_data - filename = file_data['name'] - assert filename and not '/' in filename - file_tmp_dir = self.host.getLocalPath(client, C.FILES_TMP_DIR, peer_jid.userhost(), component=True, profile=False) - file_tmp_path = file_data['file_path'] = os.path.join(file_tmp_dir, file_data['name']) + filename = file_data["name"] + assert filename and not "/" in filename + file_tmp_dir = self.host.getLocalPath( + client, C.FILES_TMP_DIR, peer_jid.userhost(), component=True, profile=False + ) + file_tmp_path = file_data["file_path"] = os.path.join( + file_tmp_dir, file_data["name"] + ) - transfer_data['finished_d'].addCallback(self._fileTransferedCb, client, peer_jid, file_data, file_tmp_path) + transfer_data["finished_d"].addCallback( + self._fileTransferedCb, client, peer_jid, file_data, file_tmp_path + ) - self._f.openFileWrite(client, file_tmp_path, transfer_data, file_data, stream_object) + self._f.openFileWrite( + client, file_tmp_path, transfer_data, file_data, stream_object + ) return False, defer.succeed(True) @defer.inlineCallbacks - def _retrieveFiles(self, client, session, content_data, content_name, file_data, file_elt): + def _retrieveFiles( + self, client, session, content_data, content_name, file_data, file_elt + ): """This method retrieve a file on request, and send if after checking permissions""" - peer_jid = session[u'peer_jid'] + peer_jid = session[u"peer_jid"] try: - found_files = yield self.host.memory.getFiles(client, - peer_jid=peer_jid, - name=file_data.get(u'name'), - file_hash=file_data.get(u'file_hash'), - hash_algo=file_data.get(u'hash_algo'), - path=file_data.get(u'path'), - namespace=file_data.get(u'namespace')) + found_files = yield self.host.memory.getFiles( + client, + peer_jid=peer_jid, + name=file_data.get(u"name"), + file_hash=file_data.get(u"file_hash"), + hash_algo=file_data.get(u"hash_algo"), + path=file_data.get(u"path"), + namespace=file_data.get(u"namespace"), + ) except exceptions.NotFound: found_files = None except exceptions.PermissionError: - log.warning(_(u"{peer_jid} is trying to access an unauthorized file: {name}").format( - peer_jid=peer_jid, name=file_data.get(u'name'))) + log.warning( + _(u"{peer_jid} is trying to access an unauthorized file: {name}").format( + peer_jid=peer_jid, name=file_data.get(u"name") + ) + ) defer.returnValue(False) if not found_files: - log.warning(_(u"no matching file found ({file_data})").format(file_data=file_data)) + log.warning( + _(u"no matching file found ({file_data})").format(file_data=file_data) + ) defer.returnValue(False) # we only use the first found file found_file = found_files[0] - file_hash = found_file[u'file_hash'] + file_hash = found_file[u"file_hash"] file_path = os.path.join(self.files_path, file_hash) - file_data[u'hash_hasher'] = hasher = self._h.getHasher(found_file[u'hash_algo']) - size = file_data[u'size'] = found_file[u'size'] - file_data[u'file_hash'] = file_hash - file_data[u'hash_algo'] = found_file[u'hash_algo'] + file_data[u"hash_hasher"] = hasher = self._h.getHasher(found_file[u"hash_algo"]) + size = file_data[u"size"] = found_file[u"size"] + file_data[u"file_hash"] = file_hash + file_data[u"hash_algo"] = found_file[u"hash_algo"] # we complete file_elt so peer can have some details on the file - if u'name' not in file_data: - file_elt.addElement(u'name', content=found_file[u'name']) - file_elt.addElement(u'size', content=unicode(size)) - content_data['stream_object'] = stream.FileStreamObject( + if u"name" not in file_data: + file_elt.addElement(u"name", content=found_file[u"name"]) + file_elt.addElement(u"size", content=unicode(size)) + content_data["stream_object"] = stream.FileStreamObject( self.host, client, file_path, uid=self._jf.getProgressId(session, content_name), size=size, data_cb=lambda data: hasher.update(data), - ) + ) defer.returnValue(True) - def _fileSendingRequestTrigger(self, client, session, content_data, content_name, file_data, file_elt): + def _fileSendingRequestTrigger( + self, client, session, content_data, content_name, file_data, file_elt + ): if not client.is_component: return True, None else: - return False, self._retrieveFiles(client, session, content_data, content_name, file_data, file_elt) + return ( + False, + self._retrieveFiles( + client, session, content_data, content_name, file_data, file_elt + ), + ) ## comments triggers ## def _addFileComments(self, file_elt, extra_args): try: - comments_url = extra_args.pop('comments_url') + comments_url = extra_args.pop("comments_url") except KeyError: return - comment_elt = file_elt.addElement((NS_COMMENTS, 'comments'), content=comments_url) + comment_elt = file_elt.addElement((NS_COMMENTS, "comments"), content=comments_url) try: - count = len(extra_args[u'extra'][u'comments']) + count = len(extra_args[u"extra"][u"comments"]) except KeyError: count = 0 - comment_elt['count'] = unicode(count) + comment_elt["count"] = unicode(count) return True def _getFileComments(self, file_elt, file_data): try: - comments_elt = next(file_elt.elements(NS_COMMENTS, 'comments')) + comments_elt = next(file_elt.elements(NS_COMMENTS, "comments")) except StopIteration: return - file_data['comments_url'] = unicode(comments_elt) - file_data['comments_count'] = comments_elt['count'] + file_data["comments_url"] = unicode(comments_elt) + file_data["comments_count"] = comments_elt["count"] return True def _addCommentsData(self, client, iq_elt, owner, node_path, files_data): for file_data in files_data: - file_data['comments_url'] = uri.buildXMPPUri('pubsub', - path=client.jid.full(), - node=COMMENT_NODE_PREFIX + file_data['id']) + file_data["comments_url"] = uri.buildXMPPUri( + "pubsub", + path=client.jid.full(), + node=COMMENT_NODE_PREFIX + file_data["id"], + ) return True @@ -243,19 +294,21 @@ """This class is a minimal Pubsub service handling virtual nodes for comments""" def __init__(self, plugin_parent): - super(Comments_handler, self).__init__() # PubsubVirtualResource()) + super(Comments_handler, self).__init__() # PubsubVirtualResource()) self.host = plugin_parent.host self.plugin_parent = plugin_parent - self.discoIdentity = {'category': 'pubsub', - 'type': 'virtual', # FIXME: non standard, here to avoid this service being considered as main pubsub one - 'name': 'files commenting service'} + self.discoIdentity = { + "category": "pubsub", + "type": "virtual", # FIXME: non standard, here to avoid this service being considered as main pubsub one + "name": "files commenting service", + } def _getFileId(self, nodeIdentifier): if not nodeIdentifier.startswith(COMMENT_NODE_PREFIX): - raise error.StanzaError('item-not-found') - file_id = nodeIdentifier[len(COMMENT_NODE_PREFIX):] + raise error.StanzaError("item-not-found") + file_id = nodeIdentifier[len(COMMENT_NODE_PREFIX) :] if not file_id: - raise error.StanzaError('item-not-found') + raise error.StanzaError("item-not-found") return file_id @defer.inlineCallbacks @@ -266,11 +319,11 @@ except (exceptions.NotFound, exceptions.PermissionError): # we don't differenciate between NotFound and PermissionError # to avoid leaking information on existing files - raise error.StanzaError('item-not-found') + raise error.StanzaError("item-not-found") if not files: - raise error.StanzaError('item-not-found') + raise error.StanzaError("item-not-found") if len(files) > 1: - raise error.InternalError('there should be only one file') + raise error.InternalError("there should be only one file") defer.returnValue(files[0]) def commentsUpdate(self, extra, new_comments, peer_jid): @@ -280,8 +333,8 @@ @param new_comments(list[tuple(unicode, unicode, unicode)]): comments to update or insert @param peer_jid(unicode, None): bare jid of the requestor, or None if request is done by owner """ - current_comments = extra.setdefault('comments', []) - new_comments_by_id = {c[0]:c for c in new_comments} + current_comments = extra.setdefault("comments", []) + new_comments_by_id = {c[0]: c for c in new_comments} updated = [] # we now check every current comment, to see if one id in new ones # exist, in which case we must update @@ -306,7 +359,7 @@ def commentsDelete(self, extra, comments): try: - comments_dict = extra['comments'] + comments_dict = extra["comments"] except KeyError: return for comment in comments: @@ -324,42 +377,43 @@ iq_elt = item_elt while iq_elt.parent != None: iq_elt = iq_elt.parent - return iq_elt['from'] + return iq_elt["from"] @defer.inlineCallbacks def publish(self, requestor, service, nodeIdentifier, items): - # we retrieve file a first time to check authorisations + # we retrieve file a first time to check authorisations file_data = yield self.getFileData(requestor, nodeIdentifier) - file_id = file_data['id'] - comments = [(item['id'], self._getFrom(item), item.toXml()) for item in items] - if requestor.userhostJID() == file_data['owner']: + file_id = file_data["id"] + comments = [(item["id"], self._getFrom(item), item.toXml()) for item in items] + if requestor.userhostJID() == file_data["owner"]: peer_jid = None else: peer_jid = requestor.userhost() update_cb = partial(self.commentsUpdate, new_comments=comments, peer_jid=peer_jid) try: - yield self.host.memory.fileUpdate(file_id, 'extra', update_cb) + yield self.host.memory.fileUpdate(file_id, "extra", update_cb) except exceptions.PermissionError: - raise error.StanzaError('not-authorized') + raise error.StanzaError("not-authorized") @defer.inlineCallbacks - def items(self, requestor, service, nodeIdentifier, maxItems, - itemIdentifiers): + def items(self, requestor, service, nodeIdentifier, maxItems, itemIdentifiers): file_data = yield self.getFileData(requestor, nodeIdentifier) - comments = file_data['extra'].get('comments', []) + comments = file_data["extra"].get("comments", []) if itemIdentifiers: - defer.returnValue([generic.parseXml(c[2]) for c in comments if c[0] in itemIdentifiers]) + defer.returnValue( + [generic.parseXml(c[2]) for c in comments if c[0] in itemIdentifiers] + ) else: defer.returnValue([generic.parseXml(c[2]) for c in comments]) @defer.inlineCallbacks def retract(self, requestor, service, nodeIdentifier, itemIdentifiers): file_data = yield self.getFileData(requestor, nodeIdentifier) - file_id = file_data['id'] + file_id = file_data["id"] try: - comments = file_data['extra']['comments'] + comments = file_data["extra"]["comments"] except KeyError: - raise error.StanzaError('item-not-found') + raise error.StanzaError("item-not-found") to_remove = [] for comment in comments: @@ -372,11 +426,11 @@ if itemIdentifiers: # not all items have been to_remove, we can't continue - raise error.StanzaError('item-not-found') + raise error.StanzaError("item-not-found") - if requestor.userhostJID() != file_data['owner']: + if requestor.userhostJID() != file_data["owner"]: if not all([c[1] == requestor.userhost() for c in to_remove]): - raise error.StanzaError('not-authorized') + raise error.StanzaError("not-authorized") remove_cb = partial(self.commentsDelete, comments=to_remove) - yield self.host.memory.fileUpdate(file_id, 'extra', remove_cb) + yield self.host.memory.fileUpdate(file_id, "extra", remove_cb)
--- a/sat/plugins/plugin_exp_command_export.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_exp_command_export.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.protocols.jabber import jid from twisted.internet import reactor, protocol @@ -35,9 +36,10 @@ C.PI_DEPENDENCIES: [], C.PI_MAIN: "CommandExport", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Implementation of command export""") + C.PI_DESCRIPTION: _("""Implementation of command export"""), } + class ExportCommandProtocol(protocol.ProcessProtocol): """ Try to register an account with prosody """ @@ -49,26 +51,26 @@ def _clean(self, data): if not data: - log.error ("data should not be empty !") + log.error("data should not be empty !") return u"" - decoded = data.decode('utf-8', 'ignore')[:-1 if data[-1] == '\n' else None] + decoded = data.decode("utf-8", "ignore")[: -1 if data[-1] == "\n" else None] return clean_ustr(decoded) def connectionMade(self): log.info("connectionMade :)") def outReceived(self, data): - self.client.sendMessage(self.target, {'': self._clean(data)}, no_trigger=True) + self.client.sendMessage(self.target, {"": self._clean(data)}, no_trigger=True) def errReceived(self, data): - self.client.sendMessage(self.target, {'': self._clean(data)}, no_trigger=True) + self.client.sendMessage(self.target, {"": self._clean(data)}, no_trigger=True) def processEnded(self, reason): - log.info (u"process finished: %d" % (reason.value.exitCode,)) + log.info(u"process finished: %d" % (reason.value.exitCode,)) self.parent.removeProcess(self.target, self) def write(self, message): - self.transport.write(message.encode('utf-8')) + self.transport.write(message.encode("utf-8")) def boolOption(self, key): """ Get boolean value from options @@ -81,6 +83,7 @@ class CommandExport(object): """Command export plugin: export a command to an entity""" + # XXX: This plugin can be potentially dangerous if we don't trust entities linked # this is specially true if we have other triggers. # FIXME: spawned should be a client attribute, not a class one @@ -88,9 +91,15 @@ def __init__(self, host): log.info(_("Plugin command export initialization")) self.host = host - self.spawned = {} # key = entity + self.spawned = {} # key = entity host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=10000) - host.bridge.addMethod("exportCommand", ".plugin", in_sign='sasasa{ss}s', out_sign='', method=self._exportCommand) + host.bridge.addMethod( + "exportCommand", + ".plugin", + in_sign="sasasa{ss}s", + out_sign="", + method=self._exportCommand, + ) def removeProcess(self, entity, process): """ Called when the process is finished @@ -100,7 +109,7 @@ processes_set = self.spawned[(entity, process.client.profile)] processes_set.discard(process) if not processes_set: - del(self.spawned[(entity, process.client.profile)]) + del (self.spawned[(entity, process.client.profile)]) except ValueError: pass @@ -111,12 +120,12 @@ if spawned_key in self.spawned: try: - body = message_elt.elements(C.NS_CLIENT, 'body').next() + body = message_elt.elements(C.NS_CLIENT, "body").next() except StopIteration: # do not block message without body (chat state notification...) return True - mess_data = unicode(body) + '\n' + mess_data = unicode(body) + "\n" processes_set = self.spawned[spawned_key] _continue = False exclusive = False @@ -152,5 +161,7 @@ log.info(u"invalid target ignored: %s" % (target,)) continue process_prot = ExportCommandProtocol(self, client, _jid, options) - self.spawned.setdefault((_jid, client.profile),set()).add(process_prot) - reactor.spawnProcess(process_prot, command, args, usePTY = process_prot.boolOption('pty')) + self.spawned.setdefault((_jid, client.profile), set()).add(process_prot) + reactor.spawnProcess( + process_prot, command, args, usePTY=process_prot.boolOption("pty") + )
--- a/sat/plugins/plugin_exp_events.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_exp_events.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core import exceptions from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.tools import utils from sat.tools.common import uri as xmpp_uri @@ -45,14 +46,15 @@ C.PI_RECOMMENDATIONS: ["INVITATIONS", "XEP-0277"], C.PI_MAIN: "Events", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Experimental implementation of XMPP events management""") + C.PI_DESCRIPTION: _("""Experimental implementation of XMPP events management"""), } -NS_EVENT = 'org.salut-a-toi.event:0' -NS_EVENT_LIST = NS_EVENT + '#list' -NS_EVENT_INVIT = NS_EVENT + '#invitation' +NS_EVENT = "org.salut-a-toi.event:0" +NS_EVENT_LIST = NS_EVENT + "#list" +NS_EVENT_INVIT = NS_EVENT + "#invitation" INVITATION = '/message[@type="chat"]/invitation[@xmlns="{ns_invit}"]'.format( - ns_invit=NS_EVENT_INVIT) + ns_invit=NS_EVENT_INVIT +) class Events(object): @@ -64,40 +66,78 @@ self._p = self.host.plugins["XEP-0060"] self._i = self.host.plugins.get("INVITATIONS") self._b = self.host.plugins.get("XEP-0277") - host.bridge.addMethod("eventGet", ".plugin", - in_sign='ssss', out_sign='(ia{ss})', - method=self._eventGet, - async=True) - host.bridge.addMethod("eventCreate", ".plugin", - in_sign='ia{ss}ssss', out_sign='s', - method=self._eventCreate, - async=True) - host.bridge.addMethod("eventModify", ".plugin", - in_sign='sssia{ss}s', out_sign='', - method=self._eventModify, - async=True) - host.bridge.addMethod("eventsList", ".plugin", - in_sign='sss', out_sign='aa{ss}', - method=self._eventsList, - async=True) - host.bridge.addMethod("eventInviteeGet", ".plugin", - in_sign='sss', out_sign='a{ss}', - method=self._eventInviteeGet, - async=True) - host.bridge.addMethod("eventInviteeSet", ".plugin", - in_sign='ssa{ss}s', out_sign='', - method=self._eventInviteeSet, - async=True) - host.bridge.addMethod("eventInviteesList", ".plugin", - in_sign='sss', out_sign='a{sa{ss}}', - method=self._eventInviteesList, - async=True), - host.bridge.addMethod("eventInvite", ".plugin", in_sign='sssss', out_sign='', - method=self._invite, - async=True) - host.bridge.addMethod("eventInviteByEmail", ".plugin", in_sign='ssssassssssss', out_sign='', - method=self._inviteByEmail, - async=True) + host.bridge.addMethod( + "eventGet", + ".plugin", + in_sign="ssss", + out_sign="(ia{ss})", + method=self._eventGet, + async=True, + ) + host.bridge.addMethod( + "eventCreate", + ".plugin", + in_sign="ia{ss}ssss", + out_sign="s", + method=self._eventCreate, + async=True, + ) + host.bridge.addMethod( + "eventModify", + ".plugin", + in_sign="sssia{ss}s", + out_sign="", + method=self._eventModify, + async=True, + ) + host.bridge.addMethod( + "eventsList", + ".plugin", + in_sign="sss", + out_sign="aa{ss}", + method=self._eventsList, + async=True, + ) + host.bridge.addMethod( + "eventInviteeGet", + ".plugin", + in_sign="sss", + out_sign="a{ss}", + method=self._eventInviteeGet, + async=True, + ) + host.bridge.addMethod( + "eventInviteeSet", + ".plugin", + in_sign="ssa{ss}s", + out_sign="", + method=self._eventInviteeSet, + async=True, + ) + host.bridge.addMethod( + "eventInviteesList", + ".plugin", + in_sign="sss", + out_sign="a{sa{ss}}", + method=self._eventInviteesList, + async=True, + ), + host.bridge.addMethod( + "eventInvite", + ".plugin", + in_sign="sssss", + out_sign="", + method=self._invite, + async=True, + ) + host.bridge.addMethod( + "eventInviteByEmail", + ".plugin", + in_sign="ssssassssssss", + out_sign="", + method=self._inviteByEmail, + async=True, + ) def getHandler(self, client): return EventsHandler(self) @@ -115,13 +155,13 @@ data = {} - for key in (u'name',): + for key in (u"name",): try: data[key] = event_elt[key] except KeyError: continue - for elt_name in (u'description',): + for elt_name in (u"description",): try: elt = next(event_elt.elements(NS_EVENT, elt_name)) except StopIteration: @@ -129,21 +169,21 @@ else: data[elt_name] = unicode(elt) - for elt_name in (u'image', 'background-image'): + for elt_name in (u"image", "background-image"): try: image_elt = next(event_elt.elements(NS_EVENT, elt_name)) - data[elt_name] = image_elt['src'] + data[elt_name] = image_elt["src"] except StopIteration: continue except KeyError: - log.warning(_(u'no src found for image')) + log.warning(_(u"no src found for image")) - for uri_type in (u'invitees', u'blog'): + for uri_type in (u"invitees", u"blog"): try: elt = next(event_elt.elements(NS_EVENT, uri_type)) - uri = data[uri_type + u'_uri'] = elt['uri'] + uri = data[uri_type + u"_uri"] = elt["uri"] uri_data = xmpp_uri.parseXMPPUri(uri) - if uri_data[u'type'] != u'pubsub': + if uri_data[u"type"] != u"pubsub": raise ValueError except StopIteration: log.warning(_(u"no {uri_type} element found!").format(uri_type=uri_type)) @@ -152,22 +192,26 @@ except ValueError: log.warning(_(u"bad {uri_type} element").format(uri_type=uri_type)) else: - data[uri_type + u'_service'] = uri_data[u'path'] - data[uri_type + u'_node'] = uri_data[u'node'] + data[uri_type + u"_service"] = uri_data[u"path"] + data[uri_type + u"_node"] = uri_data[u"node"] - for meta_elt in event_elt.elements(NS_EVENT, 'meta'): - key = meta_elt[u'name'] + for meta_elt in event_elt.elements(NS_EVENT, "meta"): + key = meta_elt[u"name"] if key in data: - log.warning(u'Ignoring conflicting meta element: {xml}'.format(xml=meta_elt.toXml())) + log.warning( + u"Ignoring conflicting meta element: {xml}".format( + xml=meta_elt.toXml() + ) + ) continue data[key] = unicode(meta_elt) if event_elt.link: link_elt = event_elt.link - data['service'] = link_elt['service'] - data['node'] = link_elt['node'] - data['item'] = link_elt['item'] - if event_elt.getAttribute('creator') == 'true': - data['creator'] = True + data["service"] = link_elt["service"] + data["node"] = link_elt["node"] + data["item"] = link_elt["item"] + if event_elt.getAttribute("creator") == "true": + data["creator"] = True return timestamp, data @defer.inlineCallbacks @@ -184,7 +228,7 @@ id_ = NS_EVENT items, metadata = yield self._p.getItems(client, service, node, item_ids=[id_]) try: - event_elt = next(items[0].elements(NS_EVENT, u'event')) + event_elt = next(items[0].elements(NS_EVENT, u"event")) except IndexError: raise exceptions.NotFound(_(u"No event with this id has been found")) defer.returnValue(event_elt) @@ -204,30 +248,30 @@ try: # TODO: check auto-create, no need to create node first if available options = {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_WHITELIST} - yield self._p.createNode(client, - client.jid.userhostJID(), - nodeIdentifier=NS_EVENT_LIST, - options=options) + yield self._p.createNode( + client, + client.jid.userhostJID(), + nodeIdentifier=NS_EVENT_LIST, + options=options, + ) except error.StanzaError as e: - if e.condition == u'conflict': + if e.condition == u"conflict": log.debug(_(u"requested node already exists")) - link_elt = event_elt.addElement((NS_EVENT_LIST, 'link')) + link_elt = event_elt.addElement((NS_EVENT_LIST, "link")) link_elt["service"] = service.full() link_elt["node"] = node link_elt["item"] = event_id - item_id = xmpp_uri.buildXMPPUri(u'pubsub', - path=service.full(), - node=node, - item=event_id) + item_id = xmpp_uri.buildXMPPUri( + u"pubsub", path=service.full(), node=node, item=event_id + ) if creator: - event_elt['creator'] = 'true' + event_elt["creator"] = "true" item_elt = pubsub.Item(id=item_id, payload=event_elt) - yield self._p.publish(client, - client.jid.userhostJID(), - NS_EVENT_LIST, - items=[item_elt]) + yield self._p.publish( + client, client.jid.userhostJID(), NS_EVENT_LIST, items=[item_elt] + ) - def _eventGet(self, service, node, id_=u'', profile_key=C.PROF_KEY_NONE): + def _eventGet(self, service, node, id_=u"", profile_key=C.PROF_KEY_NONE): service = jid.JID(service) if service else None node = node if node else NS_EVENT client = self.host.getClient(profile_key) @@ -251,11 +295,13 @@ defer.returnValue(self._parseEventElt(event_elt)) - def _eventCreate(self, timestamp, data, service, node, id_=u'', profile_key=C.PROF_KEY_NONE): + def _eventCreate( + self, timestamp, data, service, node, id_=u"", profile_key=C.PROF_KEY_NONE + ): service = jid.JID(service) if service else None node = node or None client = self.host.getClient(profile_key) - data[u'register'] = C.bool(data.get(u'register', C.BOOL_FALSE)) + data[u"register"] = C.bool(data.get(u"register", C.BOOL_FALSE)) return self.eventCreate(client, timestamp, data, service, node, id_ or NS_EVENT) @defer.inlineCallbacks @@ -282,57 +328,66 @@ if not service: service = client.jid.userhostJID() if not node: - node = NS_EVENT + u'__' + shortuuid.uuid() - event_elt = domish.Element((NS_EVENT, 'event')) + node = NS_EVENT + u"__" + shortuuid.uuid() + event_elt = domish.Element((NS_EVENT, "event")) if timestamp is not None and timestamp != -1: formatted_date = utils.xmpp_date(timestamp) - event_elt.addElement((NS_EVENT, 'date'), content=formatted_date) - register = data.pop('register', False) - for key in (u'name',): + event_elt.addElement((NS_EVENT, "date"), content=formatted_date) + register = data.pop("register", False) + for key in (u"name",): if key in data: event_elt[key] = data.pop(key) - for key in (u'description',): + for key in (u"description",): if key in data: event_elt.addElement((NS_EVENT, key), content=data.pop(key)) - for key in (u'image', u'background-image'): + for key in (u"image", u"background-image"): if key in data: elt = event_elt.addElement((NS_EVENT, key)) - elt['src'] = data.pop(key) + elt["src"] = data.pop(key) # we first create the invitees and blog nodes (if not specified in data) - for uri_type in (u'invitees', u'blog'): - key = uri_type + u'_uri' - for to_delete in (u'service', u'node'): - k = uri_type + u'_' + to_delete + for uri_type in (u"invitees", u"blog"): + key = uri_type + u"_uri" + for to_delete in (u"service", u"node"): + k = uri_type + u"_" + to_delete if k in data: del data[k] if key not in data: # FIXME: affiliate invitees uri_node = yield self._p.createNode(client, service) - yield self._p.setConfiguration(client, service, uri_node, {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_WHITELIST}) + yield self._p.setConfiguration( + client, + service, + uri_node, + {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_WHITELIST}, + ) uri_service = service else: uri = data.pop(key) uri_data = xmpp_uri.parseXMPPUri(uri) - if uri_data[u'type'] != u'pubsub': - raise ValueError(_(u'The given URI is not valid: {uri}').format(uri=uri)) - uri_service = jid.JID(uri_data[u'path']) - uri_node = uri_data[u'node'] + if uri_data[u"type"] != u"pubsub": + raise ValueError( + _(u"The given URI is not valid: {uri}").format(uri=uri) + ) + uri_service = jid.JID(uri_data[u"path"]) + uri_node = uri_data[u"node"] elt = event_elt.addElement((NS_EVENT, uri_type)) - elt['uri'] = xmpp_uri.buildXMPPUri('pubsub', path=uri_service.full(), node=uri_node) + elt["uri"] = xmpp_uri.buildXMPPUri( + "pubsub", path=uri_service.full(), node=uri_node + ) # remaining data are put in <meta> elements for key in data.keys(): - elt = event_elt.addElement((NS_EVENT, 'meta'), content = data.pop(key)) - elt['name'] = key + elt = event_elt.addElement((NS_EVENT, "meta"), content=data.pop(key)) + elt["name"] = key item_elt = pubsub.Item(id=event_id, payload=event_elt) try: # TODO: check auto-create, no need to create node first if available node = yield self._p.createNode(client, service, nodeIdentifier=node) except error.StanzaError as e: - if e.condition == u'conflict': + if e.condition == u"conflict": log.debug(_(u"requested node already exists")) yield self._p.publish(client, service, node, items=[item_elt]) @@ -341,14 +396,26 @@ yield self.register(client, service, node, event_id, event_elt, creator=True) defer.returnValue(node) - def _eventModify(self, service, node, id_, timestamp_update, data_update, profile_key=C.PROF_KEY_NONE): + def _eventModify( + self, + service, + node, + id_, + timestamp_update, + data_update, + profile_key=C.PROF_KEY_NONE, + ): service = jid.JID(service) if service else None node = node if node else NS_EVENT client = self.host.getClient(profile_key) - return self.eventModify(client, service, node, id_ or NS_EVENT, timestamp_update or None, data_update) + return self.eventModify( + client, service, node, id_ or NS_EVENT, timestamp_update or None, data_update + ) @defer.inlineCallbacks - def eventModify(self, client, service, node, id_=NS_EVENT, timestamp_update=None, data_update=None): + def eventModify( + self, client, service, node, id_=NS_EVENT, timestamp_update=None, data_update=None + ): """Update an event Similar as create instead that it update existing item instead of @@ -364,8 +431,8 @@ def _eventsListSerialise(self, events): for timestamp, data in events: - data['date'] = unicode(timestamp) - data['creator'] = C.boolConst(data.get('creator', False)) + data["date"] = unicode(timestamp) + data["creator"] = C.boolConst(data.get("creator", False)) return [e[1] for e in events] def _eventsList(self, service, node, profile): @@ -388,10 +455,11 @@ events = [] for item in items[0]: try: - event_elt = next(item.elements(NS_EVENT, u'event')) + event_elt = next(item.elements(NS_EVENT, u"event")) except IndexError: - log.error(_(u"No event found in item {item_id}").format( - item_id = item['id'])) + log.error( + _(u"No event found in item {item_id}").format(item_id=item["id"]) + ) timestamp, data = self._parseEventElt(event_elt) events.append((timestamp, data)) defer.returnValue(events) @@ -411,21 +479,23 @@ @return (dict): a dict with current attendance status, an empty dict is returned if nothing has been answered yed """ - items, metadata = yield self._p.getItems(client, service, node, item_ids=[client.jid.userhost()]) + items, metadata = yield self._p.getItems( + client, service, node, item_ids=[client.jid.userhost()] + ) try: - event_elt = next(items[0].elements(NS_EVENT, u'invitee')) + event_elt = next(items[0].elements(NS_EVENT, u"invitee")) except IndexError: # no item found, event data are not set yet defer.returnValue({}) data = {} - for key in (u'attend', u'guests'): + for key in (u"attend", u"guests"): try: data[key] = event_elt[key] except KeyError: continue defer.returnValue(data) - def _eventInviteeSet(self, service, node, event_data, profile_key): + def _eventInviteeSet(self, service, node, event_data, profile_key): service = jid.JID(service) if service else None node = node if node else NS_EVENT client = self.host.getClient(profile_key) @@ -441,8 +511,8 @@ attend: one of "yes", "no", "maybe" guests: an int """ - event_elt = domish.Element((NS_EVENT, 'invitee')) - for key in (u'attend', u'guests'): + event_elt = domish.Element((NS_EVENT, "invitee")) + for key in (u"attend", u"guests"): try: event_elt[key] = data.pop(key) except KeyError: @@ -469,22 +539,24 @@ invitees = {} for item in items: try: - event_elt = next(item.elements(NS_EVENT, u'invitee')) + event_elt = next(item.elements(NS_EVENT, u"invitee")) except StopIteration: # no item found, event data are not set yet - log.warning(_(u"no data found for {item_id} (service: {service}, node: {node})".format( - item_id=item['id'], - service=service, - node=node - ))) + log.warning( + _( + u"no data found for {item_id} (service: {service}, node: {node})".format( + item_id=item["id"], service=service, node=node + ) + ) + ) else: data = {} - for key in (u'attend', u'guests'): + for key in (u"attend", u"guests"): try: data[key] = event_elt[key] except KeyError: continue - invitees[item['id']] = data + invitees[item["id"]] = data defer.returnValue(invitees) def sendMessageInvitation(self, client, invitee_jid, service, node, item_id): @@ -496,20 +568,20 @@ @param item_id(unicode): id of the event """ mess_data = { - 'from': client.jid, - 'to': invitee_jid, - 'uid': '', - 'message': {}, - 'type': C.MESS_TYPE_CHAT, - 'subject': {}, - 'extra': {}, - } + "from": client.jid, + "to": invitee_jid, + "uid": "", + "message": {}, + "type": C.MESS_TYPE_CHAT, + "subject": {}, + "extra": {}, + } client.generateMessageXML(mess_data) - event_elt = mess_data['xml'].addElement('invitation', NS_EVENT_INVIT) - event_elt['service'] = service.full() - event_elt['node'] = node - event_elt['item'] = item_id - client.send(mess_data['xml']) + event_elt = mess_data["xml"].addElement("invitation", NS_EVENT_INVIT) + event_elt["service"] = service.full() + event_elt["node"] = node + event_elt["item"] = item_id + client.send(mess_data["xml"]) def _invite(self, invitee_jid, service, node, item_id, profile): client = self.host.getClient(profile) @@ -531,51 +603,83 @@ @param item_id(unicode): event id """ if self._b is None: - raise exceptions.FeatureNotFound(_(u'"XEP-0277" (blog) plugin is needed for this feature')) + raise exceptions.FeatureNotFound( + _(u'"XEP-0277" (blog) plugin is needed for this feature') + ) if item_id is None: item_id = NS_EVENT # first we authorize our invitee to see the nodes of interest - yield self._p.setNodeAffiliations(client, service, node, {invitee_jid: u'member'}) - log.debug(_(u'affiliation set on event node')) + yield self._p.setNodeAffiliations(client, service, node, {invitee_jid: u"member"}) + log.debug(_(u"affiliation set on event node")) dummy, event_data = yield self.eventGet(client, service, node, item_id) - log.debug(_(u'got event data')) - invitees_service = jid.JID(event_data['invitees_service']) - invitees_node = event_data['invitees_node'] - blog_service = jid.JID(event_data['blog_service']) - blog_node = event_data['blog_node'] - yield self._p.setNodeAffiliations(client, invitees_service, invitees_node, {invitee_jid: u'publisher'}) - log.debug(_(u'affiliation set on invitee node')) - yield self._p.setNodeAffiliations(client, blog_service, blog_node, {invitee_jid: u'member'}) + log.debug(_(u"got event data")) + invitees_service = jid.JID(event_data["invitees_service"]) + invitees_node = event_data["invitees_node"] + blog_service = jid.JID(event_data["blog_service"]) + blog_node = event_data["blog_node"] + yield self._p.setNodeAffiliations( + client, invitees_service, invitees_node, {invitee_jid: u"publisher"} + ) + log.debug(_(u"affiliation set on invitee node")) + yield self._p.setNodeAffiliations( + client, blog_service, blog_node, {invitee_jid: u"member"} + ) blog_items, dummy = yield self._b.mbGet(client, blog_service, blog_node, None) for item in blog_items: try: - comments_service = jid.JID(item['comments_service']) - comments_node = item['comments_node'] + comments_service = jid.JID(item["comments_service"]) + comments_node = item["comments_node"] except KeyError: - log.debug(u"no comment service set for item {item_id}".format(item_id=item['id'])) + log.debug( + u"no comment service set for item {item_id}".format( + item_id=item["id"] + ) + ) else: - yield self._p.setNodeAffiliations(client, comments_service, comments_node, {invitee_jid: u'publisher'}) - log.debug(_(u'affiliation set on blog and comments nodes')) + yield self._p.setNodeAffiliations( + client, comments_service, comments_node, {invitee_jid: u"publisher"} + ) + log.debug(_(u"affiliation set on blog and comments nodes")) # now we send the invitation self.sendMessageInvitation(client, invitee_jid, service, node, item_id) - def _inviteByEmail(self, service, node, id_=NS_EVENT, email=u'', emails_extra=None, name=u'', host_name=u'', language=u'', url_template=u'', - message_subject=u'', message_body=u'', profile_key=C.PROF_KEY_NONE): + def _inviteByEmail( + self, + service, + node, + id_=NS_EVENT, + email=u"", + emails_extra=None, + name=u"", + host_name=u"", + language=u"", + url_template=u"", + message_subject=u"", + message_body=u"", + profile_key=C.PROF_KEY_NONE, + ): client = self.host.getClient(profile_key) - kwargs = {u'profile': client.profile, - u'emails_extra': [unicode(e) for e in emails_extra] - } - for key in ("email", "name", "host_name", "language", "url_template", "message_subject", "message_body"): + kwargs = { + u"profile": client.profile, + u"emails_extra": [unicode(e) for e in emails_extra], + } + for key in ( + "email", + "name", + "host_name", + "language", + "url_template", + "message_subject", + "message_body", + ): value = locals()[key] kwargs[key] = unicode(value) - return self.inviteByEmail(client, - jid.JID(service) if service else None, - node, - id_ or NS_EVENT, - **kwargs) + return self.inviteByEmail( + client, jid.JID(service) if service else None, node, id_ or NS_EVENT, **kwargs + ) @defer.inlineCallbacks def inviteByEmail(self, client, service, node, id_=NS_EVENT, **kwargs): @@ -586,18 +690,21 @@ @param id_(unicode): id_ with even data """ if self._i is None: - raise exceptions.FeatureNotFound(_(u'"Invitations" plugin is needed for this feature')) + raise exceptions.FeatureNotFound( + _(u'"Invitations" plugin is needed for this feature') + ) if self._b is None: - raise exceptions.FeatureNotFound(_(u'"XEP-0277" (blog) plugin is needed for this feature')) + raise exceptions.FeatureNotFound( + _(u'"XEP-0277" (blog) plugin is needed for this feature') + ) service = service or client.jid.userhostJID() - event_uri = xmpp_uri.buildXMPPUri('pubsub', - path=service.full(), - node=node, - item=id_) - kwargs['extra'] = {u'event_uri': event_uri} + event_uri = xmpp_uri.buildXMPPUri( + "pubsub", path=service.full(), node=node, item=id_ + ) + kwargs["extra"] = {u"event_uri": event_uri} invitation_data = yield self._i.create(**kwargs) - invitee_jid = invitation_data[u'jid'] - log.debug(_(u'invitation created')) + invitee_jid = invitation_data[u"jid"] + log.debug(_(u"invitation created")) # now that we have a jid, we can send normal invitation yield self.invite(client, invitee_jid, service, node, id_) @@ -605,9 +712,9 @@ def onInvitation(self, message_elt, client): invitation_elt = message_elt.invitation try: - service = jid.JID(invitation_elt['service']) - node = invitation_elt['node'] - event_id = invitation_elt['item'] + service = jid.JID(invitation_elt["service"]) + node = invitation_elt["node"] + event_id = invitation_elt["item"] except (RuntimeError, KeyError): log.warning(_(u"Bad invitation: {xml}").format(xml=message_elt.toXml())) @@ -626,14 +733,16 @@ return self.plugin_parent.host def connectionInitialized(self): - self.xmlstream.addObserver(INVITATION, - self.plugin_parent.onInvitation, - client=self.parent) + self.xmlstream.addObserver( + INVITATION, self.plugin_parent.onInvitation, client=self.parent + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): - return [disco.DiscoFeature(NS_EVENT), - disco.DiscoFeature(NS_EVENT_LIST), - disco.DiscoFeature(NS_EVENT_INVIT)] + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): + return [ + disco.DiscoFeature(NS_EVENT), + disco.DiscoFeature(NS_EVENT_LIST), + disco.DiscoFeature(NS_EVENT_INVIT), + ] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_exp_jingle_stream.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_exp_jingle_stream.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core.constants import Const as C from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) from sat.tools import xml_tools from sat.tools import stream @@ -35,8 +36,8 @@ from zope import interface import errno -NS_STREAM = 'http://salut-a-toi.org/protocol/stream' -SECURITY_LIMIT=30 +NS_STREAM = "http://salut-a-toi.org/protocol/stream" +SECURITY_LIMIT = 30 START_PORT = 8888 PLUGIN_INFO = { @@ -47,7 +48,7 @@ C.PI_DEPENDENCIES: ["XEP-0166"], C.PI_MAIN: "JingleStream", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Jingle Stream plugin""") + C.PI_DESCRIPTION: _("""Jingle Stream plugin"""), } CONFIRM = D_(u"{peer} wants to send you a stream, do you accept ?") @@ -55,7 +56,6 @@ class StreamProtocol(protocol.Protocol): - def __init__(self): self.pause = False @@ -118,7 +118,9 @@ def startStream(self, consumer): if self.consumer is not None: - raise exceptions.InternalError(_(u"stream can't be used with multiple consumers")) + raise exceptions.InternalError( + _(u"stream can't be used with multiple consumers") + ) assert self.deferred is None self.consumer = consumer consumer.registerProducer(self, True) @@ -176,9 +178,17 @@ def __init__(self, host): log.info(_("Plugin Stream initialization")) self.host = host - self._j = host.plugins["XEP-0166"] # shortcut to access jingle + self._j = host.plugins["XEP-0166"] # shortcut to access jingle self._j.registerApplication(NS_STREAM, self) - host.bridge.addMethod("streamOut", ".plugin", in_sign='ss', out_sign='s', method=self._streamOut, async=True) + host.bridge.addMethod( + "streamOut", + ".plugin", + in_sign="ss", + out_sign="s", + method=self._streamOut, + async=True, + ) + # jingle callbacks def _streamOut(self, to_jid_s, profile_key): @@ -206,64 +216,75 @@ else: factory.port_listening = port_listening break - self._j.initiate(client, - to_jid, - [{'app_ns': NS_STREAM, - 'senders': self._j.ROLE_INITIATOR, - 'app_kwargs': {'stream_object': factory}, - }]) + self._j.initiate( + client, + to_jid, + [ + { + "app_ns": NS_STREAM, + "senders": self._j.ROLE_INITIATOR, + "app_kwargs": {"stream_object": factory}, + } + ], + ) defer.returnValue(unicode(port)) def jingleSessionInit(self, client, session, content_name, stream_object): - content_data = session['contents'][content_name] - application_data = content_data['application_data'] - assert 'stream_object' not in application_data - application_data['stream_object'] = stream_object - desc_elt = domish.Element((NS_STREAM, 'description')) + content_data = session["contents"][content_name] + application_data = content_data["application_data"] + assert "stream_object" not in application_data + application_data["stream_object"] = stream_object + desc_elt = domish.Element((NS_STREAM, "description")) return desc_elt @defer.inlineCallbacks def jingleRequestConfirmation(self, client, action, session, content_name, desc_elt): """This method request confirmation for a jingle session""" - content_data = session['contents'][content_name] - if content_data['senders'] not in (self._j.ROLE_INITIATOR, self._j.ROLE_RESPONDER): + content_data = session["contents"][content_name] + if content_data["senders"] not in ( + self._j.ROLE_INITIATOR, + self._j.ROLE_RESPONDER, + ): log.warning(u"Bad sender, assuming initiator") - content_data['senders'] = self._j.ROLE_INITIATOR + content_data["senders"] = self._j.ROLE_INITIATOR - confirm_data = yield xml_tools.deferDialog(self.host, - _(CONFIRM).format(peer=session['peer_jid'].full()), + confirm_data = yield xml_tools.deferDialog( + self.host, + _(CONFIRM).format(peer=session["peer_jid"].full()), _(CONFIRM_TITLE), type_=C.XMLUI_DIALOG_CONFIRM, - action_extra={'meta_from_jid': session['peer_jid'].full(), - 'meta_type': "STREAM", - }, + action_extra={ + "meta_from_jid": session["peer_jid"].full(), + "meta_type": "STREAM", + }, security_limit=SECURITY_LIMIT, - profile=client.profile) + profile=client.profile, + ) - if not C.bool(confirm_data['answer']): + if not C.bool(confirm_data["answer"]): defer.returnValue(False) try: - port = int(confirm_data['port']) + port = int(confirm_data["port"]) except (ValueError, KeyError): - raise exceptions.DataError(_(u'given port is invalid')) - endpoint = endpoints.TCP4ClientEndpoint(reactor, 'localhost', port) + raise exceptions.DataError(_(u"given port is invalid")) + endpoint = endpoints.TCP4ClientEndpoint(reactor, "localhost", port) factory = StreamFactory() yield endpoint.connect(factory) - content_data['stream_object'] = factory - finished_d = content_data['finished_d'] = defer.Deferred() + content_data["stream_object"] = factory + finished_d = content_data["finished_d"] = defer.Deferred() args = [client, session, content_name, content_data] finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args) defer.returnValue(True) def jingleHandler(self, client, action, session, content_name, desc_elt): - content_data = session['contents'][content_name] - application_data = content_data['application_data'] + content_data = session["contents"][content_name] + application_data = content_data["application_data"] if action in (self._j.A_ACCEPTED_ACK, self._j.A_SESSION_INITIATE): pass elif action == self._j.A_SESSION_ACCEPT: - assert not 'stream_object' in content_data - content_data['stream_object'] = application_data['stream_object'] - finished_d = content_data['finished_d'] = defer.Deferred() + assert not "stream_object" in content_data + content_data["stream_object"] = application_data["stream_object"] + finished_d = content_data["finished_d"] = defer.Deferred() args = [client, session, content_name, content_data] finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args) else: @@ -273,9 +294,11 @@ def _finishedCb(self, dummy, client, session, content_name, content_data): log.info(u"Pipe transfer completed") self._j.contentTerminate(client, session, content_name) - content_data['stream_object'].stopStream() + content_data["stream_object"].stopStream() def _finishedEb(self, failure, client, session, content_name, content_data): log.warning(u"Error while streaming pipe: {}".format(failure)) - self._j.contentTerminate(client, session, content_name, reason=self._j.REASON_FAILED_TRANSPORT) - content_data['stream_object'].stopStream() + self._j.contentTerminate( + client, session, content_name, reason=self._j.REASON_FAILED_TRANSPORT + ) + content_data["stream_object"].stopStream()
--- a/sat/plugins/plugin_exp_lang_detect.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_exp_lang_detect.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,13 +20,16 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions try: from langid.langid import LanguageIdentifier, model except ImportError: - raise exceptions.MissingModule(u'Missing module langid, please download/install it with "pip install langid")') + raise exceptions.MissingModule( + u'Missing module langid, please download/install it with "pip install langid")' + ) identifier = LanguageIdentifier.from_modelstring(model, norm_probs=False) @@ -39,7 +42,7 @@ C.PI_DEPENDENCIES: [], C.PI_MAIN: "LangDetect", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Detect and set message language when unknown""") + C.PI_DESCRIPTION: _("""Detect and set message language when unknown"""), } CATEGORY = D_(u"Misc") @@ -53,14 +56,12 @@ </category> </individual> </params> - """.format(category_name=CATEGORY, - name=NAME, - label=_(LABEL), - ) + """.format( + category_name=CATEGORY, name=NAME, label=_(LABEL) +) class LangDetect(object): - def __init__(self, host): log.info(_(u"Language detection plugin initialization")) self.host = host @@ -69,8 +70,8 @@ host.trigger.add("sendMessage", self.MessageSendTrigger) def addLanguage(self, mess_data): - message = mess_data['message'] - if len(message) == 1 and message.keys()[0] == '': + message = mess_data["message"] + if len(message) == 1 and message.keys()[0] == "": msg = message.values()[0] lang = identifier.classify(msg)[0] mess_data["message"] = {lang: msg} @@ -79,13 +80,17 @@ def MessageReceivedTrigger(self, client, message_elt, post_treat): """ Check if source is linked and repeat message, else do nothing """ - lang_detect = self.host.memory.getParamA(NAME, CATEGORY, profile_key=client.profile) + lang_detect = self.host.memory.getParamA( + NAME, CATEGORY, profile_key=client.profile + ) if lang_detect: post_treat.addCallback(self.addLanguage) return True def MessageSendTrigger(self, client, data, pre_xml_treatments, post_xml_treatments): - lang_detect = self.host.memory.getParamA(NAME, CATEGORY, profile_key=client.profile) + lang_detect = self.host.memory.getParamA( + NAME, CATEGORY, profile_key=client.profile + ) if lang_detect: self.addLanguage(data) return True
--- a/sat/plugins/plugin_exp_parrot.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_exp_parrot.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,11 +20,13 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.protocols.jabber import jid from sat.core.exceptions import UnknownEntityError -#from sat.tools import trigger + +# from sat.tools import trigger PLUGIN_INFO = { C.PI_NAME: "Parrot Plugin", @@ -35,12 +37,15 @@ C.PI_RECOMMENDATIONS: [C.TEXT_CMDS], C.PI_MAIN: "Exp_Parrot", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _(u"""Implementation of parrot mode (repeat messages between 2 entities)""") + C.PI_DESCRIPTION: _( + u"""Implementation of parrot mode (repeat messages between 2 entities)""" + ), } class Exp_Parrot(object): """Parrot mode plugin: repeat messages from one entity or MUC room to another one""" + # XXX: This plugin can be potentially dangerous if we don't trust entities linked # this is specially true if we have other triggers. # sendMessageTrigger avoid other triggers execution, it's deactivated to allow @@ -51,13 +56,13 @@ log.info(_("Plugin Parrot initialization")) self.host = host host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100) - #host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100) + # host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100) try: self.host.plugins[C.TEXT_CMDS].registerTextCommands(self) except KeyError: log.info(_(u"Text commands not available")) - #def sendMessageTrigger(self, client, mess_data, treatments): + # def sendMessageTrigger(self, client, mess_data, treatments): # """ Deactivate other triggers if recipient is in parrot links """ # try: # _links = client.parrot_links @@ -84,18 +89,22 @@ return True message = {} - for e in message_elt.elements(C.NS_CLIENT, 'body'): + for e in message_elt.elements(C.NS_CLIENT, "body"): body = unicode(e) - lang = e.getAttribute('lang') or '' + lang = e.getAttribute("lang") or "" try: - entity_type = self.host.memory.getEntityData(from_jid, ['type'], profile)["type"] + entity_type = self.host.memory.getEntityData(from_jid, ["type"], profile)[ + "type" + ] except (UnknownEntityError, KeyError): entity_type = "contact" - if entity_type == 'chatroom': + if entity_type == "chatroom": src_txt = from_jid.resource - if src_txt == self.host.plugins["XEP-0045"].getRoomNick(client, from_jid.userhostJID()): - #we won't repeat our own messages + if src_txt == self.host.plugins["XEP-0045"].getRoomNick( + client, from_jid.userhostJID() + ): + # we won't repeat our own messages return True else: src_txt = from_jid.user @@ -103,7 +112,9 @@ linked = _links[from_jid.userhostJID()] - client.sendMessage(jid.JID(unicode(linked)), message, None, "auto", no_trigger=True) + client.sendMessage( + jid.JID(unicode(linked)), message, None, "auto", no_trigger=True + ) return True @@ -119,7 +130,10 @@ _links = client.parrot_links = {} _links[source_jid.userhostJID()] = dest_jid - log.info(u"Parrot mode: %s will be repeated to %s" % (source_jid.userhost(), unicode(dest_jid))) + log.info( + u"Parrot mode: %s will be repeated to %s" + % (source_jid.userhost(), unicode(dest_jid)) + ) def removeParrot(self, client, source_jid): """Remove parrot link @@ -141,15 +155,21 @@ if not link_left_jid.user or not link_left_jid.host: raise jid.InvalidFormat except (RuntimeError, jid.InvalidFormat, AttributeError): - txt_cmd.feedBack(client, "Can't activate Parrot mode for invalid jid", mess_data) + txt_cmd.feedBack( + client, "Can't activate Parrot mode for invalid jid", mess_data + ) return False - link_right_jid = mess_data['to'] + link_right_jid = mess_data["to"] self.addParrot(client, link_left_jid, link_right_jid) self.addParrot(client, link_right_jid, link_left_jid) - txt_cmd.feedBack(client, "Parrot mode activated for {}".format(unicode(link_left_jid)), mess_data) + txt_cmd.feedBack( + client, + "Parrot mode activated for {}".format(unicode(link_left_jid)), + mess_data, + ) return False @@ -163,14 +183,22 @@ if not link_left_jid.user or not link_left_jid.host: raise jid.InvalidFormat except jid.InvalidFormat: - txt_cmd.feedBack(client, u"Can't deactivate Parrot mode for invalid jid", mess_data) + txt_cmd.feedBack( + client, u"Can't deactivate Parrot mode for invalid jid", mess_data + ) return False - link_right_jid = mess_data['to'] + link_right_jid = mess_data["to"] self.removeParrot(client, link_left_jid) self.removeParrot(client, link_right_jid) - txt_cmd.feedBack(client, u"Parrot mode deactivated for {} and {}".format(unicode(link_left_jid), unicode(link_right_jid)), mess_data) + txt_cmd.feedBack( + client, + u"Parrot mode deactivated for {} and {}".format( + unicode(link_left_jid), unicode(link_right_jid) + ), + mess_data, + ) return False
--- a/sat/plugins/plugin_exp_pubsub_hook.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_exp_pubsub_hook.py Wed Jun 27 20:14:46 2018 +0200 @@ -24,9 +24,10 @@ from sat.memory import persistent from twisted.words.protocols.jabber import jid from twisted.internet import defer + log = getLogger(__name__) -NS_PUBSUB_HOOK = 'PUBSUB_HOOK' +NS_PUBSUB_HOOK = "PUBSUB_HOOK" PLUGIN_INFO = { C.PI_NAME: "PubSub Hook", @@ -36,40 +37,48 @@ C.PI_DEPENDENCIES: ["XEP-0060"], C.PI_MAIN: "PubsubHook", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Experimental plugin to launch on action on Pubsub notifications""") + C.PI_DESCRIPTION: _( + """Experimental plugin to launch on action on Pubsub notifications""" + ), } -# python module -HOOK_TYPE_PYTHON = u'python' +# python module +HOOK_TYPE_PYTHON = u"python" # python file path -HOOK_TYPE_PYTHON_FILE = u'python_file' +HOOK_TYPE_PYTHON_FILE = u"python_file" # python code directly -HOOK_TYPE_PYTHON_CODE = u'python_code' +HOOK_TYPE_PYTHON_CODE = u"python_code" HOOK_TYPES = (HOOK_TYPE_PYTHON, HOOK_TYPE_PYTHON_FILE, HOOK_TYPE_PYTHON_CODE) class PubsubHook(object): - def __init__(self, host): log.info(_(u"PubSub Hook initialization")) self.host = host self.node_hooks = {} # keep track of the number of hooks per node (for all profiles) - host.bridge.addMethod("psHookAdd", ".plugin", - in_sign='ssssbs', out_sign='', - method=self._addHook - ) - host.bridge.addMethod("psHookRemove", ".plugin", - in_sign='sssss', out_sign='i', - method=self._removeHook - ) - host.bridge.addMethod("psHookList", ".plugin", - in_sign='s', out_sign='aa{ss}', - method=self._listHooks - ) + host.bridge.addMethod( + "psHookAdd", ".plugin", in_sign="ssssbs", out_sign="", method=self._addHook + ) + host.bridge.addMethod( + "psHookRemove", + ".plugin", + in_sign="sssss", + out_sign="i", + method=self._removeHook, + ) + host.bridge.addMethod( + "psHookList", + ".plugin", + in_sign="s", + out_sign="aa{ss}", + method=self._listHooks, + ) @defer.inlineCallbacks def profileConnected(self, client): - hooks = client._hooks = persistent.PersistentBinaryDict(NS_PUBSUB_HOOK, client.profile) + hooks = client._hooks = persistent.PersistentBinaryDict( + NS_PUBSUB_HOOK, client.profile + ) client._hooks_temporary = {} yield hooks.load() for node in hooks: @@ -85,10 +94,11 @@ self.node_hooks[node] += 1 else: # first hook on this node - self.host.plugins['XEP-0060'].addManagedNode(node, items_cb=self._itemsReceived) + self.host.plugins["XEP-0060"].addManagedNode( + node, items_cb=self._itemsReceived + ) self.node_hooks[node] = 0 - log.info(_(u"node manager installed on {node}").format( - node = node)) + log.info(_(u"node manager installed on {node}").format(node=node)) def _removeNodeManager(self, client, node): try: @@ -98,34 +108,40 @@ else: if self.node_hooks[node] == 0: del self.node_hooks[node] - self.host.plugins['XEP-0060'].removeManagedNode(node, self._itemsReceived) + self.host.plugins["XEP-0060"].removeManagedNode(node, self._itemsReceived) log.debug(_(u"hook removed")) else: log.debug(_(u"node still needed for an other hook")) def installHook(self, client, service, node, hook_type, hook_arg, persistent): if hook_type not in HOOK_TYPES: - raise exceptions.DataError(_(u'{hook_type} is not handled').format(hook_type=hook_type)) + raise exceptions.DataError( + _(u"{hook_type} is not handled").format(hook_type=hook_type) + ) if hook_type != HOOK_TYPE_PYTHON_FILE: - raise NotImplementedError(_(u'{hook_type} hook type not implemented yet').format(hook_type=hook_type)) + raise NotImplementedError( + _(u"{hook_type} hook type not implemented yet").format( + hook_type=hook_type + ) + ) self._installNodeManager(client, node) - hook_data = {'service': service, - 'type': hook_type, - 'arg': hook_arg - } + hook_data = {"service": service, "type": hook_type, "arg": hook_arg} if persistent: - hooks_list = client._hooks.setdefault(node,[]) + hooks_list = client._hooks.setdefault(node, []) hooks_list.append(hook_data) client._hooks.force(node) else: - hooks_list = client._hooks_temporary.setdefault(node,[]) + hooks_list = client._hooks_temporary.setdefault(node, []) hooks_list.append(hook_data) - log.info(_(u"{persistent} hook installed on {node} for {profile}").format( - persistent = _(u'persistent') if persistent else _(u'temporary'), - node = node, - profile = client.profile)) + log.info( + _(u"{persistent} hook installed on {node} for {profile}").format( + persistent=_(u"persistent") if persistent else _(u"temporary"), + node=node, + profile=client.profile, + ) + ) def _itemsReceived(self, client, itemsEvent): node = itemsEvent.nodeIdentifier @@ -134,24 +150,30 @@ continue hooks_list = hooks[node] for hook_data in hooks_list[:]: - if hook_data['service'] != itemsEvent.sender.userhostJID(): + if hook_data["service"] != itemsEvent.sender.userhostJID(): continue try: - callback = hook_data['callback'] + callback = hook_data["callback"] except KeyError: # first time we get this hook, we create the callback - hook_type = hook_data['type'] + hook_type = hook_data["type"] try: if hook_type == HOOK_TYPE_PYTHON_FILE: hook_globals = {} - execfile(hook_data['arg'], hook_globals) - callback = hook_globals['hook'] + execfile(hook_data["arg"], hook_globals) + callback = hook_globals["hook"] else: - raise NotImplementedError(_(u'{hook_type} hook type not implemented yet').format( - hook_type=hook_type)) + raise NotImplementedError( + _(u"{hook_type} hook type not implemented yet").format( + hook_type=hook_type + ) + ) except Exception as e: - log.warning(_(u"Can't load Pubsub hook at node {node}, it will be removed: {reason}").format( - node=node, reason=e)) + log.warning( + _( + u"Can't load Pubsub hook at node {node}, it will be removed: {reason}" + ).format(node=node, reason=e) + ) hooks_list.remove(hook_data) continue @@ -159,14 +181,23 @@ try: callback(self.host, client, item) except Exception as e: - log.warning(_(u"Error while running Pubsub hook for node {node}: {msg}").format( - node = node, - msg = e)) + log.warning( + _( + u"Error while running Pubsub hook for node {node}: {msg}" + ).format(node=node, msg=e) + ) def _addHook(self, service, node, hook_type, hook_arg, persistent, profile): client = self.host.getClient(profile) service = jid.JID(service) if service else client.jid.userhostJID() - return self.addHook(client, service, unicode(node), unicode(hook_type), unicode(hook_arg), persistent) + return self.addHook( + client, + service, + unicode(node), + unicode(hook_type), + unicode(hook_arg), + persistent, + ) def addHook(self, client, service, node, hook_type, hook_arg, persistent): r"""Add a hook which will be triggered on a pubsub notification @@ -210,14 +241,18 @@ for hooks in (client._hooks, client._hooks_temporary): if node in hooks: for hook_data in hooks[node]: - if (service != hook_data[u'service'] - or hook_type is not None and hook_type != hook_data[u'type'] - or hook_arg is not None and hook_arg != hook_data[u'arg']): + if ( + service != hook_data[u"service"] + or hook_type is not None + and hook_type != hook_data[u"type"] + or hook_arg is not None + and hook_arg != hook_data[u"arg"] + ): continue hooks[node].remove(hook_data) removed += 1 if not hooks[node]: - # no more hooks, we can remove the node + # no more hooks, we can remove the node del hooks[node] self._removeNodeManager(client, node) else: @@ -228,8 +263,8 @@ def _listHooks(self, profile): hooks_list = self.listHooks(self.host.getClient(profile)) for hook in hooks_list: - hook[u'service'] = hook[u'service'].full() - hook[u'persistent'] = C.boolConst(hook[u'persistent']) + hook[u"service"] = hook[u"service"].full() + hook[u"persistent"] = C.boolConst(hook[u"persistent"]) return hooks_list def listHooks(self, client): @@ -239,11 +274,13 @@ persistent = hooks is client._hooks for node, hooks_data in hooks.iteritems(): for hook_data in hooks_data: - hooks_list.append({u'service': hook_data[u'service'], - u'node': node, - u'type': hook_data[u'type'], - u'arg': hook_data[u'arg'], - u'persistent': persistent - }) + hooks_list.append( + { + u"service": hook_data[u"service"], + u"node": node, + u"type": hook_data[u"type"], + u"arg": hook_data[u"arg"], + u"persistent": persistent, + } + ) return hooks_list -
--- a/sat/plugins/plugin_exp_pubsub_schema.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_exp_pubsub_schema.py Wed Jun 27 20:14:46 2018 +0200 @@ -27,6 +27,7 @@ from twisted.words.protocols.jabber.xmlstream import XMPPHandler from twisted.internet import defer from sat.core.log import getLogger + log = getLogger(__name__) from wokkel import disco, iwokkel from wokkel import data_form @@ -36,7 +37,7 @@ import copy import itertools -NS_SCHEMA = 'https://salut-a-toi/protocol/schema:0' +NS_SCHEMA = "https://salut-a-toi/protocol/schema:0" PLUGIN_INFO = { C.PI_NAME: "PubSub Schema", @@ -46,47 +47,63 @@ C.PI_DEPENDENCIES: ["XEP-0060", "IDENTITY"], C.PI_MAIN: "PubsubSchema", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Handle Pubsub data schemas""") + C.PI_DESCRIPTION: _("""Handle Pubsub data schemas"""), } class PubsubSchema(object): - def __init__(self, host): log.info(_(u"PubSub Schema initialization")) self.host = host self._p = self.host.plugins["XEP-0060"] self._i = self.host.plugins["IDENTITY"] - host.bridge.addMethod("psSchemaGet", ".plugin", - in_sign='sss', out_sign='s', - method=self._getSchema, - async=True - ) - host.bridge.addMethod("psSchemaSet", ".plugin", - in_sign='ssss', out_sign='', - method=self._setSchema, - async=True - ) - host.bridge.addMethod("psSchemaUIGet", ".plugin", - in_sign='sss', out_sign='s', - method=utils.partial(self._getUISchema, default_node=None), - async=True - ) - host.bridge.addMethod("psItemsFormGet", ".plugin", - in_sign='ssssiassa{ss}s', out_sign='(asa{ss})', - method=self._getDataFormItems, - async=True) - host.bridge.addMethod("psItemFormSend", ".plugin", - in_sign='ssa{sas}ssa{ss}s', out_sign='s', - method=self._sendDataFormItem, - async=True) + host.bridge.addMethod( + "psSchemaGet", + ".plugin", + in_sign="sss", + out_sign="s", + method=self._getSchema, + async=True, + ) + host.bridge.addMethod( + "psSchemaSet", + ".plugin", + in_sign="ssss", + out_sign="", + method=self._setSchema, + async=True, + ) + host.bridge.addMethod( + "psSchemaUIGet", + ".plugin", + in_sign="sss", + out_sign="s", + method=utils.partial(self._getUISchema, default_node=None), + async=True, + ) + host.bridge.addMethod( + "psItemsFormGet", + ".plugin", + in_sign="ssssiassa{ss}s", + out_sign="(asa{ss})", + method=self._getDataFormItems, + async=True, + ) + host.bridge.addMethod( + "psItemFormSend", + ".plugin", + in_sign="ssa{sas}ssa{ss}s", + out_sign="s", + method=self._sendDataFormItem, + async=True, + ) def getHandler(self, client): return SchemaHandler() def _getSchemaBridgeCb(self, schema_elt): if schema_elt is None: - return u'' + return u"" return schema_elt.toXml() def _getSchema(self, service, nodeIdentifier, profile_key=C.PROF_KEY_NONE): @@ -98,11 +115,11 @@ def _getSchemaCb(self, iq_elt): try: - schema_elt = next(iq_elt.elements(NS_SCHEMA, 'schema')) + schema_elt = next(iq_elt.elements(NS_SCHEMA, "schema")) except StopIteration: - raise exceptions.DataError('missing <schema> element') + raise exceptions.DataError("missing <schema> element") try: - x_elt = next(schema_elt.elements((data_form.NS_X_DATA, 'x'))) + x_elt = next(schema_elt.elements((data_form.NS_X_DATA, "x"))) except StopIteration: # there is not schema on this node return None @@ -117,18 +134,26 @@ @return (domish.Element, None): schema (<x> element) None if not schema has been set on this node """ - iq_elt = client.IQ(u'get') + iq_elt = client.IQ(u"get") if service is not None: - iq_elt['to'] = service.full() - pubsub_elt = iq_elt.addElement((NS_SCHEMA, 'pubsub')) - schema_elt = pubsub_elt.addElement((NS_SCHEMA, 'schema')) - schema_elt['node'] = nodeIdentifier + iq_elt["to"] = service.full() + pubsub_elt = iq_elt.addElement((NS_SCHEMA, "pubsub")) + schema_elt = pubsub_elt.addElement((NS_SCHEMA, "schema")) + schema_elt["node"] = nodeIdentifier d = iq_elt.send() d.addCallback(self._getSchemaCb) return d @defer.inlineCallbacks - def getSchemaForm(self, client, service, nodeIdentifier, schema=None, form_type='form', copy_form=True): + def getSchemaForm( + self, + client, + service, + nodeIdentifier, + schema=None, + form_type="form", + copy_form=True, + ): """get data form from node's schema @param service(None, jid.JID): PubSub service @@ -147,7 +172,11 @@ log.debug(_(u"unspecified schema, we need to request it")) schema = yield self.getSchema(client, service, nodeIdentifier) if schema is None: - raise exceptions.DataError(_(u"no schema specified, and this node has no schema either, we can't construct the data form")) + raise exceptions.DataError( + _( + u"no schema specified, and this node has no schema either, we can't construct the data form" + ) + ) elif isinstance(schema, data_form.Form): if copy_form: schema = copy.deepcopy(schema) @@ -156,17 +185,18 @@ try: form = data_form.Form.fromElement(schema) except data_form.Error as e: - raise exceptions.DataError(_(u"Invalid Schema: {msg}").format( - msg = e)) + raise exceptions.DataError(_(u"Invalid Schema: {msg}").format(msg=e)) form.formType = form_type defer.returnValue(form) def schema2XMLUI(self, schema_elt): form = data_form.Form.fromElement(schema_elt) - xmlui = xml_tools.dataForm2XMLUI(form, '') + xmlui = xml_tools.dataForm2XMLUI(form, "") return xmlui - def _getUISchema(self, service, nodeIdentifier, default_node=None, profile_key=C.PROF_KEY_NONE): + def _getUISchema( + self, service, nodeIdentifier, default_node=None, profile_key=C.PROF_KEY_NONE + ): if not nodeIdentifier: if not default_node: raise ValueError(_(u"nodeIndentifier needs to be set")) @@ -185,7 +215,7 @@ def _setSchema(self, service, nodeIdentifier, schema, profile_key=C.PROF_KEY_NONE): client = self.host.getClient(profile_key) service = None if not service else jid.JID(service) - schema = generic.parseXml(schema.encode('utf-8')) + schema = generic.parseXml(schema.encode("utf-8")) return self.setSchema(client, service, nodeIdentifier, schema) def setSchema(self, client, service, nodeIdentifier, schema): @@ -196,31 +226,67 @@ """ iq_elt = client.IQ() if service is not None: - iq_elt['to'] = service.full() - pubsub_elt = iq_elt.addElement((NS_SCHEMA, 'pubsub')) - schema_elt = pubsub_elt.addElement((NS_SCHEMA, 'schema')) - schema_elt['node'] = nodeIdentifier + iq_elt["to"] = service.full() + pubsub_elt = iq_elt.addElement((NS_SCHEMA, "pubsub")) + schema_elt = pubsub_elt.addElement((NS_SCHEMA, "schema")) + schema_elt["node"] = nodeIdentifier if schema is not None: schema_elt.addChild(schema) return iq_elt.send() - def _getDataFormItems(self, form_ns='', service='', node='', schema='', max_items=10, item_ids=None, sub_id=None, extra_dict=None, profile_key=C.PROF_KEY_NONE): + def _getDataFormItems( + self, + form_ns="", + service="", + node="", + schema="", + max_items=10, + item_ids=None, + sub_id=None, + extra_dict=None, + profile_key=C.PROF_KEY_NONE, + ): client = self.host.getClient(profile_key) service = jid.JID(service) if service else None if not node: - raise exceptions.DataError(_(u'empty node is not allowed')) + raise exceptions.DataError(_(u"empty node is not allowed")) if schema: - schema = generic.parseXml(schema.encode('utf-8')) + schema = generic.parseXml(schema.encode("utf-8")) else: schema = None max_items = None if max_items == C.NO_LIMIT else max_items extra = self._p.parseExtra(extra_dict) - d = self.getDataFormItems(client, service, node, schema, max_items or None, item_ids, sub_id or None, extra.rsm_request, extra.extra, form_ns=form_ns or None) + d = self.getDataFormItems( + client, + service, + node, + schema, + max_items or None, + item_ids, + sub_id or None, + extra.rsm_request, + extra.extra, + form_ns=form_ns or None, + ) d.addCallback(self._p.serItemsData) return d @defer.inlineCallbacks - def getDataFormItems(self, client, service, nodeIdentifier, schema=None, max_items=None, item_ids=None, sub_id=None, rsm_request=None, extra=None, default_node=None, form_ns=None, filters=None): + def getDataFormItems( + self, + client, + service, + nodeIdentifier, + schema=None, + max_items=None, + item_ids=None, + sub_id=None, + rsm_request=None, + extra=None, + default_node=None, + form_ns=None, + filters=None, + ): """Get items known as being data forms, and convert them to XMLUI @param schema(domish.Element, data_form.Form, None): schema of the node if known @@ -237,15 +303,28 @@ """ if not nodeIdentifier: if not default_node: - raise ValueError(_(u"default_node must be set if nodeIdentifier is not set")) + raise ValueError( + _(u"default_node must be set if nodeIdentifier is not set") + ) nodeIdentifier = default_node # we need the initial form to get options of fields when suitable - schema_form = yield self.getSchemaForm(client, service, nodeIdentifier, schema, form_type='result', copy_form=False) - items_data = yield self._p.getItems(client, service, nodeIdentifier, max_items, item_ids, sub_id, rsm_request, extra) + schema_form = yield self.getSchemaForm( + client, service, nodeIdentifier, schema, form_type="result", copy_form=False + ) + items_data = yield self._p.getItems( + client, + service, + nodeIdentifier, + max_items, + item_ids, + sub_id, + rsm_request, + extra, + ) items, metadata = items_data items_xmlui = [] for item_elt in items: - for x_elt in item_elt.elements((data_form.NS_X_DATA, u'x')): + for x_elt in item_elt.elements((data_form.NS_X_DATA, u"x")): form = data_form.Form.fromElement(x_elt) if form_ns and form.formNamespace != form_ns: continue @@ -254,28 +333,59 @@ schema_form, # FIXME: conflicts with schema (i.e. if "id" or "publisher" already exists) # are not checked - prepend = (('label', 'id'),('text', item_elt['id'], u'id'), - ('label', 'publisher'),('text', item_elt.getAttribute('publisher',''), u'publisher')), - filters = filters, - ) + prepend=( + ("label", "id"), + ("text", item_elt["id"], u"id"), + ("label", "publisher"), + ("text", item_elt.getAttribute("publisher", ""), u"publisher"), + ), + filters=filters, + ) items_xmlui.append(xmlui) break defer.returnValue((items_xmlui, metadata)) - - def _sendDataFormItem(self, service, nodeIdentifier, values, schema=None, item_id=None, extra=None, profile_key=C.PROF_KEY_NONE): + def _sendDataFormItem( + self, + service, + nodeIdentifier, + values, + schema=None, + item_id=None, + extra=None, + profile_key=C.PROF_KEY_NONE, + ): client = self.host.getClient(profile_key) service = None if not service else jid.JID(service) if schema: - schema = generic.parseXml(schema.encode('utf-8')) + schema = generic.parseXml(schema.encode("utf-8")) else: schema = None - d = self.sendDataFormItem(client, service, nodeIdentifier, values, schema, item_id or None, extra, deserialise=True) - d.addCallback(lambda ret: ret or u'') + d = self.sendDataFormItem( + client, + service, + nodeIdentifier, + values, + schema, + item_id or None, + extra, + deserialise=True, + ) + d.addCallback(lambda ret: ret or u"") return d @defer.inlineCallbacks - def sendDataFormItem(self, client, service, nodeIdentifier, values, schema=None, item_id=None, extra=None, deserialise=False): + def sendDataFormItem( + self, + client, + service, + nodeIdentifier, + values, + schema=None, + item_id=None, + extra=None, + deserialise=False, + ): """Publish an item as a dataform when we know that there is a schema @param values(dict[key(unicode), [iterable[object], object]]): values set for the form @@ -289,26 +399,34 @@ other parameters as the same as for [self._p.sendItem] @return (unicode): id of the created item """ - form = yield self.getSchemaForm(client, service, nodeIdentifier, schema, form_type='submit') + form = yield self.getSchemaForm( + client, service, nodeIdentifier, schema, form_type="submit" + ) for name, values_list in values.iteritems(): try: field = form.fields[name] except KeyError: - log.warning(_(u"field {name} doesn't exist, ignoring it").format(name=name)) + log.warning( + _(u"field {name} doesn't exist, ignoring it").format(name=name) + ) continue - if isinstance(values_list, basestring) or not isinstance(values_list, Iterable): + if isinstance(values_list, basestring) or not isinstance( + values_list, Iterable + ): values_list = [values_list] if deserialise: - if field.fieldType == 'boolean': + if field.fieldType == "boolean": values_list = [C.bool(v) for v in values_list] - elif field.fieldType == 'text-multi': + elif field.fieldType == "text-multi": # for text-multi, lines must be put on separate values - values_list = list(itertools.chain(*[v.splitlines() for v in values_list])) + values_list = list( + itertools.chain(*[v.splitlines() for v in values_list]) + ) - elif 'jid' in field.fieldType: + elif "jid" in field.fieldType: values_list = [jid.JID(v) for v in values_list] - if 'list' in field.fieldType: + if "list" in field.fieldType: # for lists, we check that given values are allowed in form allowed_values = [o.value for o in field.options] values_list = [v for v in values_list if v in allowed_values] @@ -317,7 +435,9 @@ values_list = field.values field.values = values_list - yield self._p.sendItem(client, service, nodeIdentifier, form.toElement(), item_id, extra) + yield self._p.sendItem( + client, service, nodeIdentifier, form.toElement(), item_id, extra + ) ## filters ## # filters useful for data form to XMLUI conversion # @@ -327,7 +447,7 @@ if not args[0]: # value is not filled: we use user part of publisher (if we have it) try: - publisher = jid.JID(form_xmlui.named_widgets['publisher'].value) + publisher = jid.JID(form_xmlui.named_widgets["publisher"].value) except (KeyError, RuntimeError): pass else: @@ -339,18 +459,20 @@ main use case is using a textbox for labels """ - if widget_type != u'textbox': + if widget_type != u"textbox": return widget_type, args, kwargs - widget_type = u'list' - options = [o for o in args.pop(0).split(u'\n') if o] - kwargs = {'options': options, - 'name': kwargs.get('name'), - 'styles': (u'noselect', u'extensible', u'reducible')} + widget_type = u"list" + options = [o for o in args.pop(0).split(u"\n") if o] + kwargs = { + "options": options, + "name": kwargs.get("name"), + "styles": (u"noselect", u"extensible", u"reducible"), + } return widget_type, args, kwargs def dateFilter(self, form_xmlui, widget_type, args, kwargs): """Convert a string with a date to a unix timestamp""" - if widget_type != u'string' or not args[0]: + if widget_type != u"string" or not args[0]: return widget_type, args, kwargs # we convert XMPP date to timestamp try: @@ -377,7 +499,19 @@ return client, service, node, max_items, extra, sub_id - def _get(self, service='', node='', max_items=10, item_ids=None, sub_id=None, extra=None, default_node=None, form_ns=None, filters=None, profile_key=C.PROF_KEY_NONE): + def _get( + self, + service="", + node="", + max_items=10, + item_ids=None, + sub_id=None, + extra=None, + default_node=None, + form_ns=None, + filters=None, + profile_key=C.PROF_KEY_NONE, + ): """Bridge method to retrieve data from node with schema this method is a helper so dependant plugins can use it directly @@ -392,11 +526,16 @@ extra = {} # XXX: Q&D way to get list for labels when displaying them, but text when we # have to modify them - if C.bool(extra.get('labels_as_list', C.BOOL_FALSE)): + if C.bool(extra.get("labels_as_list", C.BOOL_FALSE)): filters = filters.copy() - filters[u'labels'] = self.textbox2ListFilter - client, service, node, max_items, extra, sub_id = self.prepareBridgeGet(service, node, max_items, sub_id, extra, profile_key) - d = self.getDataFormItems(client, service, node or None, + filters[u"labels"] = self.textbox2ListFilter + client, service, node, max_items, extra, sub_id = self.prepareBridgeGet( + service, node, max_items, sub_id, extra, profile_key + ) + d = self.getDataFormItems( + client, + service, + node or None, max_items=max_items, item_ids=item_ids, sub_id=sub_id, @@ -404,7 +543,8 @@ extra=extra.extra, default_node=default_node, form_ns=form_ns, - filters=filters) + filters=filters, + ) d.addCallback(self._p.serItemsData) return d @@ -416,30 +556,65 @@ client = self.host.getClient(profile_key) service = None if not service else jid.JID(service) if schema: - schema = generic.parseXml(schema.encode('utf-8')) + schema = generic.parseXml(schema.encode("utf-8")) else: schema = None - if extra and u'update' in extra: - extra[u'update'] = C.bool(extra[u'update']) + if extra and u"update" in extra: + extra[u"update"] = C.bool(extra[u"update"]) return client, service, node or None, schema, item_id or None, extra - def _set(self, service, node, values, schema=None, item_id=None, extra=None, default_node=None, form_ns=None, fill_author=True, profile_key=C.PROF_KEY_NONE): + def _set( + self, + service, + node, + values, + schema=None, + item_id=None, + extra=None, + default_node=None, + form_ns=None, + fill_author=True, + profile_key=C.PROF_KEY_NONE, + ): """Bridge method to set item in node with schema this method is a helper so dependant plugins can use it directly when adding *Set methods """ - client, service, node, schema, item_id, extra = self.prepareBridgeSet(service, node, schema, item_id, extra) - d = self.set(client, service, node, values, schema, item_id, extra, - deserialise=True, - form_ns=form_ns, - default_node=default_node, - fill_author=fill_author) - d.addCallback(lambda ret: ret or u'') + client, service, node, schema, item_id, extra = self.prepareBridgeSet( + service, node, schema, item_id, extra + ) + d = self.set( + client, + service, + node, + values, + schema, + item_id, + extra, + deserialise=True, + form_ns=form_ns, + default_node=default_node, + fill_author=fill_author, + ) + d.addCallback(lambda ret: ret or u"") return d @defer.inlineCallbacks - def set(self, client, service, node, values, schema, item_id, extra, deserialise, form_ns, default_node=None, fill_author=True): + def set( + self, + client, + service, + node, + values, + schema, + item_id, + extra, + deserialise, + form_ns, + default_node=None, + fill_author=True, + ): """Set an item in a node with a schema This method can be used directly by *Set methods added by dependant plugin @@ -463,44 +638,56 @@ node = default_node now = utils.xmpp_date() if not item_id: - values['created'] = now - elif extra.get(u'update', False): + values["created"] = now + elif extra.get(u"update", False): if item_id is None: - raise exceptions.DataError(_(u'if extra["update"] is set, item_id must be set too')) + raise exceptions.DataError( + _(u'if extra["update"] is set, item_id must be set too') + ) try: # we get previous item - items_data = yield self._p.getItems(client, service, node, item_ids=[item_id]) + items_data = yield self._p.getItems( + client, service, node, item_ids=[item_id] + ) item_elt = items_data[0][0] except Exception as e: - log.warning(_(u"Can't get previous item, update ignored: {reason}").format( - reason = e)) + log.warning( + _(u"Can't get previous item, update ignored: {reason}").format( + reason=e + ) + ) else: # and parse it form = data_form.findForm(item_elt, form_ns) if form is None: - log.warning(_(u"Can't parse previous item, update ignored: data form not found").format( - reason = e)) + log.warning( + _( + u"Can't parse previous item, update ignored: data form not found" + ).format(reason=e) + ) else: for name, field in form.fields.iteritems(): if name not in values: - values[name] = u'\n'.join(unicode(v) for v in field.values) + values[name] = u"\n".join(unicode(v) for v in field.values) - values['updated'] = now + values["updated"] = now if fill_author: - if not values.get('author'): + if not values.get("author"): identity = yield self._i.getIdentity(client, client.jid) - values['author'] = identity['nick'] - if not values.get('author_jid'): - values['author_jid'] = client.jid.full() - item_id = yield self.sendDataFormItem(client, service, node, values, schema, item_id, extra, deserialise) + values["author"] = identity["nick"] + if not values.get("author_jid"): + values["author_jid"] = client.jid.full() + item_id = yield self.sendDataFormItem( + client, service, node, values, schema, item_id, extra, deserialise + ) defer.returnValue(item_id) class SchemaHandler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, service, nodeIdentifier=''): + def getDiscoInfo(self, requestor, service, nodeIdentifier=""): return [disco.DiscoFeature(NS_SCHEMA)] - def getDiscoItems(self, requestor, service, nodeIdentifier=''): + def getDiscoItems(self, requestor, service, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_import.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_import.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.internet import defer from sat.core import exceptions @@ -37,14 +38,13 @@ C.PI_DEPENDENCIES: [], C.PI_MAIN: "ImportPlugin", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _(u"""Generic import plugin, base for specialized importers""") + C.PI_DESCRIPTION: _(u"""Generic import plugin, base for specialized importers"""), } -Importer = collections.namedtuple('Importer', ('callback', 'short_desc', 'long_desc')) +Importer = collections.namedtuple("Importer", ("callback", "short_desc", "long_desc")) class ImportPlugin(object): - def __init__(self, host): log.info(_("plugin Import initialization")) self.host = host @@ -64,21 +64,51 @@ @param name(unicode): import handler name """ assert name == name.lower().strip() - log.info(_(u'initializing {name} import handler').format(name=name)) + log.info(_(u"initializing {name} import handler").format(name=name)) import_handler.name = name import_handler.register = partial(self.register, import_handler) import_handler.unregister = partial(self.unregister, import_handler) import_handler.importers = {} + def _import(name, location, options, pubsub_service, pubsub_node, profile): - return self._doImport(import_handler, name, location, options, pubsub_service, pubsub_node, profile) + return self._doImport( + import_handler, + name, + location, + options, + pubsub_service, + pubsub_node, + profile, + ) + def _importList(): return self.listImporters(import_handler) + def _importDesc(name): return self.getDescription(import_handler, name) - self.host.bridge.addMethod(name + "Import", ".plugin", in_sign='ssa{ss}sss', out_sign='s', method=_import, async=True) - self.host.bridge.addMethod(name + "ImportList", ".plugin", in_sign='', out_sign='a(ss)', method=_importList) - self.host.bridge.addMethod(name + "ImportDesc", ".plugin", in_sign='s', out_sign='(ss)', method=_importDesc) + self.host.bridge.addMethod( + name + "Import", + ".plugin", + in_sign="ssa{ss}sss", + out_sign="s", + method=_import, + async=True, + ) + self.host.bridge.addMethod( + name + "ImportList", + ".plugin", + in_sign="", + out_sign="a(ss)", + method=_importList, + ) + self.host.bridge.addMethod( + name + "ImportDesc", + ".plugin", + in_sign="s", + out_sign="(ss)", + method=_importDesc, + ) def getProgress(self, import_handler, progress_id, profile): client = self.host.getClient(profile) @@ -87,7 +117,10 @@ def listImporters(self, import_handler): importers = import_handler.importers.keys() importers.sort() - return [(name, import_handler.importers[name].short_desc) for name in import_handler.importers] + return [ + (name, import_handler.importers[name].short_desc) + for name in import_handler.importers + ] def getDescription(self, import_handler, name): """Return import short and long descriptions @@ -98,13 +131,24 @@ try: importer = import_handler.importers[name] except KeyError: - raise exceptions.NotFound(u"{handler_name} importer not found [{name}]".format( - handler_name = import_handler.name, - name = name)) + raise exceptions.NotFound( + u"{handler_name} importer not found [{name}]".format( + handler_name=import_handler.name, name=name + ) + ) else: return importer.short_desc, importer.long_desc - def _doImport(self, import_handler, name, location, options, pubsub_service='', pubsub_node='', profile=C.PROF_KEY_NONE): + def _doImport( + self, + import_handler, + name, + location, + options, + pubsub_service="", + pubsub_node="", + profile=C.PROF_KEY_NONE, + ): client = self.host.getClient(profile) options = {key: unicode(value) for key, value in options.iteritems()} for option in import_handler.BOOL_OPTIONS: @@ -116,12 +160,31 @@ try: options[option] = json.loads(options[option]) except ValueError: - raise exceptions.DataError(_(u'invalid json option: {name}').format(name=option)) + raise exceptions.DataError( + _(u"invalid json option: {name}").format(name=option) + ) pubsub_service = jid.JID(pubsub_service) if pubsub_service else None - return self.doImport(client, import_handler, unicode(name), unicode(location), options, pubsub_service, pubsub_node or None) + return self.doImport( + client, + import_handler, + unicode(name), + unicode(location), + options, + pubsub_service, + pubsub_node or None, + ) @defer.inlineCallbacks - def doImport(self, client, import_handler, name, location, options=None, pubsub_service=None, pubsub_node=None): + def doImport( + self, + client, + import_handler, + name, + location, + options=None, + pubsub_service=None, + pubsub_node=None, + ): """Import data @param import_handler(object): instance of the import handler @@ -142,7 +205,7 @@ for opt_name, opt_default in import_handler.OPT_DEFAULTS.iteritems(): # we want a filled options dict, with all empty or False values removed try: - value =options[opt_name] + value = options[opt_name] except KeyError: if opt_default: options[opt_name] = opt_default @@ -154,31 +217,61 @@ importer = import_handler.importers[name] except KeyError: raise exceptions.NotFound(u"Importer [{}] not found".format(name)) - items_import_data, items_count = yield importer.callback(client, location, options) + items_import_data, items_count = yield importer.callback( + client, location, options + ) progress_id = unicode(uuid.uuid4()) try: _import = client._import except AttributeError: _import = client._import = {} progress_data = _import.setdefault(import_handler.name, {}) - progress_data[progress_id] = {u'position': '0'} + progress_data[progress_id] = {u"position": "0"} if items_count is not None: - progress_data[progress_id]['size'] = unicode(items_count) - metadata = {'name': u'{}: {}'.format(name, location), - 'direction': 'out', - 'type': import_handler.name.upper() + '_IMPORT' - } - self.host.registerProgressCb(progress_id, partial(self.getProgress, import_handler), metadata, profile=client.profile) + progress_data[progress_id]["size"] = unicode(items_count) + metadata = { + "name": u"{}: {}".format(name, location), + "direction": "out", + "type": import_handler.name.upper() + "_IMPORT", + } + self.host.registerProgressCb( + progress_id, + partial(self.getProgress, import_handler), + metadata, + profile=client.profile, + ) self.host.bridge.progressStarted(progress_id, metadata, client.profile) - session = { # session data, can be used by importers - u'root_service': pubsub_service, - u'root_node': pubsub_node + session = { # session data, can be used by importers + u"root_service": pubsub_service, + u"root_node": pubsub_node, } - self.recursiveImport(client, import_handler, items_import_data, progress_id, session, options, None, pubsub_service, pubsub_node) + self.recursiveImport( + client, + import_handler, + items_import_data, + progress_id, + session, + options, + None, + pubsub_service, + pubsub_node, + ) defer.returnValue(progress_id) @defer.inlineCallbacks - def recursiveImport(self, client, import_handler, items_import_data, progress_id, session, options, return_data=None, service=None, node=None, depth=0): + def recursiveImport( + self, + client, + import_handler, + items_import_data, + progress_id, + session, + options, + return_data=None, + service=None, + node=None, + depth=0, + ): """Do the import recursively @param import_handler(object): instance of the import handler @@ -196,33 +289,37 @@ if return_data is None: return_data = {} for idx, item_import_data in enumerate(items_import_data): - item_data = yield import_handler.importItem(client, item_import_data, session, options, return_data, service, node) + item_data = yield import_handler.importItem( + client, item_import_data, session, options, return_data, service, node + ) yield import_handler.itemFilters(client, item_data, session, options) - recurse_kwargs = yield import_handler.importSubItems(client, item_import_data, item_data, session, options) + recurse_kwargs = yield import_handler.importSubItems( + client, item_import_data, item_data, session, options + ) yield import_handler.publishItem(client, item_data, service, node, session) if recurse_kwargs is not None: - recurse_kwargs['client'] = client - recurse_kwargs['import_handler'] = import_handler - recurse_kwargs['progress_id'] = progress_id - recurse_kwargs['session'] = session - recurse_kwargs.setdefault('options', options) - recurse_kwargs['return_data'] = return_data - recurse_kwargs['depth'] = depth + 1 + recurse_kwargs["client"] = client + recurse_kwargs["import_handler"] = import_handler + recurse_kwargs["progress_id"] = progress_id + recurse_kwargs["session"] = session + recurse_kwargs.setdefault("options", options) + recurse_kwargs["return_data"] = return_data + recurse_kwargs["depth"] = depth + 1 log.debug(_(u"uploading subitems")) yield self.recursiveImport(**recurse_kwargs) if depth == 0: - client._import[import_handler.name][progress_id]['position'] = unicode(idx+1) + client._import[import_handler.name][progress_id]["position"] = unicode( + idx + 1 + ) if depth == 0: - self.host.bridge.progressFinished(progress_id, - return_data, - client.profile) + self.host.bridge.progressFinished(progress_id, return_data, client.profile) self.host.removeProgressCb(progress_id, client.profile) del client._import[import_handler.name][progress_id] - def register(self, import_handler, name, callback, short_desc='', long_desc=''): + def register(self, import_handler, name, callback, short_desc="", long_desc=""): """Register an Importer method @param name(unicode): unique importer name, should indicate the software it can import and always lowercase @@ -239,9 +336,11 @@ """ name = name.lower() if name in import_handler.importers: - raise exceptions.ConflictError(_(u"An {handler_name} importer with the name {name} already exist").format( - handler_name = import_handler.name, - name = name)) + raise exceptions.ConflictError( + _( + u"An {handler_name} importer with the name {name} already exist" + ).format(handler_name=import_handler.name, name=name) + ) import_handler.importers[name] = Importer(callback, short_desc, long_desc) def unregister(self, import_handler, name):
--- a/sat/plugins/plugin_misc_account.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_account.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,6 +19,7 @@ from sat.core.i18n import _, D_ from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from sat.tools import xml_tools @@ -31,7 +32,7 @@ from twisted.words.protocols.jabber import jid from sat.tools import email as sat_email -# FIXME: this plugin code is old and need a cleaning +# FIXME: this plugin code is old and need a cleaning # TODO: account deletion/password change need testing @@ -41,10 +42,10 @@ C.PI_TYPE: "MISC", C.PI_PROTOCOLS: [], C.PI_DEPENDENCIES: ["XEP-0077"], - C.PI_RECOMMENDATIONS: ['GROUPBLOG'], + C.PI_RECOMMENDATIONS: ["GROUPBLOG"], C.PI_MAIN: "MiscAccount", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _(u"""SàT account creation""") + C.PI_DESCRIPTION: _(u"""SàT account creation"""), } CONFIG_SECTION = "plugin account" @@ -53,22 +54,24 @@ # all theses values (key=option name, value=default) can (and should) be overriden in sat.conf # in section CONFIG_SECTION -default_conf = {"email_from": "NOREPLY@example.net", - "email_server": "localhost", - "email_sender_domain": "", - "email_port": 25, - "email_username": "", - "email_password": "", - "email_starttls": "false", - "email_auth": "false", - "email_admins_list": [], - "admin_email": "", - "new_account_server": "localhost", - "new_account_domain": "", # use xmpp_domain if not found - "reserved_list": ['libervia'] # profiles which can't be used - } +default_conf = { + "email_from": "NOREPLY@example.net", + "email_server": "localhost", + "email_sender_domain": "", + "email_port": 25, + "email_username": "", + "email_password": "", + "email_starttls": "false", + "email_auth": "false", + "email_admins_list": [], + "admin_email": "", + "new_account_server": "localhost", + "new_account_domain": "", # use xmpp_domain if not found + "reserved_list": ["libervia"], # profiles which can't be used +} -WELCOME_MSG = D_(u"""Welcome to Libervia, the web interface of Salut à Toi. +WELCOME_MSG = D_( + u"""Welcome to Libervia, the web interface of Salut à Toi. Your account on {domain} has been successfully created. This is a demonstration version to show you the current status of the project. @@ -88,55 +91,101 @@ Salut à Toi association https://www.salut-a-toi.org -""") +""" +) DEFAULT_DOMAIN = u"example.net" class MiscAccount(object): """Account plugin: create a SàT + XMPP account, used by Libervia""" + # XXX: This plugin was initialy a Q&D one used for the demo. # TODO: cleaning, separate email handling, more configuration/tests, fixes - def __init__(self, host): log.info(_(u"Plugin Account initialization")) self.host = host - host.bridge.addMethod("registerSatAccount", ".plugin", in_sign='sss', out_sign='', method=self._registerAccount, async=True) - host.bridge.addMethod("getNewAccountDomain", ".plugin", in_sign='', out_sign='s', method=self.getNewAccountDomain, async=False) - host.bridge.addMethod("getAccountDialogUI", ".plugin", in_sign='s', out_sign='s', method=self._getAccountDialogUI, async=False) - host.bridge.addMethod("asyncConnectWithXMPPCredentials", ".plugin", in_sign='ss', out_sign='b', method=self.asyncConnectWithXMPPCredentials, async=True) + host.bridge.addMethod( + "registerSatAccount", + ".plugin", + in_sign="sss", + out_sign="", + method=self._registerAccount, + async=True, + ) + host.bridge.addMethod( + "getNewAccountDomain", + ".plugin", + in_sign="", + out_sign="s", + method=self.getNewAccountDomain, + async=False, + ) + host.bridge.addMethod( + "getAccountDialogUI", + ".plugin", + in_sign="s", + out_sign="s", + method=self._getAccountDialogUI, + async=False, + ) + host.bridge.addMethod( + "asyncConnectWithXMPPCredentials", + ".plugin", + in_sign="ss", + out_sign="b", + method=self.asyncConnectWithXMPPCredentials, + async=True, + ) self.fixEmailAdmins() self._sessions = Sessions() - self.__account_cb_id = host.registerCallback(self._accountDialogCb, with_data=True) - self.__change_password_id = host.registerCallback(self.__changePasswordCb, with_data=True) + self.__account_cb_id = host.registerCallback( + self._accountDialogCb, with_data=True + ) + self.__change_password_id = host.registerCallback( + self.__changePasswordCb, with_data=True + ) def deleteBlogCallback(posts, comments): - return lambda data, profile: self.__deleteBlogPostsCb(posts, comments, data, profile) + return lambda data, profile: self.__deleteBlogPostsCb( + posts, comments, data, profile + ) - self.__delete_posts_id = host.registerCallback(deleteBlogCallback(True, False), with_data=True) - self.__delete_comments_id = host.registerCallback(deleteBlogCallback(False, True), with_data=True) - self.__delete_posts_comments_id = host.registerCallback(deleteBlogCallback(True, True), with_data=True) + self.__delete_posts_id = host.registerCallback( + deleteBlogCallback(True, False), with_data=True + ) + self.__delete_comments_id = host.registerCallback( + deleteBlogCallback(False, True), with_data=True + ) + self.__delete_posts_comments_id = host.registerCallback( + deleteBlogCallback(True, True), with_data=True + ) - self.__delete_account_id = host.registerCallback(self.__deleteAccountCb, with_data=True) - + self.__delete_account_id = host.registerCallback( + self.__deleteAccountCb, with_data=True + ) # FIXME: remove this after some time, when the deprecated parameter is really abandoned def fixEmailAdmins(self): """Handle deprecated config option "admin_email" to fix the admin emails list""" - admin_email = self.getConfig('admin_email') + admin_email = self.getConfig("admin_email") if not admin_email: return - log.warning(u"admin_email parameter is deprecated, please use email_admins_list instead") + log.warning( + u"admin_email parameter is deprecated, please use email_admins_list instead" + ) param_name = "email_admins_list" try: section = "" value = self.host.memory.getConfig(section, param_name, Exception) except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): section = CONFIG_SECTION - value = self.host.memory.getConfig(section, param_name, default_conf[param_name]) + value = self.host.memory.getConfig( + section, param_name, default_conf[param_name] + ) value = set(value) value.add(admin_email) @@ -193,7 +242,7 @@ if not password or not profile: raise exceptions.DataError - if profile.lower() in self.getConfig('reserved_list'): + if profile.lower() in self.getConfig("reserved_list"): return defer.fail(Failure(exceptions.ConflictError)) d = self.host.memory.createProfile(profile, password) @@ -216,11 +265,15 @@ else: jid_s = profile + u"@" + self.getNewAccountDomain() jid_ = jid.JID(jid_s) - d = self.host.plugins['XEP-0077'].registerNewAccount(jid_, password) + d = self.host.plugins["XEP-0077"].registerNewAccount(jid_, password) def setParams(dummy): - self.host.memory.setParam("JabberID", jid_s, "Connection", profile_key=profile) - d = self.host.memory.setParam("Password", password, "Connection", profile_key=profile) + self.host.memory.setParam( + "JabberID", jid_s, "Connection", profile_key=profile + ) + d = self.host.memory.setParam( + "Password", password, "Connection", profile_key=profile + ) return d def removeProfile(failure): @@ -235,9 +288,11 @@ def _sendEmailEb(self, failure_, email): # TODO: return error code to user - log.error(_(u"Failed to send account creation confirmation to {email}: {msg}").format( - email = email, - msg = failure_)) + log.error( + _(u"Failed to send account creation confirmation to {email}: {msg}").format( + email=email, msg=failure_ + ) + ) def sendEmails(self, email, profile): # time to send the email @@ -245,43 +300,68 @@ domain = self.getNewAccountDomain() # email to the administrators - admins_emails = self.getConfig('email_admins_list') + admins_emails = self.getConfig("email_admins_list") if not admins_emails: - log.warning(u"No known admin email, we can't send email to administrator(s).\nPlease fill email_admins_list parameter") + log.warning( + u"No known admin email, we can't send email to administrator(s).\nPlease fill email_admins_list parameter" + ) d_admin = defer.fail(exceptions.DataError("no admin email")) else: - subject = _(u'New Libervia account created') - body = (u"""New account created: {profile} [{email}]""".format( - profile = profile, + subject = _(u"New Libervia account created") + body = u"""New account created: {profile} [{email}]""".format( + profile=profile, # there is no email when an existing XMPP account is used - email = email or u"<no email>")) + email=email or u"<no email>", + ) d_admin = sat_email.sendEmail(self.host, admins_emails, subject, body) - admins_emails_txt = u', '.join([u'<' + addr + u'>' for addr in admins_emails]) - d_admin.addCallbacks(lambda dummy: log.debug(u"Account creation notification sent to admin(s) {}".format(admins_emails_txt)), - lambda dummy: log.error(u"Failed to send account creation notification to admin {}".format(admins_emails_txt))) + admins_emails_txt = u", ".join([u"<" + addr + u">" for addr in admins_emails]) + d_admin.addCallbacks( + lambda dummy: log.debug( + u"Account creation notification sent to admin(s) {}".format( + admins_emails_txt + ) + ), + lambda dummy: log.error( + u"Failed to send account creation notification to admin {}".format( + admins_emails_txt + ) + ), + ) if not email: # TODO: if use register with an existing account, an XMPP message should be sent return d_admin - jid_s = self.host.memory.getParamA(u"JabberID", u"Connection", profile_key=profile) - subject = _(u'Your Libervia account has been created') - body = (_(WELCOME_MSG).format(profile=profile, jid=jid_s, domain=domain)) + jid_s = self.host.memory.getParamA( + u"JabberID", u"Connection", profile_key=profile + ) + subject = _(u"Your Libervia account has been created") + body = _(WELCOME_MSG).format(profile=profile, jid=jid_s, domain=domain) # XXX: this will not fail when the email address doesn't exist # FIXME: check email reception to validate email given by the user # FIXME: delete the profile if the email could not been sent? d_user = sat_email.sendEmail(self.host, [email], subject, body) - d_user.addCallbacks(lambda dummy: log.debug(u"Account creation confirmation sent to <{}>".format(email)), - self._sendEmailEb) + d_user.addCallbacks( + lambda dummy: log.debug( + u"Account creation confirmation sent to <{}>".format(email) + ), + self._sendEmailEb, + ) return defer.DeferredList([d_user, d_admin]) def getNewAccountDomain(self): """get the domain that will be set to new account""" - domain = self.getConfig('new_account_domain') or self.getConfig('xmpp_domain', None) + domain = self.getConfig("new_account_domain") or self.getConfig( + "xmpp_domain", None + ) if not domain: - log.warning(_(u'xmpp_domain needs to be set in sat.conf. Using "{default}" meanwhile').format(default=DEFAULT_DOMAIN)) + log.warning( + _( + u'xmpp_domain needs to be set in sat.conf. Using "{default}" meanwhile' + ).format(default=DEFAULT_DOMAIN) + ) return DEFAULT_DOMAIN return domain @@ -291,10 +371,17 @@ @param profile: %(doc_profile)s @return: XML of the dialog """ - form_ui = xml_tools.XMLUI("form", "tabs", title=D_("Manage your account"), submit_id=self.__account_cb_id) + form_ui = xml_tools.XMLUI( + "form", + "tabs", + title=D_("Manage your account"), + submit_id=self.__account_cb_id, + ) tab_container = form_ui.current_container - tab_container.addTab("update", D_("Change your password"), container=xml_tools.PairsContainer) + tab_container.addTab( + "update", D_("Change your password"), container=xml_tools.PairsContainer + ) form_ui.addLabel(D_("Current profile password")) form_ui.addPassword("current_passwd", value="") form_ui.addLabel(D_("New password")) @@ -328,7 +415,9 @@ @param data @param profile """ - sat_cipher = yield self.host.memory.asyncGetParamA(C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile) + sat_cipher = yield self.host.memory.asyncGetParamA( + C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile + ) @defer.inlineCallbacks def verify(attempt): @@ -340,7 +429,7 @@ message = D_("The provided profile password doesn't match.") error_ui = xml_tools.XMLUI("popup", title=D_("Attempt failure")) error_ui.addText(message) - return {'xmlui': error_ui.toXml()} + return {"xmlui": error_ui.toXml()} # check for account deletion # FIXME: uncomment and fix these features @@ -370,9 +459,9 @@ """ # check for password modification - current_passwd = data[xml_tools.SAT_FORM_PREFIX + 'current_passwd'] - new_passwd1 = data[xml_tools.SAT_FORM_PREFIX + 'new_passwd1'] - new_passwd2 = data[xml_tools.SAT_FORM_PREFIX + 'new_passwd2'] + current_passwd = data[xml_tools.SAT_FORM_PREFIX + "current_passwd"] + new_passwd1 = data[xml_tools.SAT_FORM_PREFIX + "new_passwd1"] + new_passwd2 = data[xml_tools.SAT_FORM_PREFIX + "new_passwd2"] if new_passwd1 or new_passwd2: verified = yield verify(current_passwd) assert isinstance(verified, bool) @@ -381,7 +470,11 @@ data = yield self.__changePassword(new_passwd1, profile=profile) defer.returnValue(data) else: - defer.returnValue(error_ui(D_("The values entered for the new password are not equal."))) + defer.returnValue( + error_ui( + D_("The values entered for the new password are not equal.") + ) + ) defer.returnValue(error_ui()) defer.returnValue({}) @@ -392,11 +485,22 @@ @param password (str): the new password @param profile (str): %(doc_profile)s """ - session_id, dummy = self._sessions.newSession({'new_password': password}, profile=profile) - form_ui = xml_tools.XMLUI("form", title=D_("Change your password?"), submit_id=self.__change_password_id, session_id=session_id) - form_ui.addText(D_("Note for advanced users: this will actually change both your SàT profile password AND your XMPP account password.")) + session_id, dummy = self._sessions.newSession( + {"new_password": password}, profile=profile + ) + form_ui = xml_tools.XMLUI( + "form", + title=D_("Change your password?"), + submit_id=self.__change_password_id, + session_id=session_id, + ) + form_ui.addText( + D_( + "Note for advanced users: this will actually change both your SàT profile password AND your XMPP account password." + ) + ) form_ui.addText(D_("Continue with changing the password?")) - return {'xmlui': form_ui.toXml()} + return {"xmlui": form_ui.toXml()} def __changePasswordCb(self, data, profile): """Actually change the user XMPP account and SàT profile password @@ -404,22 +508,33 @@ @profile (str): %(doc_profile)s """ client = self.host.getClient(profile) - password = self._sessions.profileGet(data['session_id'], profile)['new_password'] - del self._sessions[data['session_id']] + password = self._sessions.profileGet(data["session_id"], profile)["new_password"] + del self._sessions[data["session_id"]] def passwordChanged(dummy): - d = self.host.memory.setParam(C.PROFILE_PASS_PATH[1], password, C.PROFILE_PASS_PATH[0], profile_key=profile) - d.addCallback(lambda dummy: self.host.memory.setParam("Password", password, "Connection", profile_key=profile)) + d = self.host.memory.setParam( + C.PROFILE_PASS_PATH[1], + password, + C.PROFILE_PASS_PATH[0], + profile_key=profile, + ) + d.addCallback( + lambda dummy: self.host.memory.setParam( + "Password", password, "Connection", profile_key=profile + ) + ) confirm_ui = xml_tools.XMLUI("popup", title=D_("Confirmation")) confirm_ui.addText(D_("Your password has been changed.")) - return defer.succeed({'xmlui': confirm_ui.toXml()}) + return defer.succeed({"xmlui": confirm_ui.toXml()}) def errback(failure): error_ui = xml_tools.XMLUI("popup", title=D_("Error")) - error_ui.addText(D_("Your password could not be changed: %s") % failure.getErrorMessage()) - return defer.succeed({'xmlui': error_ui.toXml()}) + error_ui.addText( + D_("Your password could not be changed: %s") % failure.getErrorMessage() + ) + return defer.succeed({"xmlui": error_ui.toXml()}) - d = self.host.plugins['XEP-0077'].changePassword(client, password) + d = self.host.plugins["XEP-0077"].changePassword(client, password) d.addCallbacks(passwordChanged, errback) return d @@ -427,12 +542,31 @@ """Ask for a confirmation before deleting the XMPP account and SàT profile @param profile """ - form_ui = xml_tools.XMLUI("form", title=D_("Delete your account?"), submit_id=self.__delete_account_id) - form_ui.addText(D_("If you confirm this dialog, you will be disconnected and then your XMPP account AND your SàT profile will both be DELETED.")) - target = D_('contact list, messages history, blog posts and comments' if 'GROUPBLOG' in self.host.plugins else D_('contact list and messages history')) - form_ui.addText(D_("All your data stored on %(server)s, including your %(target)s will be erased.") % {'server': self.getNewAccountDomain(), 'target': target}) - form_ui.addText(D_("There is no other confirmation dialog, this is the very last one! Are you sure?")) - return {'xmlui': form_ui.toXml()} + form_ui = xml_tools.XMLUI( + "form", title=D_("Delete your account?"), submit_id=self.__delete_account_id + ) + form_ui.addText( + D_( + "If you confirm this dialog, you will be disconnected and then your XMPP account AND your SàT profile will both be DELETED." + ) + ) + target = D_( + "contact list, messages history, blog posts and comments" + if "GROUPBLOG" in self.host.plugins + else D_("contact list and messages history") + ) + form_ui.addText( + D_( + "All your data stored on %(server)s, including your %(target)s will be erased." + ) + % {"server": self.getNewAccountDomain(), "target": target} + ) + form_ui.addText( + D_( + "There is no other confirmation dialog, this is the very last one! Are you sure?" + ) + ) + return {"xmlui": form_ui.toXml()} def __deleteAccountCb(self, data, profile): """Actually delete the XMPP account and SàT profile @@ -441,18 +575,25 @@ @param profile """ client = self.host.getClient(profile) + def userDeleted(dummy): # FIXME: client should be disconnected at this point, so 2 next loop should be removed (to be confirmed) for jid_ in client.roster._jids: # empty roster client.presence.unsubscribe(jid_) - for jid_ in self.host.memory.getWaitingSub(profile): # delete waiting subscriptions + for jid_ in self.host.memory.getWaitingSub( + profile + ): # delete waiting subscriptions self.host.memory.delWaitingSub(jid_) - delete_profile = lambda: self.host.memory.asyncDeleteProfile(profile, force=True) - if 'GROUPBLOG' in self.host.plugins: - d = self.host.plugins['GROUPBLOG'].deleteAllGroupBlogsAndComments(profile_key=profile) + delete_profile = lambda: self.host.memory.asyncDeleteProfile( + profile, force=True + ) + if "GROUPBLOG" in self.host.plugins: + d = self.host.plugins["GROUPBLOG"].deleteAllGroupBlogsAndComments( + profile_key=profile + ) d.addCallback(lambda dummy: delete_profile()) else: delete_profile() @@ -461,10 +602,13 @@ def errback(failure): error_ui = xml_tools.XMLUI("popup", title=D_("Error")) - error_ui.addText(D_("Your XMPP account could not be deleted: %s") % failure.getErrorMessage()) - return defer.succeed({'xmlui': error_ui.toXml()}) + error_ui.addText( + D_("Your XMPP account could not be deleted: %s") + % failure.getErrorMessage() + ) + return defer.succeed({"xmlui": error_ui.toXml()}) - d = self.host.plugins['XEP-0077'].unregister(client, jid.JID(client.jid.host)) + d = self.host.plugins["XEP-0077"].unregister(client, jid.JID(client.jid.host)) d.addCallbacks(userDeleted, errback) return d @@ -477,20 +621,60 @@ """ if posts: if comments: # delete everything - form_ui = xml_tools.XMLUI("form", title=D_("Delete all your (micro-)blog posts and comments?"), submit_id=self.__delete_posts_comments_id) - form_ui.addText(D_("If you confirm this dialog, all the (micro-)blog data you submitted will be erased.")) - form_ui.addText(D_("These are the public and private posts and comments you sent to any group.")) - form_ui.addText(D_("There is no other confirmation dialog, this is the very last one! Are you sure?")) + form_ui = xml_tools.XMLUI( + "form", + title=D_("Delete all your (micro-)blog posts and comments?"), + submit_id=self.__delete_posts_comments_id, + ) + form_ui.addText( + D_( + "If you confirm this dialog, all the (micro-)blog data you submitted will be erased." + ) + ) + form_ui.addText( + D_( + "These are the public and private posts and comments you sent to any group." + ) + ) + form_ui.addText( + D_( + "There is no other confirmation dialog, this is the very last one! Are you sure?" + ) + ) else: # delete only the posts - form_ui = xml_tools.XMLUI("form", title=D_("Delete all your (micro-)blog posts?"), submit_id=self.__delete_posts_id) - form_ui.addText(D_("If you confirm this dialog, all the public and private posts you sent to any group will be erased.")) - form_ui.addText(D_("There is no other confirmation dialog, this is the very last one! Are you sure?")) + form_ui = xml_tools.XMLUI( + "form", + title=D_("Delete all your (micro-)blog posts?"), + submit_id=self.__delete_posts_id, + ) + form_ui.addText( + D_( + "If you confirm this dialog, all the public and private posts you sent to any group will be erased." + ) + ) + form_ui.addText( + D_( + "There is no other confirmation dialog, this is the very last one! Are you sure?" + ) + ) elif comments: # delete only the comments - form_ui = xml_tools.XMLUI("form", title=D_("Delete all your (micro-)blog comments?"), submit_id=self.__delete_comments_id) - form_ui.addText(D_("If you confirm this dialog, all the public and private comments you made on other people's posts will be erased.")) - form_ui.addText(D_("There is no other confirmation dialog, this is the very last one! Are you sure?")) + form_ui = xml_tools.XMLUI( + "form", + title=D_("Delete all your (micro-)blog comments?"), + submit_id=self.__delete_comments_id, + ) + form_ui.addText( + D_( + "If you confirm this dialog, all the public and private comments you made on other people's posts will be erased." + ) + ) + form_ui.addText( + D_( + "There is no other confirmation dialog, this is the very last one! Are you sure?" + ) + ) - return {'xmlui': form_ui.toXml()} + return {"xmlui": form_ui.toXml()} def __deleteBlogPostsCb(self, posts, comments, data, profile): """Actually delete the XMPP account and SàT profile @@ -500,26 +684,39 @@ """ if posts: if comments: - target = D_('blog posts and comments') - d = self.host.plugins['GROUPBLOG'].deleteAllGroupBlogsAndComments(profile_key=profile) + target = D_("blog posts and comments") + d = self.host.plugins["GROUPBLOG"].deleteAllGroupBlogsAndComments( + profile_key=profile + ) else: - target = D_('blog posts') - d = self.host.plugins['GROUPBLOG'].deleteAllGroupBlogs(profile_key=profile) + target = D_("blog posts") + d = self.host.plugins["GROUPBLOG"].deleteAllGroupBlogs( + profile_key=profile + ) elif comments: - target = D_('comments') - d = self.host.plugins['GROUPBLOG'].deleteAllGroupBlogsComments(profile_key=profile) + target = D_("comments") + d = self.host.plugins["GROUPBLOG"].deleteAllGroupBlogsComments( + profile_key=profile + ) def deleted(result): ui = xml_tools.XMLUI("popup", title=D_("Deletion confirmation")) # TODO: change the message when delete/retract notifications are done with XEP-0060 - ui.addText(D_("Your %(target)s have been deleted.") % {'target': target}) - ui.addText(D_("Known issue of the demo version: you need to refresh the page to make the deleted posts actually disappear.")) - return defer.succeed({'xmlui': ui.toXml()}) + ui.addText(D_("Your %(target)s have been deleted.") % {"target": target}) + ui.addText( + D_( + "Known issue of the demo version: you need to refresh the page to make the deleted posts actually disappear." + ) + ) + return defer.succeed({"xmlui": ui.toXml()}) def errback(failure): error_ui = xml_tools.XMLUI("popup", title=D_("Error")) - error_ui.addText(D_("Your %(target)s could not be deleted: %(message)s") % {'target': target, 'message': failure.getErrorMessage()}) - return defer.succeed({'xmlui': error_ui.toXml()}) + error_ui.addText( + D_("Your %(target)s could not be deleted: %(message)s") + % {"target": target, "message": failure.getErrorMessage()} + ) + return defer.succeed({"xmlui": error_ui.toXml()}) d.addCallbacks(deleted, errback) return d @@ -541,16 +738,21 @@ raise exceptions.ConflictError d = self.createProfile(password, jid_s, jid_s) - d.addCallback(lambda dummy: self.host.memory.getProfileName(jid_s)) # checks if the profile has been successfuly created + d.addCallback( + lambda dummy: self.host.memory.getProfileName(jid_s) + ) # checks if the profile has been successfuly created d.addCallback(self.host.connect, password, {}, 0) - def connected(result): self.sendEmails(None, profile=jid_s) return result - def removeProfile(failure): # profile has been successfully created but the XMPP credentials are wrong! - log.debug("Removing previously auto-created profile: %s" % failure.getErrorMessage()) + def removeProfile( + failure + ): # profile has been successfully created but the XMPP credentials are wrong! + log.debug( + "Removing previously auto-created profile: %s" % failure.getErrorMessage() + ) self.host.memory.asyncDeleteProfile(jid_s) raise failure
--- a/sat/plugins/plugin_misc_android.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_android.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions import sys @@ -32,7 +33,9 @@ C.PI_TYPE: C.PLUG_TYPE_MISC, C.PI_MAIN: "AndroidPlugin", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: D_("""Manage Android platform specificities, like pause or notifications""") + C.PI_DESCRIPTION: D_( + """Manage Android platform specificities, like pause or notifications""" + ), } if sys.platform != "android": @@ -44,6 +47,7 @@ PARAM_VIBRATE_NAME = "vibrate" PARAM_VIBRATE_LABEL = D_(u"Vibrate on notifications") + class AndroidPlugin(object): params = """ @@ -55,40 +59,41 @@ </individual> </params> """.format( - category_name = PARAM_VIBRATE_CATEGORY, - category_label = D_(PARAM_VIBRATE_CATEGORY), - param_name = PARAM_VIBRATE_NAME, - param_label = PARAM_VIBRATE_LABEL, - ) + category_name=PARAM_VIBRATE_CATEGORY, + category_label=D_(PARAM_VIBRATE_CATEGORY), + param_name=PARAM_VIBRATE_NAME, + param_label=PARAM_VIBRATE_LABEL, + ) def __init__(self, host): log.info(_("plugin Android initialization")) self.host = host host.memory.updateParams(self.params) - self.cagou_status_fd = open('.cagou_status', 'rb') - self.cagou_status = mmap.mmap(self.cagou_status_fd.fileno(), 1, prot=mmap.PROT_READ) + self.cagou_status_fd = open(".cagou_status", "rb") + self.cagou_status = mmap.mmap( + self.cagou_status_fd.fileno(), 1, prot=mmap.PROT_READ + ) # we set a low priority because we want the notification to be sent after all plugins have done their job host.trigger.add("MessageReceived", self.messageReceivedTrigger, priority=-1000) @property def cagou_active(self): - # 'R' status means Cagou is running in front - return self.cagou_status[0] == 'R' + # 'R' status means Cagou is running in front + return self.cagou_status[0] == "R" def _notifyMessage(self, mess_data, client): # send notification if there is a message and it is not a groupchat - if mess_data['message'] and mess_data['type'] != C.MESS_TYPE_GROUPCHAT: - message = mess_data['message'].itervalues().next() + if mess_data["message"] and mess_data["type"] != C.MESS_TYPE_GROUPCHAT: + message = mess_data["message"].itervalues().next() try: - subject = mess_data['subject'].itervalues().next() + subject = mess_data["subject"].itervalues().next() except StopIteration: - subject = u'Cagou new message' + subject = u"Cagou new message" - notification.notify( - title = subject, - message = message - ) - if self.host.memory.getParamA(PARAM_VIBRATE_NAME, PARAM_VIBRATE_CATEGORY, profile_key=client.profile): + notification.notify(title=subject, message=message) + if self.host.memory.getParamA( + PARAM_VIBRATE_NAME, PARAM_VIBRATE_CATEGORY, profile_key=client.profile + ): vibrator.vibrate() return mess_data
--- a/sat/plugins/plugin_misc_debug.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_debug.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,6 +19,7 @@ from sat.core.i18n import _ from sat.core.log import getLogger + log = getLogger(__name__) from sat.core.constants import Const as C import json @@ -31,17 +32,21 @@ C.PI_DEPENDENCIES: [], C.PI_MAIN: "Debug", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Set of method to make development and debugging easier""") + C.PI_DESCRIPTION: _("""Set of method to make development and debugging easier"""), } class Debug(object): - def __init__(self, host): log.info(_("Plugin Debug initialization")) self.host = host - host.bridge.addMethod("debugFakeSignal", ".plugin", in_sign='sss', out_sign='', method=self._fakeSignal) - + host.bridge.addMethod( + "debugFakeSignal", + ".plugin", + in_sign="sss", + out_sign="", + method=self._fakeSignal, + ) def _fakeSignal(self, signal, arguments, profile_key): """send a signal from backend @@ -56,6 +61,3 @@ profile = self.host.memory.getProfileName(profile_key) args.append(profile) method(*args) - - -
--- a/sat/plugins/plugin_misc_extra_pep.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_extra_pep.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.memory import params from twisted.words.protocols.jabber import jid @@ -34,7 +35,7 @@ C.PI_RECOMMENDATIONS: [], C.PI_MAIN: "ExtraPEP", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _(u"""Display messages from extra PEP services""") + C.PI_DESCRIPTION: _(u"""Display messages from extra PEP services"""), } @@ -57,11 +58,11 @@ </individual> </params> """ % { - 'category_name': PARAM_KEY, - 'category_label': D_(PARAM_KEY), - 'param_name': PARAM_NAME, - 'param_label': D_(PARAM_LABEL), - 'jids': u"\n".join({elt.toXml() for elt in params.createJidElts(PARAM_DEFAULT)}) + "category_name": PARAM_KEY, + "category_label": D_(PARAM_KEY), + "param_name": PARAM_NAME, + "param_label": D_(PARAM_LABEL), + "jids": u"\n".join({elt.toXml() for elt in params.createJidElts(PARAM_DEFAULT)}), } def __init__(self, host):
--- a/sat/plugins/plugin_misc_file.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_file.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from sat.tools import xml_tools @@ -37,38 +38,67 @@ C.PI_MODES: C.PLUG_MODE_BOTH, C.PI_MAIN: "FilePlugin", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""File Tansfer Management: -This plugin manage the various ways of sending a file, and choose the best one.""") + C.PI_DESCRIPTION: _( + """File Tansfer Management: +This plugin manage the various ways of sending a file, and choose the best one.""" + ), } -SENDING = D_(u'Please select a file to send to {peer}') -SENDING_TITLE = D_(u'File sending') -CONFIRM = D_(u'{peer} wants to send the file "{name}" to you:\n{desc}\n\nThe file has a size of {size_human}\n\nDo you accept ?') -CONFIRM_TITLE = D_(u'Confirm file transfer') -CONFIRM_OVERWRITE = D_(u'File {} already exists, are you sure you want to overwrite ?') -CONFIRM_OVERWRITE_TITLE = D_(u'File exists') +SENDING = D_(u"Please select a file to send to {peer}") +SENDING_TITLE = D_(u"File sending") +CONFIRM = D_( + u'{peer} wants to send the file "{name}" to you:\n{desc}\n\nThe file has a size of {size_human}\n\nDo you accept ?' +) +CONFIRM_TITLE = D_(u"Confirm file transfer") +CONFIRM_OVERWRITE = D_(u"File {} already exists, are you sure you want to overwrite ?") +CONFIRM_OVERWRITE_TITLE = D_(u"File exists") SECURITY_LIMIT = 30 -PROGRESS_ID_KEY = 'progress_id' +PROGRESS_ID_KEY = "progress_id" class FilePlugin(object): - File=stream.SatFile + File = stream.SatFile def __init__(self, host): log.info(_("plugin File initialization")) self.host = host - host.bridge.addMethod("fileSend", ".plugin", in_sign='ssssa{ss}s', out_sign='a{ss}', method=self._fileSend, async=True) + host.bridge.addMethod( + "fileSend", + ".plugin", + in_sign="ssssa{ss}s", + out_sign="a{ss}", + method=self._fileSend, + async=True, + ) self._file_callbacks = [] - host.importMenu((D_("Action"), D_("send file")), self._fileSendMenu, security_limit=10, help_string=D_("Send a file"), type_=C.MENU_SINGLE) + host.importMenu( + (D_("Action"), D_("send file")), + self._fileSendMenu, + security_limit=10, + help_string=D_("Send a file"), + type_=C.MENU_SINGLE, + ) - def _fileSend(self, peer_jid_s, filepath, name="", file_desc="", extra=None, profile=C.PROF_KEY_NONE): + def _fileSend( + self, + peer_jid_s, + filepath, + name="", + file_desc="", + extra=None, + profile=C.PROF_KEY_NONE, + ): client = self.host.getClient(profile) - return self.fileSend(client, jid.JID(peer_jid_s), filepath, name or None, file_desc or None, extra) + return self.fileSend( + client, jid.JID(peer_jid_s), filepath, name or None, file_desc or None, extra + ) @defer.inlineCallbacks - def fileSend(self, client, peer_jid, filepath, filename=None, file_desc=None, extra=None): + def fileSend( + self, client, peer_jid, filepath, filename=None, file_desc=None, extra=None + ): """Send a file using best available method @param peer_jid(jid.JID): jid of the destinee @@ -81,22 +111,34 @@ if not os.path.isfile(filepath): raise exceptions.DataError(u"The given path doesn't link to a file") if not filename: - filename = os.path.basename(filepath) or '_' + filename = os.path.basename(filepath) or "_" for namespace, callback, priority, method_name in self._file_callbacks: has_feature = yield self.host.hasFeature(client, namespace, peer_jid) if has_feature: - log.info(u"{name} method will be used to send the file".format(name=method_name)) - progress_id = yield callback(client, peer_jid, filepath, filename, file_desc, extra) - defer.returnValue({'progress': progress_id}) + log.info( + u"{name} method will be used to send the file".format( + name=method_name + ) + ) + progress_id = yield callback( + client, peer_jid, filepath, filename, file_desc, extra + ) + defer.returnValue({"progress": progress_id}) msg = u"Can't find any method to send file to {jid}".format(jid=peer_jid.full()) log.warning(msg) - defer.returnValue({'xmlui': xml_tools.note(u"Can't transfer file", msg, C.XMLUI_DATA_LVL_WARNING).toXml()}) + defer.returnValue( + { + "xmlui": xml_tools.note( + u"Can't transfer file", msg, C.XMLUI_DATA_LVL_WARNING + ).toXml() + } + ) def _onFileChoosed(self, client, peer_jid, data): cancelled = C.bool(data.get("cancelled", C.BOOL_FALSE)) if cancelled: return - path=data['path'] + path = data["path"] return self.fileSend(client, peer_jid, path) def _fileSendMenu(self, data, profile): @@ -105,20 +147,28 @@ @param profile: %(doc_profile)s """ try: - jid_ = jid.JID(data['jid']) + jid_ = jid.JID(data["jid"]) except RuntimeError: raise exceptions.DataError(_("Invalid JID")) - file_choosed_id = self.host.registerCallback(lambda data, profile: self._onFileChoosed(self.host.getClient(profile), jid_, data), with_data=True, one_shot=True) + file_choosed_id = self.host.registerCallback( + lambda data, profile: self._onFileChoosed( + self.host.getClient(profile), jid_, data + ), + with_data=True, + one_shot=True, + ) xml_ui = xml_tools.XMLUI( C.XMLUI_DIALOG, - dialog_opt = { + dialog_opt={ C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_FILE, - C.XMLUI_DATA_MESS: _(SENDING).format(peer=jid_.full())}, - title = _(SENDING_TITLE), - submit_id = file_choosed_id) + C.XMLUI_DATA_MESS: _(SENDING).format(peer=jid_.full()), + }, + title=_(SENDING_TITLE), + submit_id=file_choosed_id, + ) - return {'xmlui': xml_ui.toXml()} + return {"xmlui": xml_ui.toXml()} def register(self, namespace, callback, priority=0, method_name=None): """Register a fileSending method @@ -130,8 +180,12 @@ """ for data in self._file_callbacks: if namespace == data[0]: - raise exceptions.ConflictError(u'A method with this namespace is already registered') - self._file_callbacks.append((namespace, callback, priority, method_name or namespace)) + raise exceptions.ConflictError( + u"A method with this namespace is already registered" + ) + self._file_callbacks.append( + (namespace, callback, priority, method_name or namespace) + ) self._file_callbacks.sort(key=lambda data: data[2], reverse=True) def unregister(self, namespace): @@ -148,29 +202,31 @@ """create SatFile or FileStremaObject for the requested file and fill suitable data """ if stream_object: - assert 'stream_object' not in transfer_data - transfer_data['stream_object'] = stream.FileStreamObject( + assert "stream_object" not in transfer_data + transfer_data["stream_object"] = stream.FileStreamObject( self.host, client, file_path, - mode='wb', + mode="wb", uid=file_data[PROGRESS_ID_KEY], - size=file_data['size'], - data_cb = file_data.get('data_cb'), - ) + size=file_data["size"], + data_cb=file_data.get("data_cb"), + ) else: - assert 'file_obj' not in transfer_data - transfer_data['file_obj'] = stream.SatFile( + assert "file_obj" not in transfer_data + transfer_data["file_obj"] = stream.SatFile( self.host, client, file_path, - mode='wb', + mode="wb", uid=file_data[PROGRESS_ID_KEY], - size=file_data['size'], - data_cb = file_data.get('data_cb'), - ) + size=file_data["size"], + data_cb=file_data.get("data_cb"), + ) - def _gotConfirmation(self, data, client, peer_jid, transfer_data, file_data, stream_object): + def _gotConfirmation( + self, data, client, peer_jid, transfer_data, file_data, stream_object + ): """Called when the permission and dest path have been received @param peer_jid(jid.JID): jid of the file sender @@ -181,17 +237,20 @@ False if user wants to cancel if file exists ask confirmation and call again self._getDestDir if needed """ - if data.get('cancelled', False): + if data.get("cancelled", False): return False - path = data['path'] - file_data['file_path'] = file_path = os.path.join(path, file_data['name']) - log.debug(u'destination file path set to {}'.format(file_path)) + path = data["path"] + file_data["file_path"] = file_path = os.path.join(path, file_data["name"]) + log.debug(u"destination file path set to {}".format(file_path)) # we manage case where file already exists if os.path.exists(file_path): + def check_overwrite(overwrite): if overwrite: - self.openFileWrite(client, file_path, transfer_data, file_data, stream_object) + self.openFileWrite( + client, file_path, transfer_data, file_data, stream_object + ) return True else: return self.getDestDir(client, peer_jid, transfer_data, file_data) @@ -200,12 +259,14 @@ self.host, _(CONFIRM_OVERWRITE).format(file_path), _(CONFIRM_OVERWRITE_TITLE), - action_extra={'meta_from_jid': peer_jid.full(), - 'meta_type': C.META_TYPE_OVERWRITE, - 'meta_progress_id': file_data[PROGRESS_ID_KEY] - }, + action_extra={ + "meta_from_jid": peer_jid.full(), + "meta_type": C.META_TYPE_OVERWRITE, + "meta_progress_id": file_data[PROGRESS_ID_KEY], + }, security_limit=SECURITY_LIMIT, - profile=client.profile) + profile=client.profile, + ) exists_d.addCallback(check_overwrite) return exists_d @@ -239,24 +300,38 @@ a stream.FileStreamObject will be used return (defer.Deferred): True if transfer is accepted """ - cont,ret_value = self.host.trigger.returnPoint("FILE_getDestDir", client, peer_jid, transfer_data, file_data, stream_object) + cont, ret_value = self.host.trigger.returnPoint( + "FILE_getDestDir", client, peer_jid, transfer_data, file_data, stream_object + ) if not cont: return ret_value - filename = file_data['name'] - assert filename and not '/' in filename + filename = file_data["name"] + assert filename and not "/" in filename assert PROGRESS_ID_KEY in file_data # human readable size - file_data['size_human'] = u'{:.6n} Mio'.format(float(file_data['size'])/(1024**2)) - d = xml_tools.deferDialog(self.host, + file_data["size_human"] = u"{:.6n} Mio".format( + float(file_data["size"]) / (1024 ** 2) + ) + d = xml_tools.deferDialog( + self.host, _(CONFIRM).format(peer=peer_jid.full(), **file_data), _(CONFIRM_TITLE), type_=C.XMLUI_DIALOG_FILE, options={C.XMLUI_DATA_FILETYPE: C.XMLUI_DATA_FILETYPE_DIR}, - action_extra={'meta_from_jid': peer_jid.full(), - 'meta_type': C.META_TYPE_FILE, - 'meta_progress_id': file_data[PROGRESS_ID_KEY] - }, + action_extra={ + "meta_from_jid": peer_jid.full(), + "meta_type": C.META_TYPE_FILE, + "meta_progress_id": file_data[PROGRESS_ID_KEY], + }, security_limit=SECURITY_LIMIT, - profile=client.profile) - d.addCallback(self._gotConfirmation, client, peer_jid, transfer_data, file_data, stream_object) + profile=client.profile, + ) + d.addCallback( + self._gotConfirmation, + client, + peer_jid, + transfer_data, + file_data, + stream_object, + ) return d
--- a/sat/plugins/plugin_misc_groupblog.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_groupblog.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.internet import defer from sat.core import exceptions @@ -32,10 +33,12 @@ except ImportError: from wokkel.subprotocols import XMPPHandler -NS_PUBSUB = 'http://jabber.org/protocol/pubsub' -NS_GROUPBLOG = 'http://salut-a-toi.org/protocol/groupblog' -#NS_PUBSUB_EXP = 'http://goffi.org/protocol/pubsub' #for non official features -NS_PUBSUB_EXP = NS_PUBSUB # XXX: we can't use custom namespace as Wokkel's PubSubService use official NS +NS_PUBSUB = "http://jabber.org/protocol/pubsub" +NS_GROUPBLOG = "http://salut-a-toi.org/protocol/groupblog" +# NS_PUBSUB_EXP = 'http://goffi.org/protocol/pubsub' #for non official features +NS_PUBSUB_EXP = ( + NS_PUBSUB +) # XXX: we can't use custom namespace as Wokkel's PubSubService use official NS NS_PUBSUB_GROUPBLOG = NS_PUBSUB_EXP + "#groupblog" NS_PUBSUB_ITEM_CONFIG = NS_PUBSUB_EXP + "#item-config" @@ -48,7 +51,7 @@ C.PI_DEPENDENCIES: ["XEP-0277"], C.PI_MAIN: "GroupBlog", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of microblogging fine permissions""") + C.PI_DESCRIPTION: _("""Implementation of microblogging fine permissions"""), } @@ -74,7 +77,11 @@ yield self.host.checkFeatures(client, (NS_PUBSUB_GROUPBLOG,)) except exceptions.FeatureNotFound: client.server_groupblog_available = False - log.warning(_(u"Server is not able to manage item-access pubsub, we can't use group blog")) + log.warning( + _( + u"Server is not able to manage item-access pubsub, we can't use group blog" + ) + ) else: client.server_groupblog_available = True log.info(_(u"Server can manage group blogs")) @@ -85,7 +92,7 @@ except exceptions.ProfileNotSetError: return {} try: - return {'available': C.boolConst(client.server_groupblog_available)} + return {"available": C.boolConst(client.server_groupblog_available)} except AttributeError: if self.host.isConnected(profile): log.debug("Profile is not connected, service is not checked yet") @@ -100,7 +107,11 @@ return access_model = config_form.get(self._p.OPT_ACCESS_MODEL, self._p.ACCESS_OPEN) if access_model == self._p.ACCESS_PUBLISHER_ROSTER: - data_format.iter2dict('group', config_form.fields[self._p.OPT_ROSTER_GROUPS_ALLOWED].values, microblog_data) + data_format.iter2dict( + "group", + config_form.fields[self._p.OPT_ROSTER_GROUPS_ALLOWED].values, + microblog_data, + ) def _data2entryTrigger(self, client, mb_data, entry_elt, item_elt): """Build fine access permission if needed @@ -108,14 +119,16 @@ This trigger check if "group*" key are present, and create a fine item config to restrict view to these groups """ - groups = list(data_format.dict2iter('group', mb_data)) + groups = list(data_format.dict2iter("group", mb_data)) if not groups: return if not client.server_groupblog_available: raise exceptions.CancelError(u"GroupBlog is not available") log.debug(u"This entry use group blog") - form = data_form.Form('submit', formNamespace=NS_PUBSUB_ITEM_CONFIG) - access = data_form.Field(None, self._p.OPT_ACCESS_MODEL, value=self._p.ACCESS_PUBLISHER_ROSTER) + form = data_form.Form("submit", formNamespace=NS_PUBSUB_ITEM_CONFIG) + access = data_form.Field( + None, self._p.OPT_ACCESS_MODEL, value=self._p.ACCESS_PUBLISHER_ROSTER + ) allowed = data_form.Field(None, self._p.OPT_ROSTER_GROUPS_ALLOWED, values=groups) form.addField(access) form.addField(allowed) @@ -128,14 +141,16 @@ """ if "group" in mb_data: options[self._p.OPT_ACCESS_MODEL] = self._p.ACCESS_PUBLISHER_ROSTER - options[self._p.OPT_ROSTER_GROUPS_ALLOWED] = list(data_format.dict2iter('group', mb_data)) + options[self._p.OPT_ROSTER_GROUPS_ALLOWED] = list( + data_format.dict2iter("group", mb_data) + ) class GroupBlog_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_GROUPBLOG)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_misc_identity.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_identity.py Wed Jun 27 20:14:46 2018 +0200 @@ -22,6 +22,7 @@ from sat.core.constants import Const as C from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) from twisted.internet import defer from twisted.words.protocols.jabber import jid @@ -31,24 +32,37 @@ PLUGIN_INFO = { C.PI_NAME: "Identity Plugin", C.PI_IMPORT_NAME: "IDENTITY", - C.PI_TYPE: C.PLUG_TYPE_MISC , + C.PI_TYPE: C.PLUG_TYPE_MISC, C.PI_PROTOCOLS: [], C.PI_DEPENDENCIES: ["XEP-0054"], C.PI_RECOMMENDATIONS: [], C.PI_MAIN: "Identity", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Identity manager""") + C.PI_DESCRIPTION: _("""Identity manager"""), } class Identity(object): - def __init__(self, host): log.info(_(u"Plugin Identity initialization")) self.host = host - self._v = host.plugins[u'XEP-0054'] - host.bridge.addMethod(u"identityGet", u".plugin", in_sign=u'ss', out_sign=u'a{ss}', method=self._getIdentity, async=True) - host.bridge.addMethod(u"identitySet", u".plugin", in_sign=u'a{ss}s', out_sign=u'', method=self._setIdentity, async=True) + self._v = host.plugins[u"XEP-0054"] + host.bridge.addMethod( + u"identityGet", + u".plugin", + in_sign=u"ss", + out_sign=u"a{ss}", + method=self._getIdentity, + async=True, + ) + host.bridge.addMethod( + u"identitySet", + u".plugin", + in_sign=u"a{ss}s", + out_sign=u"", + method=self._setIdentity, + async=True, + ) def _getIdentity(self, jid_str, profile): jid_ = jid.JID(jid_str) @@ -70,23 +84,25 @@ # we first check roster roster_item = yield client.roster.getItem(jid_.userhostJID()) if roster_item is not None and roster_item.name: - id_data[u'nick'] = roster_item.name + id_data[u"nick"] = roster_item.name elif jid_.resource and self._v.isRoom(client, jid_): - id_data[u'nick'] = jid_.resource + id_data[u"nick"] = jid_.resource else: - # and finally then vcard + # and finally then vcard nick = yield self._v.getNick(client, jid_) - id_data[u'nick'] = nick if nick else jid_.user.capitalize() + id_data[u"nick"] = nick if nick else jid_.user.capitalize() try: - avatar_path = id_data[u'avatar'] = yield self._v.getAvatar(client, jid_, cache_only=False) + avatar_path = id_data[u"avatar"] = yield self._v.getAvatar( + client, jid_, cache_only=False + ) except exceptions.NotFound: pass else: if avatar_path: - id_data[u'avatar_basename'] = os.path.basename(avatar_path) + id_data[u"avatar_basename"] = os.path.basename(avatar_path) else: - del id_data[u'avatar'] + del id_data[u"avatar"] defer.returnValue(id_data) @@ -101,8 +117,7 @@ - nick: nickname the vCard will be updated """ - if id_data.keys() != [u'nick']: - raise NotImplementedError(u'Only nick can be updated for now') - if u'nick' in id_data: - return self._v.setNick(client, id_data[u'nick']) - + if id_data.keys() != [u"nick"]: + raise NotImplementedError(u"Only nick can be updated for now") + if u"nick" in id_data: + return self._v.setNick(client, id_data[u"nick"])
--- a/sat/plugins/plugin_misc_imap.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_imap.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.internet import protocol, defer from twisted.cred import portal, checkers, credentials @@ -41,12 +42,14 @@ C.PI_DEPENDENCIES: ["Maildir"], C.PI_MAIN: "IMAP_server", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Create an Imap server that you can use to read your "normal" type messages""") + C.PI_DESCRIPTION: _( + """Create an Imap server that you can use to read your "normal" type messages""" + ), } class IMAP_server(object): - #TODO: connect profile on mailbox request, once password is accepted + # TODO: connect profile on mailbox request, once password is accepted params = """ <params> @@ -62,7 +65,7 @@ log.info(_("Plugin Imap Server initialization")) self.host = host - #parameters + # parameters host.memory.updateParams(self.params) port = int(self.host.memory.getParamA("IMAP Port", "Mail Server")) @@ -76,7 +79,7 @@ implements(imap4.IMessage) def __init__(self, uid, flags, mess_fp): - log.debug('Message Init') + log.debug("Message Init") self.uid = uid self.flags = flags self.mess_fp = mess_fp @@ -85,22 +88,22 @@ def getUID(self): """Retrieve the unique identifier associated with this message. """ - log.debug('getUID (message)') + log.debug("getUID (message)") return self.uid def getFlags(self): """Retrieve the flags associated with this message. @return: The flags, represented as strings. """ - log.debug('getFlags') + log.debug("getFlags") return self.flags def getInternalDate(self): """Retrieve the date internally associated with this message. @return: An RFC822-formatted date string. """ - log.debug('getInternalDate') - return self.message['Date'] + log.debug("getInternalDate") + return self.message["Date"] def getHeaders(self, negate, *names): """Retrieve a group of message headers. @@ -109,32 +112,33 @@ should be omitted from the return value, rather than included. @return: A mapping of header field names to header field values """ - log.debug(u'getHeaders %s - %s' % (negate, names)) + log.debug(u"getHeaders %s - %s" % (negate, names)) final_dict = {} to_check = [name.lower() for name in names] for header in self.message.keys(): - if (negate and not header.lower() in to_check) or \ - (not negate and header.lower() in to_check): + if (negate and not header.lower() in to_check) or ( + not negate and header.lower() in to_check + ): final_dict[header] = self.message[header] return final_dict def getBodyFile(self): """Retrieve a file object containing only the body of this message. """ - log.debug('getBodyFile') + log.debug("getBodyFile") return StringIO(self.message.get_payload()) def getSize(self): """Retrieve the total size, in octets, of this message. """ - log.debug('getSize') + log.debug("getSize") self.mess_fp.seek(0, os.SEEK_END) return self.mess_fp.tell() def isMultipart(self): """Indicate whether this message has subparts. """ - log.debug('isMultipart') + log.debug("isMultipart") return False def getSubPart(self, part): @@ -142,7 +146,7 @@ @param part: The number of the part to retrieve, indexed from 0. @return: The specified sub-part. """ - log.debug('getSubPart') + log.debug("getSubPart") return TypeError @@ -152,10 +156,12 @@ def __init__(self, host, name, profile): self.host = host self.listeners = set() - log.debug(u'Mailbox init (%s)' % name) + log.debug(u"Mailbox init (%s)" % name) if name != "INBOX": raise imap4.MailboxException("Only INBOX is managed for the moment") - self.mailbox = self.host.plugins["Maildir"].accessMessageBox(name, self.messageNew, profile) + self.mailbox = self.host.plugins["Maildir"].accessMessageBox( + name, self.messageNew, profile + ) def messageNew(self): """Called when a new message is in the mailbox""" @@ -167,13 +173,13 @@ def getUIDValidity(self): """Return the unique validity identifier for this mailbox. """ - log.debug('getUIDValidity') + log.debug("getUIDValidity") return 0 def getUIDNext(self): """Return the likely UID for the next message added to this mailbox. """ - log.debug('getUIDNext') + log.debug("getUIDNext") return self.mailbox.getNextUid() def getUID(self, message): @@ -181,14 +187,14 @@ @param message: The message sequence number @return: The UID of the message. """ - log.debug(u'getUID (%i)' % message) - #return self.mailbox.getUid(message-1) #XXX: it seems that this method get uid and not message sequence number + log.debug(u"getUID (%i)" % message) + # return self.mailbox.getUid(message-1) #XXX: it seems that this method get uid and not message sequence number return message def getMessageCount(self): """Return the number of messages in this mailbox. """ - log.debug('getMessageCount') + log.debug("getMessageCount") ret = self.mailbox.getMessageCount() log.debug("count = %i" % ret) return ret @@ -196,26 +202,26 @@ def getRecentCount(self): """Return the number of messages with the 'Recent' flag. """ - log.debug('getRecentCount') - return len(self.mailbox.getMessageIdsWithFlag('\\Recent')) + log.debug("getRecentCount") + return len(self.mailbox.getMessageIdsWithFlag("\\Recent")) def getUnseenCount(self): """Return the number of messages with the 'Unseen' flag. """ - log.debug('getUnseenCount') - return self.getMessageCount() - len(self.mailbox.getMessageIdsWithFlag('\\SEEN')) + log.debug("getUnseenCount") + return self.getMessageCount() - len(self.mailbox.getMessageIdsWithFlag("\\SEEN")) def isWriteable(self): """Get the read/write status of the mailbox. @return: A true value if write permission is allowed, a false value otherwise. """ - log.debug('isWriteable') + log.debug("isWriteable") return True def destroy(self): """Called before this mailbox is deleted, permanently. """ - log.debug('destroy') + log.debug("destroy") def requestStatus(self, names): """Return status information about this mailbox. @@ -227,7 +233,7 @@ information up would be costly, a deferred whose callback will eventually be passed this dictionary is returned instead. """ - log.debug('requestStatus') + log.debug("requestStatus") return imap4.statusRequestHelper(self, names) def addListener(self, listener): @@ -237,7 +243,7 @@ @param listener: An object to add to the set of those which will be notified when the contents of this mailbox change. """ - log.debug(u'addListener %s' % listener) + log.debug(u"addListener %s" % listener) self.listeners.add(listener) def removeListener(self, listener): @@ -250,11 +256,11 @@ @raise ValueError: Raised when the given object is not a listener for this mailbox. """ - log.debug('removeListener') + log.debug("removeListener") if listener in self.listeners: self.listeners.remove(listener) else: - raise imap4.MailboxException('Trying to remove an unknown listener') + raise imap4.MailboxException("Trying to remove an unknown listener") def addMessage(self, message, flags=(), date=None): """Add the given message to this mailbox. @@ -265,7 +271,7 @@ id if the message is added successfully and whose errback is invoked otherwise. """ - log.debug('addMessage') + log.debug("addMessage") raise imap4.MailboxException("Client message addition not implemented yet") def expunge(self): @@ -273,7 +279,7 @@ @return: The list of message sequence numbers which were deleted, or a Deferred whose callback will be invoked with such a list. """ - log.debug('expunge') + log.debug("expunge") self.mailbox.removeDeleted() def fetch(self, messages, uid): @@ -282,16 +288,23 @@ about @param uid: If true, the IDs specified in the query are UIDs; """ - log.debug(u'fetch (%s, %s)' % (messages, uid)) + log.debug(u"fetch (%s, %s)" % (messages, uid)) if uid: messages.last = self.mailbox.getMaxUid() messages.getnext = self.mailbox.getNextExistingUid for mess_uid in messages: if mess_uid is None: - log.debug('stopping iteration') + log.debug("stopping iteration") raise StopIteration try: - yield (mess_uid, Message(mess_uid, self.mailbox.getFlagsUid(mess_uid), self.mailbox.getMessageUid(mess_uid))) + yield ( + mess_uid, + Message( + mess_uid, + self.mailbox.getFlagsUid(mess_uid), + self.mailbox.getMessageUid(mess_uid), + ), + ) except IndexError: continue else: @@ -299,7 +312,14 @@ for mess_idx in messages: if mess_idx > self.getMessageCount(): raise StopIteration - yield (mess_idx, Message(mess_idx, self.mailbox.getFlags(mess_idx), self.mailbox.getMessage(mess_idx - 1))) + yield ( + mess_idx, + Message( + mess_idx, + self.mailbox.getFlags(mess_idx), + self.mailbox.getMessage(mess_idx - 1), + ), + ) def store(self, messages, flags, mode, uid): """Set the flags of one or more messages. @@ -316,14 +336,16 @@ been performed, or a Deferred whose callback will be invoked with such a dict. """ - log.debug('store') + log.debug("store") flags = [flag.upper() for flag in flags] def updateFlags(getF, setF): ret = {} for mess_id in messages: - if (uid and mess_id is None) or (not uid and mess_id > self.getMessageCount()): + if (uid and mess_id is None) or ( + not uid and mess_id > self.getMessageCount() + ): break _flags = set(getF(mess_id) if mode else []) if mode == -1: @@ -348,7 +370,7 @@ ret = updateFlags(self.mailbox.getFlags, self.mailbox.setFlags) newFlags = {} for idx in ret: - #we have to convert idx to uid for the listeners + # we have to convert idx to uid for the listeners newFlags[self.mailbox.getUid(idx)] = ret[idx] for listener in self.listeners: listener.flagsChanged(newFlags) @@ -359,18 +381,24 @@ Flags with the \\ prefix are reserved for use as system flags. @return: A list of the flags that can be set on messages in this mailbox. """ - log.debug('getFlags') - return ['\\SEEN', '\\ANSWERED', '\\FLAGGED', '\\DELETED', '\\DRAFT'] # TODO: add '\\RECENT' + log.debug("getFlags") + return [ + "\\SEEN", + "\\ANSWERED", + "\\FLAGGED", + "\\DELETED", + "\\DRAFT", + ] # TODO: add '\\RECENT' def getHierarchicalDelimiter(self): """Get the character which delimits namespaces for in this mailbox. """ - log.debug('getHierarchicalDelimiter') - return '.' + log.debug("getHierarchicalDelimiter") + return "." class ImapSatAccount(imap4.MemoryAccount): - #implements(imap4.IAccount) + # implements(imap4.IAccount) def __init__(self, host, profile): log.debug("ImapAccount init") @@ -378,7 +406,7 @@ self.profile = profile imap4.MemoryAccount.__init__(self, profile) self.addMailbox("Inbox") # We only manage Inbox for the moment - log.debug('INBOX added') + log.debug("INBOX added") def _emptyMailbox(self, name, id): return SatMailbox(self.host, name, self.profile) @@ -391,8 +419,8 @@ self.host = host def requestAvatar(self, avatarID, mind, *interfaces): - log.debug('requestAvatar') - profile = avatarID.decode('utf-8') + log.debug("requestAvatar") + profile = avatarID.decode("utf-8") if imap4.IAccount not in interfaces: raise NotImplementedError return imap4.IAccount, ImapSatAccount(self.host, profile), lambda: None @@ -404,16 +432,19 @@ Check if the profile exists, and if the password is OK Return the profile as avatarId """ + implements(checkers.ICredentialsChecker) - credentialInterfaces = (credentials.IUsernamePassword, - credentials.IUsernameHashedPassword) + credentialInterfaces = ( + credentials.IUsernamePassword, + credentials.IUsernameHashedPassword, + ) def __init__(self, host): self.host = host def _cbPasswordMatch(self, matched, profile): if matched: - return profile.encode('utf-8') + return profile.encode("utf-8") else: return failure.Failure(cred_error.UnauthorizedLogin()) @@ -421,7 +452,9 @@ profiles = self.host.memory.getProfilesList() if not credentials.username in profiles: return defer.fail(cred_error.UnauthorizedLogin()) - d = self.host.memory.asyncGetParamA("Password", "Connection", profile_key=credentials.username) + d = self.host.memory.asyncGetParamA( + "Password", "Connection", profile_key=credentials.username + ) d.addCallback(lambda password: credentials.checkPassword(password)) d.addCallback(self._cbPasswordMatch, credentials.username) return d
--- a/sat/plugins/plugin_misc_ip.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_ip.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.tools import xml_tools from twisted.web import client as webclient @@ -34,10 +35,13 @@ from twisted.words.protocols.jabber.xmlstream import XMPPHandler from twisted.words.protocols.jabber.error import StanzaError import urlparse + try: import netifaces except ImportError: - log.warning(u"netifaces is not available, it help discovering IPs, you can install it on https://pypi.python.org/pypi/netifaces") + log.warning( + u"netifaces is not available, it help discovering IPs, you can install it on https://pypi.python.org/pypi/netifaces" + ) netifaces = None @@ -50,26 +54,29 @@ C.PI_RECOMMENDATIONS: ["NAT-PORT"], C.PI_MAIN: "IPPlugin", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""This plugin help to discover our external IP address.""") + C.PI_DESCRIPTION: _("""This plugin help to discover our external IP address."""), } # TODO: GET_IP_PAGE should be configurable in sat.conf -GET_IP_PAGE = "http://salut-a-toi.org/whereami/" # This page must only return external IP of the requester +GET_IP_PAGE = ( + "http://salut-a-toi.org/whereami/" +) # This page must only return external IP of the requester GET_IP_LABEL = D_(u"Allow external get IP") GET_IP_CATEGORY = "General" GET_IP_NAME = "allow_get_ip" GET_IP_CONFIRM_TITLE = D_(u"Confirm external site request") -GET_IP_CONFIRM = D_(u"""To facilitate data transfer, we need to contact a website. +GET_IP_CONFIRM = D_( + u"""To facilitate data transfer, we need to contact a website. A request will be done on {page} That means that administrators of {domain} can know that you use "{app_name}" and your IP Address. IP address is an identifier to locate you on Internet (similar to a phone number). Do you agree to do this request ? -""").format( - page = GET_IP_PAGE, - domain = urlparse.urlparse(GET_IP_PAGE).netloc, - app_name = C.APP_NAME) +""" +).format( + page=GET_IP_PAGE, domain=urlparse.urlparse(GET_IP_PAGE).netloc, app_name=C.APP_NAME +) NS_IP_CHECK = "urn:xmpp:sic:1" PARAMS = """ @@ -80,7 +87,9 @@ </category> </general> </params> - """.format(category=GET_IP_CATEGORY, name=GET_IP_NAME, label=GET_IP_LABEL) + """.format( + category=GET_IP_CATEGORY, name=GET_IP_NAME, label=GET_IP_LABEL +) class IPPlugin(object): @@ -94,7 +103,7 @@ # NAT-Port try: - self._nat = host.plugins['NAT-PORT'] + self._nat = host.plugins["NAT-PORT"] except KeyError: log.debug(u"NAT port plugin not available") self._nat = None @@ -118,16 +127,26 @@ if parameter is not set, a dialog is shown to use to get its confirmation, and parameted is set according to answer @return (defer.Deferred[bool]): True if external request is autorised """ - allow_get_ip = self.host.memory.params.getParamA(GET_IP_NAME, GET_IP_CATEGORY, use_default=False) + allow_get_ip = self.host.memory.params.getParamA( + GET_IP_NAME, GET_IP_CATEGORY, use_default=False + ) if allow_get_ip is None: # we don't have autorisation from user yet to use get_ip, we ask him def setParam(allowed): # FIXME: we need to use boolConst as setParam only manage str/unicode # need to be fixed when params will be refactored - self.host.memory.setParam(GET_IP_NAME, C.boolConst(allowed), GET_IP_CATEGORY) + self.host.memory.setParam( + GET_IP_NAME, C.boolConst(allowed), GET_IP_CATEGORY + ) return allowed - d = xml_tools.deferConfirm(self.host, _(GET_IP_CONFIRM), _(GET_IP_CONFIRM_TITLE), profile=client.profile) + + d = xml_tools.deferConfirm( + self.host, + _(GET_IP_CONFIRM), + _(GET_IP_CONFIRM_TITLE), + profile=client.profile, + ) d.addCallback(setParam) return d @@ -140,7 +159,7 @@ @param ip_addr(str): IP addresse @return (bool): True if addresse is acceptable """ - return not ip_addr.startswith('127.') + return not ip_addr.startswith("127.") def _insertFirst(self, addresses, ip_addr): """Insert ip_addr as first item in addresses @@ -164,9 +183,9 @@ url = urlparse.urlparse(ext_url) port = url.port if port is None: - if url.scheme=='http': + if url.scheme == "http": port = 80 - elif url.scheme=='https': + elif url.scheme == "https": port = 443 else: log.error(u"Unknown url scheme: {}".format(url.scheme)) @@ -175,6 +194,7 @@ log.error(u"Can't find url hostname for {}".format(GET_IP_PAGE)) point = endpoints.TCP4ClientEndpoint(reactor, url.hostname, port) + def gotConnection(p): local_ip = p.transport.getHost().host p.transport.loseConnection() @@ -196,7 +216,7 @@ if self._local_ip_cache is not None: defer.returnValue(self._local_ip_cache) addresses = [] - localhost = ['127.0.0.1'] + localhost = ["127.0.0.1"] # we first try our luck with netifaces if netifaces is not None: @@ -208,7 +228,7 @@ except KeyError: continue for data in inet_list: - addresse = data['addr'] + addresse = data["addr"] if self._filterAddresse(addresse): addresses.append(addresse) @@ -251,19 +271,20 @@ if self._external_ip_cache is not None: defer.returnValue(self._external_ip_cache) - # we first try with XEP-0279 ip_check = yield self.host.hasFeature(client, NS_IP_CHECK) if ip_check: log.debug(u"Server IP Check available, we use it to retrieve our IP") iq_elt = client.IQ("get") - iq_elt.addElement((NS_IP_CHECK, 'address')) + iq_elt.addElement((NS_IP_CHECK, "address")) try: result_elt = yield iq_elt.send() - address_elt = result_elt.elements(NS_IP_CHECK, 'address').next() - ip_elt = address_elt.elements(NS_IP_CHECK,'ip').next() + address_elt = result_elt.elements(NS_IP_CHECK, "address").next() + ip_elt = address_elt.elements(NS_IP_CHECK, "ip").next() except StopIteration: - log.warning(u"Server returned invalid result on XEP-0279 request, we ignore it") + log.warning( + u"Server returned invalid result on XEP-0279 request, we ignore it" + ) except StanzaError as e: log.warning(u"error while requesting ip to server: {}".format(e)) else: @@ -289,7 +310,11 @@ log.warning(u"Can't access Domain Name System") ip = None except web_error.Error as e: - log.warning(u"Error while retrieving IP on {url}: {message}".format(url=GET_IP_PAGE, message=e)) + log.warning( + u"Error while retrieving IP on {url}: {message}".format( + url=GET_IP_PAGE, message=e + ) + ) ip = None else: self._external_ip_cache = ip @@ -299,8 +324,8 @@ class IPPlugin_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_IP_CHECK)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_misc_nat-port.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_nat-port.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from twisted.internet import threads @@ -30,7 +31,9 @@ try: import miniupnpc except ImportError: - raise exceptions.MissingModule(u"Missing module MiniUPnPc, please download/install it (and its Python binding) at http://miniupnp.free.fr/ (or use pip install miniupnpc)") + raise exceptions.MissingModule( + u"Missing module MiniUPnPc, please download/install it (and its Python binding) at http://miniupnp.free.fr/ (or use pip install miniupnpc)" + ) PLUGIN_INFO = { @@ -42,8 +45,10 @@ C.PI_DESCRIPTION: _("""Automatic NAT port mapping using UPnP"""), } -STARTING_PORT = 6000 # starting point to automatically find a port -DEFAULT_DESC = u'SaT port mapping' # we don't use "à" here as some bugged NAT don't manage charset correctly +STARTING_PORT = 6000 # starting point to automatically find a port +DEFAULT_DESC = ( + u"SaT port mapping" +) # we don't use "à" here as some bugged NAT don't manage charset correctly class MappingError(Exception): @@ -58,11 +63,11 @@ self.host = host self._external_ip = None self._initialised = defer.Deferred() - self._upnp = miniupnpc.UPnP() # will be None if no device is available - self._upnp.discoverdelay=200 - self._mutex = threading.Lock() # used to protect access to self._upnp - self._starting_port_cache = None # used to cache the first available port - self._to_unmap = [] # list of tuples (ext_port, protocol) of ports to unmap on unload + self._upnp = miniupnpc.UPnP() # will be None if no device is available + self._upnp.discoverdelay = 200 + self._mutex = threading.Lock() # used to protect access to self._upnp + self._starting_port_cache = None # used to cache the first available port + self._to_unmap = [] # list of tuples (ext_port, protocol) of ports to unmap on unload discover_d = threads.deferToThread(self._discover) discover_d.chainDeferred(self._initialised) self._initialised.addErrback(self._init_failed) @@ -99,12 +104,14 @@ @param local(bool): True to get external IP address, False to get local network one @return (None, str): found IP address, or None of something got wrong """ + def getIP(dummy): if self._upnp is None: return None # lanaddr can be the empty string if not found, # we need to return None in this case return (self._upnp.lanaddr or None) if local else self._external_ip + return self._initialised.addCallback(getIP) def _unmapPortsBlocking(self): @@ -115,11 +122,17 @@ log.info(u"Unmapping port {}".format(port)) unmapping = self._upnp.deleteportmapping( # the last parameter is remoteHost, we don't use it - port, protocol, '') + port, + protocol, + "", + ) if not unmapping: - log.error(u"Can't unmap port {port} ({protocol})".format( - port=port, protocol=protocol)) + log.error( + u"Can't unmap port {port} ({protocol})".format( + port=port, protocol=protocol + ) + ) del self._to_unmap[:] finally: self._mutex.release() @@ -143,8 +156,8 @@ ext_port = STARTING_PORT if starting_port is None else starting_port ret = self._upnp.getspecificportmapping(ext_port, protocol) while ret != None and ext_port < 65536: - ext_port += 1 - ret = self._upnp.getspecificportmapping(ext_port, protocol) + ext_port += 1 + ret = self._upnp.getspecificportmapping(ext_port, protocol) if starting_port is None: # XXX: we cache the first successfuly found external port # to avoid testing again the first series the next time @@ -153,7 +166,13 @@ try: mapping = self._upnp.addportmapping( # the last parameter is remoteHost, we don't use it - ext_port, protocol, self._upnp.lanaddr, int_port, desc, '') + ext_port, + protocol, + self._upnp.lanaddr, + int_port, + desc, + "", + ) except Exception as e: log.error(_(u"addportmapping error: {msg}").format(msg=e)) raise failure.Failure(MappingError()) @@ -167,7 +186,7 @@ return ext_port - def mapPort(self, int_port, ext_port=None, protocol='TCP', desc=DEFAULT_DESC): + def mapPort(self, int_port, ext_port=None, protocol="TCP", desc=DEFAULT_DESC): """Add a port mapping @param int_port(int): internal port to use @@ -179,19 +198,25 @@ """ if self._upnp is None: return defer.succeed(None) + def mappingCb(ext_port): - log.info(u"{protocol} mapping from {int_port} to {ext_port} successful".format( - protocol = protocol, - int_port = int_port, - ext_port = ext_port, - )) + log.info( + u"{protocol} mapping from {int_port} to {ext_port} successful".format( + protocol=protocol, int_port=int_port, ext_port=ext_port + ) + ) return ext_port + def mappingEb(failure_): failure_.trap(MappingError) log.warning(u"Can't map internal {int_port}".format(int_port=int_port)) + def mappingUnknownEb(failure_): log.error(_(u"error while trying to map ports: {msg}").format(msg=failure_)) - d = threads.deferToThread(self._mapPortBlocking, int_port, ext_port, protocol, desc) + + d = threads.deferToThread( + self._mapPortBlocking, int_port, ext_port, protocol, desc + ) d.addCallbacks(mappingCb, mappingEb) d.addErrback(mappingUnknownEb) return d
--- a/sat/plugins/plugin_misc_quiz.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_quiz.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.xish import domish from twisted.internet import reactor @@ -27,8 +28,8 @@ from time import time -NS_QG = 'http://www.goffi.org/protocol/quiz' -QG_TAG = 'quiz' +NS_QG = "http://www.goffi.org/protocol/quiz" +QG_TAG = "quiz" PLUGIN_INFO = { C.PI_NAME: "Quiz game plugin", @@ -38,78 +39,147 @@ C.PI_DEPENDENCIES: ["XEP-0045", "XEP-0249", "ROOM-GAME"], C.PI_MAIN: "Quiz", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Quiz game""") + C.PI_DESCRIPTION: _("""Implementation of Quiz game"""), } class Quiz(object): - def inheritFromRoomGame(self, host): global RoomGame RoomGame = host.plugins["ROOM-GAME"].__class__ - self.__class__ = type(self.__class__.__name__, (self.__class__, RoomGame, object), {}) + self.__class__ = type( + self.__class__.__name__, (self.__class__, RoomGame, object), {} + ) def __init__(self, host): log.info(_("Plugin Quiz initialization")) self.inheritFromRoomGame(host) - RoomGame._init_(self, host, PLUGIN_INFO, (NS_QG, QG_TAG), game_init={'stage': None}, player_init={'score': 0}) - host.bridge.addMethod("quizGameLaunch", ".plugin", in_sign='asss', out_sign='', method=self._prepareRoom) # args: players, room_jid, profile - host.bridge.addMethod("quizGameCreate", ".plugin", in_sign='sass', out_sign='', method=self._createGame) # args: room_jid, players, profile - host.bridge.addMethod("quizGameReady", ".plugin", in_sign='sss', out_sign='', method=self._playerReady) # args: player, referee, profile - host.bridge.addMethod("quizGameAnswer", ".plugin", in_sign='ssss', out_sign='', method=self.playerAnswer) - host.bridge.addSignal("quizGameStarted", ".plugin", signature='ssass') # args: room_jid, referee, players, profile - host.bridge.addSignal("quizGameNew", ".plugin", - signature='sa{ss}s', - doc={'summary': 'Start a new game', - 'param_0': "room_jid: jid of game's room", - 'param_1': "game_data: data of the game", - 'param_2': '%(doc_profile)s'}) - host.bridge.addSignal("quizGameQuestion", ".plugin", - signature='sssis', - doc={'summary': "Send the current question", - 'param_0': "room_jid: jid of game's room", - 'param_1': "question_id: question id", - 'param_2': "question: question to ask", - 'param_3': "timer: timer", - 'param_4': '%(doc_profile)s'}) - host.bridge.addSignal("quizGamePlayerBuzzed", ".plugin", - signature='ssbs', - doc={'summary': "A player just pressed the buzzer", - 'param_0': "room_jid: jid of game's room", - 'param_1': "player: player who pushed the buzzer", - 'param_2': "pause: should the game be paused ?", - 'param_3': '%(doc_profile)s'}) - host.bridge.addSignal("quizGamePlayerSays", ".plugin", - signature='sssis', - doc={'summary': "A player just pressed the buzzer", - 'param_0': "room_jid: jid of game's room", - 'param_1': "player: player who pushed the buzzer", - 'param_2': "text: what the player say", - 'param_3': "delay: how long, in seconds, the text must appear", - 'param_4': '%(doc_profile)s'}) - host.bridge.addSignal("quizGameAnswerResult", ".plugin", - signature='ssba{si}s', - doc={'summary': "Result of the just given answer", - 'param_0': "room_jid: jid of game's room", - 'param_1': "player: player who gave the answer", - 'param_2': "good_answer: True if the answer is right", - 'param_3': "score: dict of score with player as key", - 'param_4': '%(doc_profile)s'}) - host.bridge.addSignal("quizGameTimerExpired", ".plugin", - signature='ss', - doc={'summary': "Nobody answered the question in time", - 'param_0': "room_jid: jid of game's room", - 'param_1': '%(doc_profile)s'}) - host.bridge.addSignal("quizGameTimerRestarted", ".plugin", - signature='sis', - doc={'summary': "Nobody answered the question in time", - 'param_0': "room_jid: jid of game's room", - 'param_1': "time_left: time left before timer expiration", - 'param_2': '%(doc_profile)s'}) + RoomGame._init_( + self, + host, + PLUGIN_INFO, + (NS_QG, QG_TAG), + game_init={"stage": None}, + player_init={"score": 0}, + ) + host.bridge.addMethod( + "quizGameLaunch", + ".plugin", + in_sign="asss", + out_sign="", + method=self._prepareRoom, + ) # args: players, room_jid, profile + host.bridge.addMethod( + "quizGameCreate", + ".plugin", + in_sign="sass", + out_sign="", + method=self._createGame, + ) # args: room_jid, players, profile + host.bridge.addMethod( + "quizGameReady", + ".plugin", + in_sign="sss", + out_sign="", + method=self._playerReady, + ) # args: player, referee, profile + host.bridge.addMethod( + "quizGameAnswer", + ".plugin", + in_sign="ssss", + out_sign="", + method=self.playerAnswer, + ) + host.bridge.addSignal( + "quizGameStarted", ".plugin", signature="ssass" + ) # args: room_jid, referee, players, profile + host.bridge.addSignal( + "quizGameNew", + ".plugin", + signature="sa{ss}s", + doc={ + "summary": "Start a new game", + "param_0": "room_jid: jid of game's room", + "param_1": "game_data: data of the game", + "param_2": "%(doc_profile)s", + }, + ) + host.bridge.addSignal( + "quizGameQuestion", + ".plugin", + signature="sssis", + doc={ + "summary": "Send the current question", + "param_0": "room_jid: jid of game's room", + "param_1": "question_id: question id", + "param_2": "question: question to ask", + "param_3": "timer: timer", + "param_4": "%(doc_profile)s", + }, + ) + host.bridge.addSignal( + "quizGamePlayerBuzzed", + ".plugin", + signature="ssbs", + doc={ + "summary": "A player just pressed the buzzer", + "param_0": "room_jid: jid of game's room", + "param_1": "player: player who pushed the buzzer", + "param_2": "pause: should the game be paused ?", + "param_3": "%(doc_profile)s", + }, + ) + host.bridge.addSignal( + "quizGamePlayerSays", + ".plugin", + signature="sssis", + doc={ + "summary": "A player just pressed the buzzer", + "param_0": "room_jid: jid of game's room", + "param_1": "player: player who pushed the buzzer", + "param_2": "text: what the player say", + "param_3": "delay: how long, in seconds, the text must appear", + "param_4": "%(doc_profile)s", + }, + ) + host.bridge.addSignal( + "quizGameAnswerResult", + ".plugin", + signature="ssba{si}s", + doc={ + "summary": "Result of the just given answer", + "param_0": "room_jid: jid of game's room", + "param_1": "player: player who gave the answer", + "param_2": "good_answer: True if the answer is right", + "param_3": "score: dict of score with player as key", + "param_4": "%(doc_profile)s", + }, + ) + host.bridge.addSignal( + "quizGameTimerExpired", + ".plugin", + signature="ss", + doc={ + "summary": "Nobody answered the question in time", + "param_0": "room_jid: jid of game's room", + "param_1": "%(doc_profile)s", + }, + ) + host.bridge.addSignal( + "quizGameTimerRestarted", + ".plugin", + signature="sis", + doc={ + "summary": "Nobody answered the question in time", + "param_0": "room_jid: jid of game's room", + "param_1": "time_left: time left before timer expiration", + "param_2": "%(doc_profile)s", + }, + ) def __game_data_to_xml(self, game_data): """Convert a game data dict to domish element""" - game_data_elt = domish.Element((None, 'game_data')) + game_data_elt = domish.Element((None, "game_data")) for data in game_data: data_elt = domish.Element((None, data)) data_elt.addContent(game_data[data]) @@ -129,57 +199,68 @@ @return: (player, good_answer, score)""" score = {} for score_elt in answer_result_elt.elements(): - score[score_elt['player']] = int(score_elt['score']) - return (answer_result_elt['player'], answer_result_elt['good_answer'] == str(True), score) + score[score_elt["player"]] = int(score_elt["score"]) + return ( + answer_result_elt["player"], + answer_result_elt["good_answer"] == str(True), + score, + ) def __answer_result(self, player_answering, good_answer, game_data): """Convert a domish an answer_result element @param player_answering: player who gave the answer @param good_answer: True is the answer is right @param game_data: data of the game""" - players_data = game_data['players_data'] + players_data = game_data["players_data"] score = {} - for player in game_data['players']: - score[player] = players_data[player]['score'] + for player in game_data["players"]: + score[player] = players_data[player]["score"] - answer_result_elt = domish.Element((None, 'answer_result')) - answer_result_elt['player'] = player_answering - answer_result_elt['good_answer'] = str(good_answer) + answer_result_elt = domish.Element((None, "answer_result")) + answer_result_elt["player"] = player_answering + answer_result_elt["good_answer"] = str(good_answer) for player in score: score_elt = domish.Element((None, "score")) - score_elt['player'] = player - score_elt['score'] = str(score[player]) + score_elt["player"] = player + score_elt["score"] = str(score[player]) answer_result_elt.addChild(score_elt) return answer_result_elt def __ask_question(self, question_id, question, timer): """Create a element for asking a question""" - question_elt = domish.Element((None, 'question')) - question_elt['id'] = question_id - question_elt['timer'] = str(timer) + question_elt = domish.Element((None, "question")) + question_elt["id"] = question_id + question_elt["timer"] = str(timer) question_elt.addContent(question) return question_elt def __start_play(self, room_jid, game_data, profile): """Start the game (tell to the first player after dealer to play""" client = self.host.getClient(profile) - game_data['stage'] = "play" - next_player_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(game_data['players']) # the player after the dealer start - game_data['first_player'] = next_player = game_data['players'][next_player_idx] + game_data["stage"] = "play" + next_player_idx = game_data["current_player"] = ( + game_data["init_player"] + 1 + ) % len( + game_data["players"] + ) # the player after the dealer start + game_data["first_player"] = next_player = game_data["players"][next_player_idx] to_jid = jid.JID(room_jid.userhost() + "/" + next_player) mess = self.createGameElt(to_jid) - mess.firstChildElement().addElement('your_turn') + mess.firstChildElement().addElement("your_turn") client.send(mess) def playerAnswer(self, player, referee, answer, profile_key=C.PROF_KEY_NONE): """Called when a player give an answer""" client = self.host.getClient(profile_key) - log.debug(u'new player answer (%(profile)s): %(answer)s' % {'profile': client.profile, 'answer': answer}) + log.debug( + u"new player answer (%(profile)s): %(answer)s" + % {"profile": client.profile, "answer": answer} + ) mess = self.createGameElt(jid.JID(referee)) - answer_elt = mess.firstChildElement().addElement('player_answer') - answer_elt['player'] = player + answer_elt = mess.firstChildElement().addElement("player_answer") + answer_elt["player"] = player answer_elt.addContent(answer) client.send(mess) @@ -187,9 +268,9 @@ """Called when nobody answered the question in time""" client = self.host.getClient(profile) game_data = self.games[room_jid] - game_data['stage'] = 'expired' + game_data["stage"] = "expired" mess = self.createGameElt(room_jid) - mess.firstChildElement().addElement('timer_expired') + mess.firstChildElement().addElement("timer_expired") client.send(mess) reactor.callLater(4, self.askQuestion, room_jid, client.profile) @@ -197,49 +278,59 @@ """Stop the timer and save the time left""" game_data = self.games[room_jid] left = max(0, game_data["timer"].getTime() - time()) - game_data['timer'].cancel() - game_data['time_left'] = int(left) - game_data['previous_stage'] = game_data['stage'] - game_data['stage'] = "paused" + game_data["timer"].cancel() + game_data["time_left"] = int(left) + game_data["previous_stage"] = game_data["stage"] + game_data["stage"] = "paused" def restartTimer(self, room_jid, profile): """Restart a timer with the saved time""" client = self.host.getClient(profile) game_data = self.games[room_jid] - assert game_data['time_left'] is not None + assert game_data["time_left"] is not None mess = self.createGameElt(room_jid) - mess.firstChildElement().addElement('timer_restarted') - jabber_client.restarted_elt["time_left"] = str(game_data['time_left']) + mess.firstChildElement().addElement("timer_restarted") + jabber_client.restarted_elt["time_left"] = str(game_data["time_left"]) client.send(mess) - game_data["timer"] = reactor.callLater(game_data['time_left'], self.timerExpired, room_jid, profile) + game_data["timer"] = reactor.callLater( + game_data["time_left"], self.timerExpired, room_jid, profile + ) game_data["time_left"] = None - game_data['stage'] = game_data['previous_stage'] - del game_data['previous_stage'] + game_data["stage"] = game_data["previous_stage"] + del game_data["previous_stage"] def askQuestion(self, room_jid, profile): """Ask a new question""" client = self.host.getClient(profile) game_data = self.games[room_jid] - game_data['stage'] = "question" - game_data['question_id'] = "1" + game_data["stage"] = "question" + game_data["question_id"] = "1" timer = 30 mess = self.createGameElt(room_jid) - mess.firstChildElement().addChild(self.__ask_question(game_data['question_id'], u"Quel est l'âge du capitaine ?", timer)) + mess.firstChildElement().addChild( + self.__ask_question( + game_data["question_id"], u"Quel est l'âge du capitaine ?", timer + ) + ) client.send(mess) - game_data["timer"] = reactor.callLater(timer, self.timerExpired, room_jid, profile) + game_data["timer"] = reactor.callLater( + timer, self.timerExpired, room_jid, profile + ) game_data["time_left"] = None def checkAnswer(self, room_jid, player, answer, profile): """Check if the answer given is right""" client = self.host.getClient(profile) game_data = self.games[room_jid] - players_data = game_data['players_data'] - good_answer = game_data['question_id'] == "1" and answer == "42" - players_data[player]['score'] += 1 if good_answer else -1 - players_data[player]['score'] = min(9, max(0, players_data[player]['score'])) + players_data = game_data["players_data"] + good_answer = game_data["question_id"] == "1" and answer == "42" + players_data[player]["score"] += 1 if good_answer else -1 + players_data[player]["score"] = min(9, max(0, players_data[player]["score"])) mess = self.createGameElt(room_jid) - mess.firstChildElement().addChild(self.__answer_result(player, good_answer, game_data)) + mess.firstChildElement().addChild( + self.__answer_result(player, good_answer, game_data) + ) client.send(mess) if good_answer: @@ -249,82 +340,117 @@ def newGame(self, room_jid, profile): """Launch a new round""" - common_data = {'game_score': 0} - new_game_data = {"instructions": _(u"""Bienvenue dans cette partie rapide de quizz, le premier à atteindre le score de 9 remporte le jeu + common_data = {"game_score": 0} + new_game_data = { + "instructions": _( + u"""Bienvenue dans cette partie rapide de quizz, le premier à atteindre le score de 9 remporte le jeu -Attention, tu es prêt ?""")} +Attention, tu es prêt ?""" + ) + } msg_elts = self.__game_data_to_xml(new_game_data) RoomGame.newRound(self, room_jid, (common_data, msg_elts), profile) reactor.callLater(10, self.askQuestion, room_jid, profile) def room_game_cmd(self, mess_elt, profile): client = self.host.getClient(profile) - from_jid = jid.JID(mess_elt['from']) + from_jid = jid.JID(mess_elt["from"]) room_jid = jid.JID(from_jid.userhost()) game_elt = mess_elt.firstChildElement() game_data = self.games[room_jid] - # if 'players_data' in game_data: - # players_data = game_data['players_data'] + # if 'players_data' in game_data: + # players_data = game_data['players_data'] for elt in game_elt.elements(): - if elt.name == 'started': # new game created + if elt.name == "started": # new game created players = [] for player in elt.elements(): players.append(unicode(player)) - self.host.bridge.quizGameStarted(room_jid.userhost(), from_jid.full(), players, profile) + self.host.bridge.quizGameStarted( + room_jid.userhost(), from_jid.full(), players, profile + ) - elif elt.name == 'player_ready': # ready to play - player = elt['player'] - status = self.games[room_jid]['status'] - nb_players = len(self.games[room_jid]['players']) - status[player] = 'ready' - log.debug(_(u'Player %(player)s is ready to start [status: %(status)s]') % {'player': player, 'status': status}) - if status.values().count('ready') == nb_players: # everybody is ready, we can start the game + elif elt.name == "player_ready": # ready to play + player = elt["player"] + status = self.games[room_jid]["status"] + nb_players = len(self.games[room_jid]["players"]) + status[player] = "ready" + log.debug( + _(u"Player %(player)s is ready to start [status: %(status)s]") + % {"player": player, "status": status} + ) + if ( + status.values().count("ready") == nb_players + ): # everybody is ready, we can start the game self.newGame(room_jid, profile) - elif elt.name == 'game_data': - self.host.bridge.quizGameNew(room_jid.userhost(), self.__xml_to_game_data(elt), profile) + elif elt.name == "game_data": + self.host.bridge.quizGameNew( + room_jid.userhost(), self.__xml_to_game_data(elt), profile + ) - elif elt.name == 'question': # A question is asked - self.host.bridge.quizGameQuestion(room_jid.userhost(), elt["id"], unicode(elt), int(elt["timer"]), profile) + elif elt.name == "question": # A question is asked + self.host.bridge.quizGameQuestion( + room_jid.userhost(), + elt["id"], + unicode(elt), + int(elt["timer"]), + profile, + ) - elif elt.name == 'player_answer': - player = elt['player'] - pause = game_data['stage'] == 'question' # we pause the game only if we are have a question at the moment + elif elt.name == "player_answer": + player = elt["player"] + pause = ( + game_data["stage"] == "question" + ) # we pause the game only if we are have a question at the moment # we first send a buzzer message mess = self.createGameElt(room_jid) - buzzer_elt = mess.firstChildElement().addElement('player_buzzed') - buzzer_elt['player'] = player - buzzer_elt['pause'] = str(pause) + buzzer_elt = mess.firstChildElement().addElement("player_buzzed") + buzzer_elt["player"] = player + buzzer_elt["pause"] = str(pause) client.send(mess) if pause: self.pauseTimer(room_jid) # and we send the player answer mess = self.createGameElt(room_jid) _answer = unicode(elt) - say_elt = mess.firstChildElement().addElement('player_says') - say_elt['player'] = player + say_elt = mess.firstChildElement().addElement("player_says") + say_elt["player"] = player say_elt.addContent(_answer) - say_elt['delay'] = "3" + say_elt["delay"] = "3" reactor.callLater(2, client.send, mess) - reactor.callLater(6, self.checkAnswer, room_jid, player, _answer, profile=profile) + reactor.callLater( + 6, self.checkAnswer, room_jid, player, _answer, profile=profile + ) + + elif elt.name == "player_buzzed": + self.host.bridge.quizGamePlayerBuzzed( + room_jid.userhost(), elt["player"], elt["pause"] == str(True), profile + ) - elif elt.name == 'player_buzzed': - self.host.bridge.quizGamePlayerBuzzed(room_jid.userhost(), elt["player"], elt['pause'] == str(True), profile) - - elif elt.name == 'player_says': - self.host.bridge.quizGamePlayerSays(room_jid.userhost(), elt["player"], unicode(elt), int(elt["delay"]), profile) + elif elt.name == "player_says": + self.host.bridge.quizGamePlayerSays( + room_jid.userhost(), + elt["player"], + unicode(elt), + int(elt["delay"]), + profile, + ) - elif elt.name == 'answer_result': + elif elt.name == "answer_result": player, good_answer, score = self.__answer_result_to_signal_args(elt) - self.host.bridge.quizGameAnswerResult(room_jid.userhost(), player, good_answer, score, profile) + self.host.bridge.quizGameAnswerResult( + room_jid.userhost(), player, good_answer, score, profile + ) - elif elt.name == 'timer_expired': + elif elt.name == "timer_expired": self.host.bridge.quizGameTimerExpired(room_jid.userhost(), profile) - elif elt.name == 'timer_restarted': - self.host.bridge.quizGameTimerRestarted(room_jid.userhost(), int(elt['time_left']), profile) + elif elt.name == "timer_restarted": + self.host.bridge.quizGameTimerRestarted( + room_jid.userhost(), int(elt["time_left"]), profile + ) else: - log.error(_(u'Unmanaged game element: %s') % elt.name) + log.error(_(u"Unmanaged game element: %s") % elt.name)
--- a/sat/plugins/plugin_misc_radiocol.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_radiocol.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.xish import domish from twisted.internet import reactor @@ -30,17 +31,20 @@ import copy import time from os import unlink + try: from mutagen.oggvorbis import OggVorbis, OggVorbisHeaderError from mutagen.mp3 import MP3, HeaderNotFoundError from mutagen.easyid3 import EasyID3 from mutagen.id3 import ID3NoHeaderError except ImportError: - raise exceptions.MissingModule(u"Missing module Mutagen, please download/install from https://bitbucket.org/lazka/mutagen") + raise exceptions.MissingModule( + u"Missing module Mutagen, please download/install from https://bitbucket.org/lazka/mutagen" + ) -NC_RADIOCOL = 'http://www.goffi.org/protocol/radiocol' -RADIOC_TAG = 'radiocol' +NC_RADIOCOL = "http://www.goffi.org/protocol/radiocol" +RADIOC_TAG = "radiocol" PLUGIN_INFO = { C.PI_NAME: "Radio collective plugin", @@ -50,7 +54,7 @@ C.PI_DEPENDENCIES: ["XEP-0045", "XEP-0249", "ROOM-GAME"], C.PI_MAIN: "Radiocol", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of radio collective""") + C.PI_DESCRIPTION: _("""Implementation of radio collective"""), } @@ -61,34 +65,80 @@ class Radiocol(object): - def inheritFromRoomGame(self, host): global RoomGame RoomGame = host.plugins["ROOM-GAME"].__class__ - self.__class__ = type(self.__class__.__name__, (self.__class__, RoomGame, object), {}) + self.__class__ = type( + self.__class__.__name__, (self.__class__, RoomGame, object), {} + ) def __init__(self, host): log.info(_("Radio collective initialization")) self.inheritFromRoomGame(host) - RoomGame._init_(self, host, PLUGIN_INFO, (NC_RADIOCOL, RADIOC_TAG), - game_init={'queue': [], 'upload': True, 'playing': None, 'playing_time': 0, 'to_delete': {}}) + RoomGame._init_( + self, + host, + PLUGIN_INFO, + (NC_RADIOCOL, RADIOC_TAG), + game_init={ + "queue": [], + "upload": True, + "playing": None, + "playing_time": 0, + "to_delete": {}, + }, + ) self.host = host - host.bridge.addMethod("radiocolLaunch", ".plugin", in_sign='asss', out_sign='', method=self._prepareRoom, async=True) - host.bridge.addMethod("radiocolCreate", ".plugin", in_sign='sass', out_sign='', method=self._createGame) - host.bridge.addMethod("radiocolSongAdded", ".plugin", in_sign='sss', out_sign='', method=self._radiocolSongAdded, async=True) - host.bridge.addSignal("radiocolPlayers", ".plugin", signature='ssass') # room_jid, referee, players, profile - host.bridge.addSignal("radiocolStarted", ".plugin", signature='ssasais') # room_jid, referee, players, [QUEUE_TO_START, QUEUE_LIMIT], profile - host.bridge.addSignal("radiocolSongRejected", ".plugin", signature='sss') # room_jid, reason, profile - host.bridge.addSignal("radiocolPreload", ".plugin", signature='ssssssss') # room_jid, timestamp, filename, title, artist, album, profile - host.bridge.addSignal("radiocolPlay", ".plugin", signature='sss') # room_jid, filename, profile - host.bridge.addSignal("radiocolNoUpload", ".plugin", signature='ss') # room_jid, profile - host.bridge.addSignal("radiocolUploadOk", ".plugin", signature='ss') # room_jid, profile + host.bridge.addMethod( + "radiocolLaunch", + ".plugin", + in_sign="asss", + out_sign="", + method=self._prepareRoom, + async=True, + ) + host.bridge.addMethod( + "radiocolCreate", + ".plugin", + in_sign="sass", + out_sign="", + method=self._createGame, + ) + host.bridge.addMethod( + "radiocolSongAdded", + ".plugin", + in_sign="sss", + out_sign="", + method=self._radiocolSongAdded, + async=True, + ) + host.bridge.addSignal( + "radiocolPlayers", ".plugin", signature="ssass" + ) # room_jid, referee, players, profile + host.bridge.addSignal( + "radiocolStarted", ".plugin", signature="ssasais" + ) # room_jid, referee, players, [QUEUE_TO_START, QUEUE_LIMIT], profile + host.bridge.addSignal( + "radiocolSongRejected", ".plugin", signature="sss" + ) # room_jid, reason, profile + host.bridge.addSignal( + "radiocolPreload", ".plugin", signature="ssssssss" + ) # room_jid, timestamp, filename, title, artist, album, profile + host.bridge.addSignal( + "radiocolPlay", ".plugin", signature="sss" + ) # room_jid, filename, profile + host.bridge.addSignal( + "radiocolNoUpload", ".plugin", signature="ss" + ) # room_jid, profile + host.bridge.addSignal( + "radiocolUploadOk", ".plugin", signature="ss" + ) # room_jid, profile def __create_preload_elt(self, sender, song_added_elt): preload_elt = copy.deepcopy(song_added_elt) - preload_elt.name = 'preload' - preload_elt['sender'] = sender - preload_elt['timestamp'] = str(time.time()) + preload_elt.name = "preload" + preload_elt["sender"] = sender + preload_elt["timestamp"] = str(time.time()) # attributes filename, title, artist, album, length have been copied # XXX: the frontend should know the temporary directory where file is put return preload_elt @@ -108,7 +158,7 @@ # Here we cheat because we know we are on the same host, and we don't # check data. Referee will have to parse the song himself to check it try: - if song_path.lower().endswith('.mp3'): + if song_path.lower().endswith(".mp3"): actual_song = MP3(song_path) try: song = EasyID3(song_path) @@ -116,6 +166,7 @@ class Info(object): def __init__(self, length): self.length = length + song.info = Info(actual_song.info.length) except ID3NoHeaderError: song = actual_song @@ -124,17 +175,30 @@ except (OggVorbisHeaderError, HeaderNotFoundError): # this file is not ogg vorbis nor mp3, we reject it self.deleteFile(song_path) # FIXME: same host trick (see note above) - return defer.fail(exceptions.DataError(D_("The uploaded file has been rejected, only Ogg Vorbis and MP3 songs are accepted."))) + return defer.fail( + exceptions.DataError( + D_( + "The uploaded file has been rejected, only Ogg Vorbis and MP3 songs are accepted." + ) + ) + ) - attrs = {'filename': os.path.basename(song_path), - 'title': song.get("title", ["Unknown"])[0], - 'artist': song.get("artist", ["Unknown"])[0], - 'album': song.get("album", ["Unknown"])[0], - 'length': str(song.info.length) - } - radio_data = self.games[referee.userhostJID()] # FIXME: referee comes from Libervia's client side, it's unsecure - radio_data['to_delete'][attrs['filename']] = song_path # FIXME: works only because of the same host trick, see the note under the docstring - return self.send(referee, ('', 'song_added'), attrs, profile=profile) + attrs = { + "filename": os.path.basename(song_path), + "title": song.get("title", ["Unknown"])[0], + "artist": song.get("artist", ["Unknown"])[0], + "album": song.get("album", ["Unknown"])[0], + "length": str(song.info.length), + } + radio_data = self.games[ + referee.userhostJID() + ] # FIXME: referee comes from Libervia's client side, it's unsecure + radio_data["to_delete"][ + attrs["filename"] + ] = ( + song_path + ) # FIXME: works only because of the same host trick, see the note under the docstring + return self.send(referee, ("", "song_added"), attrs, profile=profile) def playNext(self, room_jid, profile): """"Play next song in queue if exists, and put a timer @@ -142,31 +206,33 @@ # TODO: songs need to be erased once played or found invalids # ==> unlink done the Q&D way with the same host trick (see above) radio_data = self.games[room_jid] - if len(radio_data['players']) == 0: - log.debug(_(u'No more participants in the radiocol: cleaning data')) - radio_data['queue'] = [] - for filename in radio_data['to_delete']: + if len(radio_data["players"]) == 0: + log.debug(_(u"No more participants in the radiocol: cleaning data")) + radio_data["queue"] = [] + for filename in radio_data["to_delete"]: self.deleteFile(filename, radio_data) - radio_data['to_delete'] = {} - queue = radio_data['queue'] + radio_data["to_delete"] = {} + queue = radio_data["queue"] if not queue: # nothing left to play, we need to wait for uploads - radio_data['playing'] = None + radio_data["playing"] = None return song = queue.pop(0) - filename, length = song['filename'], float(song['length']) - self.send(room_jid, ('', 'play'), {'filename': filename}, profile=profile) - radio_data['playing'] = song - radio_data['playing_time'] = time.time() + filename, length = song["filename"], float(song["length"]) + self.send(room_jid, ("", "play"), {"filename": filename}, profile=profile) + radio_data["playing"] = song + radio_data["playing_time"] = time.time() - if not radio_data['upload'] and len(queue) < QUEUE_LIMIT: + if not radio_data["upload"] and len(queue) < QUEUE_LIMIT: # upload is blocked and we now have resources to get more, we reactivate it - self.send(room_jid, ('', 'upload_ok'), profile=profile) - radio_data['upload'] = True + self.send(room_jid, ("", "upload_ok"), profile=profile) + radio_data["upload"] = True reactor.callLater(length, self.playNext, room_jid, profile) # we wait more than the song length to delete the file, to manage poorly reactive networks/clients - reactor.callLater(length + 90, self.deleteFile, filename, radio_data) # FIXME: same host trick (see above) + reactor.callLater( + length + 90, self.deleteFile, filename, radio_data + ) # FIXME: same host trick (see above) def deleteFile(self, filename, radio_data=None): """ @@ -177,61 +243,96 @@ """ if radio_data: try: - file_to_delete = radio_data['to_delete'][filename] + file_to_delete = radio_data["to_delete"][filename] except KeyError: - log.error(_(u"INTERNAL ERROR: can't find full path of the song to delete")) + log.error( + _(u"INTERNAL ERROR: can't find full path of the song to delete") + ) return False else: file_to_delete = filename try: unlink(file_to_delete) except OSError: - log.error(_(u"INTERNAL ERROR: can't find %s on the file system" % file_to_delete)) + log.error( + _(u"INTERNAL ERROR: can't find %s on the file system" % file_to_delete) + ) return False return True def room_game_cmd(self, mess_elt, profile): - from_jid = jid.JID(mess_elt['from']) + from_jid = jid.JID(mess_elt["from"]) room_jid = from_jid.userhostJID() nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid, profile) radio_elt = mess_elt.firstChildElement() radio_data = self.games[room_jid] - if 'queue' in radio_data: - queue = radio_data['queue'] + if "queue" in radio_data: + queue = radio_data["queue"] from_referee = self.isReferee(room_jid, from_jid.resource) - to_referee = self.isReferee(room_jid, jid.JID(mess_elt['to']).user) + to_referee = self.isReferee(room_jid, jid.JID(mess_elt["to"]).user) is_player = self.isPlayer(room_jid, nick) for elt in radio_elt.elements(): - if not from_referee and not (to_referee and elt.name == 'song_added'): + if not from_referee and not (to_referee and elt.name == "song_added"): continue # sender must be referee, expect when a song is submitted - if not is_player and (elt.name not in ('started', 'players')): + if not is_player and (elt.name not in ("started", "players")): continue # user is in the room but not playing - if elt.name in ('started', 'players'): # new game created and/or players list updated + if elt.name in ( + "started", + "players", + ): # new game created and/or players list updated players = [] for player in elt.elements(): players.append(unicode(player)) - signal = self.host.bridge.radiocolStarted if elt.name == 'started' else self.host.bridge.radiocolPlayers - signal(room_jid.userhost(), from_jid.full(), players, [QUEUE_TO_START, QUEUE_LIMIT], profile) - elif elt.name == 'preload': # a song is in queue and must be preloaded - self.host.bridge.radiocolPreload(room_jid.userhost(), elt['timestamp'], elt['filename'], elt['title'], elt['artist'], elt['album'], elt['sender'], profile) - elif elt.name == 'play': - self.host.bridge.radiocolPlay(room_jid.userhost(), elt['filename'], profile) - elif elt.name == 'song_rejected': # a song has been refused - self.host.bridge.radiocolSongRejected(room_jid.userhost(), elt['reason'], profile) - elif elt.name == 'no_upload': + signal = ( + self.host.bridge.radiocolStarted + if elt.name == "started" + else self.host.bridge.radiocolPlayers + ) + signal( + room_jid.userhost(), + from_jid.full(), + players, + [QUEUE_TO_START, QUEUE_LIMIT], + profile, + ) + elif elt.name == "preload": # a song is in queue and must be preloaded + self.host.bridge.radiocolPreload( + room_jid.userhost(), + elt["timestamp"], + elt["filename"], + elt["title"], + elt["artist"], + elt["album"], + elt["sender"], + profile, + ) + elif elt.name == "play": + self.host.bridge.radiocolPlay( + room_jid.userhost(), elt["filename"], profile + ) + elif elt.name == "song_rejected": # a song has been refused + self.host.bridge.radiocolSongRejected( + room_jid.userhost(), elt["reason"], profile + ) + elif elt.name == "no_upload": self.host.bridge.radiocolNoUpload(room_jid.userhost(), profile) - elif elt.name == 'upload_ok': + elif elt.name == "upload_ok": self.host.bridge.radiocolUploadOk(room_jid.userhost(), profile) - elif elt.name == 'song_added': # a song has been added + elif elt.name == "song_added": # a song has been added # FIXME: we are KISS for the proof of concept: every song is added, to a limit of 3 in queue. # Need to manage some sort of rules to allow peoples to send songs if len(queue) >= QUEUE_LIMIT: # there are already too many songs in queue, we reject this one # FIXME: add an error code - self.send(from_jid, ('', 'song_rejected'), {'reason': "Too many songs in queue"}, profile=profile) + self.send( + from_jid, + ("", "song_rejected"), + {"reason": "Too many songs in queue"}, + profile=profile, + ) return # The song is accepted and added in queue @@ -240,30 +341,30 @@ if len(queue) >= QUEUE_LIMIT: # We are at the limit, we refuse new upload until next play - self.send(room_jid, ('', 'no_upload'), profile=profile) - radio_data['upload'] = False + self.send(room_jid, ("", "no_upload"), profile=profile) + radio_data["upload"] = False self.send(room_jid, preload_elt, profile=profile) - if not radio_data['playing'] and len(queue) == QUEUE_TO_START: + if not radio_data["playing"] and len(queue) == QUEUE_TO_START: # We have not started playing yet, and we have QUEUE_TO_START # songs in queue. We can now start the party :) self.playNext(room_jid, profile) else: - log.error(_(u'Unmanaged game element: %s') % elt.name) + log.error(_(u"Unmanaged game element: %s") % elt.name) def getSyncDataForPlayer(self, room_jid, nick): game_data = self.games[room_jid] elements = [] - if game_data['playing']: - preload = copy.deepcopy(game_data['playing']) - current_time = game_data['playing_time'] + 1 if self.testing else time.time() - preload['filename'] += '#t=%.2f' % (current_time - game_data['playing_time']) + if game_data["playing"]: + preload = copy.deepcopy(game_data["playing"]) + current_time = game_data["playing_time"] + 1 if self.testing else time.time() + preload["filename"] += "#t=%.2f" % (current_time - game_data["playing_time"]) elements.append(preload) - play = domish.Element(('', 'play')) - play['filename'] = preload['filename'] + play = domish.Element(("", "play")) + play["filename"] = preload["filename"] elements.append(play) - if len(game_data['queue']) > 0: - elements.extend(copy.deepcopy(game_data['queue'])) - if len(game_data['queue']) == QUEUE_LIMIT: - elements.append(domish.Element(('', 'no_upload'))) + if len(game_data["queue"]) > 0: + elements.extend(copy.deepcopy(game_data["queue"])) + if len(game_data["queue"]) == QUEUE_LIMIT: + elements.append(domish.Element(("", "no_upload"))) return elements
--- a/sat/plugins/plugin_misc_register_account.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_register_account.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _, D_ from sat.core.log import getLogger + log = getLogger(__name__) from sat.core.constants import Const as C from twisted.words.protocols.jabber import jid @@ -37,7 +38,7 @@ C.PI_RECOMMENDATIONS: [], C.PI_MAIN: "RegisterAccount", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _(u"""Register XMPP account""") + C.PI_DESCRIPTION: _(u"""Register XMPP account"""), } @@ -48,8 +49,12 @@ log.info(_(u"Plugin Register Account initialization")) self.host = host self._sessions = Sessions() - host.registerCallback(self.registerNewAccountCB, with_data=True, force_id="registerNewAccount") - self.__register_account_id = host.registerCallback(self._registerConfirmation, with_data=True) + host.registerCallback( + self.registerNewAccountCB, with_data=True, force_id="registerNewAccount" + ) + self.__register_account_id = host.registerCallback( + self._registerConfirmation, with_data=True + ) def registerNewAccountCB(self, data, profile): """Called when the user click on the "New account" button.""" @@ -57,57 +62,92 @@ # FIXME: following loop is overcomplicated, hard to read # FIXME: while used with parameters, hashed password is used and overwrite clear one - for param in (u'JabberID', u'Password', C.FORCE_PORT_PARAM, C.FORCE_SERVER_PARAM): + for param in (u"JabberID", u"Password", C.FORCE_PORT_PARAM, C.FORCE_SERVER_PARAM): try: - session_data[param] = data[SAT_FORM_PREFIX + u"Connection" + SAT_PARAM_SEPARATOR + param] + session_data[param] = data[ + SAT_FORM_PREFIX + u"Connection" + SAT_PARAM_SEPARATOR + param + ] except KeyError: if param in (C.FORCE_PORT_PARAM, C.FORCE_SERVER_PARAM): - session_data[param] = '' + session_data[param] = "" - for param in (u'JabberID', u'Password'): + for param in (u"JabberID", u"Password"): if not session_data[param]: form_ui = xml_tools.XMLUI(u"popup", title=D_(u"Missing values")) - form_ui.addText(D_(u"No user JID or password given: can't register new account.")) - return {u'xmlui': form_ui.toXml()} + form_ui.addText( + D_(u"No user JID or password given: can't register new account.") + ) + return {u"xmlui": form_ui.toXml()} - session_data['user'], host, resource = jid.parse(session_data['JabberID']) - session_data['server'] = session_data[C.FORCE_SERVER_PARAM] or host + session_data["user"], host, resource = jid.parse(session_data["JabberID"]) + session_data["server"] = session_data[C.FORCE_SERVER_PARAM] or host session_id, dummy = self._sessions.newSession(session_data, profile=profile) - form_ui = xml_tools.XMLUI("form", title=D_("Register new account"), submit_id=self.__register_account_id, session_id=session_id) - form_ui.addText(D_(u"Do you want to register a new XMPP account {jid}?").format( - jid = session_data['JabberID'])) - return {'xmlui': form_ui.toXml()} + form_ui = xml_tools.XMLUI( + "form", + title=D_("Register new account"), + submit_id=self.__register_account_id, + session_id=session_id, + ) + form_ui.addText( + D_(u"Do you want to register a new XMPP account {jid}?").format( + jid=session_data["JabberID"] + ) + ) + return {"xmlui": form_ui.toXml()} def _registerConfirmation(self, data, profile): """Save the related parameters and proceed the registration.""" - session_data = self._sessions.profileGet(data['session_id'], profile) + session_data = self._sessions.profileGet(data["session_id"], profile) - self.host.memory.setParam("JabberID", session_data["JabberID"], "Connection", profile_key=profile) - self.host.memory.setParam("Password", session_data["Password"], "Connection", profile_key=profile) - self.host.memory.setParam(C.FORCE_SERVER_PARAM, session_data[C.FORCE_SERVER_PARAM], "Connection", profile_key=profile) - self.host.memory.setParam(C.FORCE_PORT_PARAM, session_data[C.FORCE_PORT_PARAM], "Connection", profile_key=profile) + self.host.memory.setParam( + "JabberID", session_data["JabberID"], "Connection", profile_key=profile + ) + self.host.memory.setParam( + "Password", session_data["Password"], "Connection", profile_key=profile + ) + self.host.memory.setParam( + C.FORCE_SERVER_PARAM, + session_data[C.FORCE_SERVER_PARAM], + "Connection", + profile_key=profile, + ) + self.host.memory.setParam( + C.FORCE_PORT_PARAM, + session_data[C.FORCE_PORT_PARAM], + "Connection", + profile_key=profile, + ) - d = self._registerNewAccount(jid.JID(session_data['JabberID']), session_data["Password"], None, session_data['server']) - del self._sessions[data['session_id']] + d = self._registerNewAccount( + jid.JID(session_data["JabberID"]), + session_data["Password"], + None, + session_data["server"], + ) + del self._sessions[data["session_id"]] return d def _registerNewAccount(self, client, jid_, password, email, server): - # FIXME: port is not set here + # FIXME: port is not set here def registeredCb(dummy): xmlui = xml_tools.XMLUI(u"popup", title=D_(u"Confirmation")) xmlui.addText(D_("Registration successful.")) - return ({'xmlui': xmlui.toXml()}) + return {"xmlui": xmlui.toXml()} def registeredEb(failure): xmlui = xml_tools.XMLUI("popup", title=D_("Failure")) xmlui.addText(D_("Registration failed: %s") % failure.getErrorMessage()) try: - if failure.value.condition == 'conflict': - xmlui.addText(D_("Username already exists, please choose an other one.")) + if failure.value.condition == "conflict": + xmlui.addText( + D_("Username already exists, please choose an other one.") + ) except AttributeError: pass - return ({'xmlui': xmlui.toXml()}) + return {"xmlui": xmlui.toXml()} - registered_d = self.host.plugins['XEP-0077'].registerNewAccount(client, jid_, password, email=email, host=server, port=C.XMPP_C2S_PORT) + registered_d = self.host.plugins["XEP-0077"].registerNewAccount( + client, jid_, password, email=email, host=server, port=C.XMPP_C2S_PORT + ) registered_d.addCallbacks(registeredCb, registeredEb) return registered_d
--- a/sat/plugins/plugin_misc_room_game.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_room_game.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.protocols.jabber import jid from twisted.words.xish import domish @@ -28,6 +29,7 @@ from wokkel import disco, iwokkel from zope.interface import implements import copy + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: @@ -44,12 +46,13 @@ C.PI_DEPENDENCIES: ["XEP-0045", "XEP-0249"], C.PI_MAIN: "RoomGame", C.PI_HANDLER: "no", # handler MUST be "no" (dynamic inheritance) - C.PI_DESCRIPTION: _("""Base class for MUC games""") + C.PI_DESCRIPTION: _("""Base class for MUC games"""), } # FIXME: this plugin is broken, need to be fixed + class RoomGame(object): """This class is used to help launching a MUC game. @@ -69,7 +72,7 @@ # Values for ready_mode (how to turn a MUC user into a player) ASK, FORCE = xrange(0, 2) - MESSAGE = '/message' + MESSAGE = "/message" REQUEST = '%s/%s[@xmlns="%s"]' def __init__(self, host): @@ -165,8 +168,13 @@ # Important: do not add the referee to 'players' yet. For a # <players /> message to be emitted whenever a new player is joining, # it is necessary to not modify 'players' outside of _updatePlayers. - referee_jid = jid.JID(room_jid.userhost() + '/' + referee_nick) - self.games[room_jid] = {'referee': referee_jid, 'players': [], 'started': False, 'status': {}} + referee_jid = jid.JID(room_jid.userhost() + "/" + referee_nick) + self.games[room_jid] = { + "referee": referee_jid, + "players": [], + "started": False, + "status": {}, + } self.games[room_jid].update(copy.deepcopy(self.game_init)) self.invitations.setdefault(room_jid, []) @@ -175,7 +183,7 @@ @param started: if False, the game must be initialized to return True, otherwise it must be initialized and started with createGame. @return: True if a game is initialized/started in that room""" - return room_jid in self.games and (not started or self.games[room_jid]['started']) + return room_jid in self.games and (not started or self.games[room_jid]["started"]) def _checkJoinAuth(self, room_jid, user_jid=None, nick="", verbose=False): """Checks if this profile is allowed to join the game. @@ -209,7 +217,14 @@ break if not auth and (verbose or _DEBUG): - log.debug(_(u"%(user)s not allowed to join the game %(game)s in %(room)s") % {'user': user_jid.userhost() or nick, 'game': self.name, 'room': room_jid.userhost()}) + log.debug( + _(u"%(user)s not allowed to join the game %(game)s in %(room)s") + % { + "user": user_jid.userhost() or nick, + "game": self.name, + "room": room_jid.userhost(), + } + ) return auth def _updatePlayers(self, room_jid, nicks, sync, profile): @@ -224,20 +239,26 @@ if nicks == []: return # this is better than set(nicks).difference(...) as it keeps the order - new_nicks = [nick for nick in nicks if nick not in self.games[room_jid]['players']] + new_nicks = [ + nick for nick in nicks if nick not in self.games[room_jid]["players"] + ] if len(new_nicks) == 0: return def setStatus(status): for nick in new_nicks: - self.games[room_jid]['status'][nick] = status + self.games[room_jid]["status"][nick] = status - sync = sync and self._gameExists(room_jid, True) and len(self.games[room_jid]['players']) > 0 - setStatus('desync' if sync else 'init') - self.games[room_jid]['players'].extend(new_nicks) + sync = ( + sync + and self._gameExists(room_jid, True) + and len(self.games[room_jid]["players"]) > 0 + ) + setStatus("desync" if sync else "init") + self.games[room_jid]["players"].extend(new_nicks) self._synchronizeRoom(room_jid, [room_jid], profile) if sync: - setStatus('init') + setStatus("init") def _synchronizeRoom(self, room_jid, recipients, profile): """Communicate the list of players to the whole room or only to some users, @@ -249,15 +270,17 @@ @param profile (unicode): %(doc_profile)s """ if self._gameExists(room_jid, started=True): - element = self._createStartElement(self.games[room_jid]['players']) + element = self._createStartElement(self.games[room_jid]["players"]) else: - element = self._createStartElement(self.games[room_jid]['players'], name="players") + element = self._createStartElement( + self.games[room_jid]["players"], name="players" + ) elements = [(element, None, None)] sync_args = [] sync_data = self._getSyncData(room_jid) for nick in sync_data: - user_jid = jid.JID(room_jid.userhost() + '/' + nick) + user_jid = jid.JID(room_jid.userhost() + "/" + nick) if user_jid in recipients: user_elements = copy.deepcopy(elements) for child in sync_data[nick]: @@ -265,7 +288,7 @@ recipients.remove(user_jid) else: user_elements = [(child, None, None) for child in sync_data[nick]] - sync_args.append(([user_jid, user_elements], {'profile': profile})) + sync_args.append(([user_jid, user_elements], {"profile": profile})) for recipient in recipients: self._sendElements(recipient, elements, profile=profile) @@ -283,8 +306,8 @@ if not self._gameExists(room_jid): return {} data = {} - status = self.games[room_jid]['status'] - nicks = [nick for nick in status if status[nick] == 'desync'] + status = self.games[room_jid]["status"] + nicks = [nick for nick in status if status[nick] == "desync"] if force_nicks is None: force_nicks = [] for nick in force_nicks: @@ -316,13 +339,19 @@ if not self._checkInviteAuth(room_jid, nick): return [] # TODO: remove invitation waiting for too long, using the time data - self.invitations[room_jid].append((time(), [player.userhostJID() for player in other_players])) + self.invitations[room_jid].append( + (time(), [player.userhostJID() for player in other_players]) + ) nicks = [] for player_jid in [player.userhostJID() for player in other_players]: # TODO: find a way to make it secure - other_nick = self.host.plugins["XEP-0045"].getRoomEntityNick(room_jid, player_jid, secure=self.testing) + other_nick = self.host.plugins["XEP-0045"].getRoomEntityNick( + room_jid, player_jid, secure=self.testing + ) if other_nick is None: - self.host.plugins["XEP-0249"].invite(player_jid, room_jid, {"game": self.name}, profile) + self.host.plugins["XEP-0249"].invite( + player_jid, room_jid, {"game": self.name}, profile + ) else: nicks.append(other_nick) return nicks @@ -339,13 +368,18 @@ if self.invite_mode == self.FROM_ALL or not self._gameExists(room_jid): auth = True elif self.invite_mode == self.FROM_NONE: - auth = not self._gameExists(room_jid, started=True) and self.isReferee(room_jid, nick) + auth = not self._gameExists(room_jid, started=True) and self.isReferee( + room_jid, nick + ) elif self.invite_mode == self.FROM_REFEREE: auth = self.isReferee(room_jid, nick) elif self.invite_mode == self.FROM_PLAYERS: auth = self.isPlayer(room_jid, nick) if not auth and (verbose or _DEBUG): - log.debug(_(u"%(user)s not allowed to invite for the game %(game)s in %(room)s") % {'user': nick, 'game': self.name, 'room': room_jid.userhost()}) + log.debug( + _(u"%(user)s not allowed to invite for the game %(game)s in %(room)s") + % {"user": nick, "game": self.name, "room": room_jid.userhost()} + ) return auth def isReferee(self, room_jid, nick): @@ -356,7 +390,9 @@ """ if not self._gameExists(room_jid): return False - return jid.JID(room_jid.userhost() + '/' + nick) == self.games[room_jid]['referee'] + return ( + jid.JID(room_jid.userhost() + "/" + nick) == self.games[room_jid]["referee"] + ) def isPlayer(self, room_jid, nick): """Checks if the user with this nick is a player for the game in this room. @@ -368,7 +404,7 @@ return False # Important: the referee is not in the 'players' list right after # the game initialization, that's why we do also check with isReferee - return nick in self.games[room_jid]['players'] or self.isReferee(room_jid, nick) + return nick in self.games[room_jid]["players"] or self.isReferee(room_jid, nick) def _checkWaitAuth(self, room, other_players, verbose=False): """Check if we must wait for other players before starting the game. @@ -388,10 +424,21 @@ result = (False, [], other_players) else: # TODO: find a way to make it secure - (nicks, missing) = self.host.plugins["XEP-0045"].getRoomNicksOfUsers(room, other_players, secure=False) + (nicks, missing) = self.host.plugins["XEP-0045"].getRoomNicksOfUsers( + room, other_players, secure=False + ) result = (len(nicks) == len(other_players), nicks, missing) if not result[0] and (verbose or _DEBUG): - log.debug(_(u"Still waiting for %(users)s before starting the game %(game)s in %(room)s") % {'users': result[2], 'game': self.name, 'room': room.occupantJID.userhost()}) + log.debug( + _( + u"Still waiting for %(users)s before starting the game %(game)s in %(room)s" + ) + % { + "users": result[2], + "game": self.name, + "room": room.occupantJID.userhost(), + } + ) return result def getUniqueName(self, muc_service=None, profile_key=C.PROF_KEY_NONE): @@ -406,7 +453,9 @@ room = self.host.plugins["XEP-0045"].getUniqueName(client, muc_service) return jid.JID("sat_%s_%s" % (self.name.lower(), room.userhost())) - def _prepareRoom(self, other_players=None, room_jid_s='', profile_key=C.PROF_KEY_NONE): + def _prepareRoom( + self, other_players=None, room_jid_s="", profile_key=C.PROF_KEY_NONE + ): room_jid = jid.JID(room_jid_s) if room_jid_s else None other_players = [jid.JID(player).userhostJID() for player in other_players] return self.prepareRoom(other_players, room_jid, profile_key) @@ -420,7 +469,7 @@ """ # FIXME: need to be refactored client = self.host.getClient(profile_key) - log.debug(_(u'Preparing room for %s game') % self.name) + log.debug(_(u"Preparing room for %s game") % self.name) profile = self.host.memory.getProfileName(profile_key) if not profile: log.error(_("Unknown profile")) @@ -438,7 +487,9 @@ user_jid = self.host.getJidNStream(profile)[0] d = self.host.plugins["XEP-0045"].join(room_jid, user_jid.user, {}, profile) - return d.addCallback(lambda dummy: self._createOrInvite(client, room_jid, other_players)) + return d.addCallback( + lambda dummy: self._createOrInvite(client, room_jid, other_players) + ) def userJoinedTrigger(self, room, user, profile): """This trigger is used to check if the new user can take part of a game, create the game if we were waiting for him or just update the players list. @@ -451,15 +502,22 @@ profile_nick = room.occupantJID.resource if not self.isReferee(room_jid, profile_nick): return True # profile is not the referee - if not self._checkJoinAuth(room_jid, user.entity if user.entity else None, user.nick): + if not self._checkJoinAuth( + room_jid, user.entity if user.entity else None, user.nick + ): # user not allowed but let him know that we are playing :p - self._synchronizeRoom(room_jid, [jid.JID(room_jid.userhost() + '/' + user.nick)], profile) + self._synchronizeRoom( + room_jid, [jid.JID(room_jid.userhost() + "/" + user.nick)], profile + ) return True if self.wait_mode == self.FOR_ALL: # considering the last batch of invitations batch = len(self.invitations[room_jid]) - 1 if batch < 0: - log.error(u"Invitations from %s to play %s in %s have been lost!" % (profile_nick, self.name, room_jid.userhost())) + log.error( + u"Invitations from %s to play %s in %s have been lost!" + % (profile_nick, self.name, room_jid.userhost()) + ) return True other_players = self.invitations[room_jid][batch][1] (auth, nicks, dummy) = self._checkWaitAuth(room, other_players) @@ -485,10 +543,10 @@ return True # profile is not the referee if self.isPlayer(room_jid, user.nick): try: - self.games[room_jid]['players'].remove(user.nick) + self.games[room_jid]["players"].remove(user.nick) except ValueError: pass - if len(self.games[room_jid]['players']) == 0: + if len(self.games[room_jid]["players"]) == 0: return True if self.wait_mode == self.FOR_ALL: # allow this user to join the game again @@ -513,15 +571,24 @@ """ user_nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid, profile) if not user_nick: - log.error(u'Internal error: profile %s has not joined the room %s' % (profile, room_jid.userhost())) + log.error( + u"Internal error: profile %s has not joined the room %s" + % (profile, room_jid.userhost()) + ) return False, False if self._gameExists(room_jid): is_referee = self.isReferee(room_jid, user_nick) if self._gameExists(room_jid, started=True): - log.info(_(u"%(game)s game already created in room %(room)s") % {'game': self.name, 'room': room_jid.userhost()}) + log.info( + _(u"%(game)s game already created in room %(room)s") + % {"game": self.name, "room": room_jid.userhost()} + ) return False, is_referee elif not is_referee: - log.info(_(u"%(game)s game in room %(room)s can only be created by %(user)s") % {'game': self.name, 'room': room_jid.userhost(), 'user': user_nick}) + log.info( + _(u"%(game)s game in room %(room)s can only be created by %(user)s") + % {"game": self.name, "room": room_jid.userhost(), "user": user_nick} + ) return False, False else: self._initGame(room_jid, user_nick) @@ -539,7 +606,10 @@ @param nicks (list[unicode]): list of players nicks in the room (referee included, in first position) @param profile_key (unicode): %(doc_profile_key)s """ - log.debug(_(u"Creating %(game)s game in room %(room)s") % {'game': self.name, 'room': room_jid}) + log.debug( + _(u"Creating %(game)s game in room %(room)s") + % {"game": self.name, "room": room_jid} + ) profile = self.host.memory.getProfileName(profile_key) if not profile: log.error(_(u"profile %s is unknown") % profile_key) @@ -551,14 +621,16 @@ if sync: self._updatePlayers(room_jid, nicks, True, profile) return - self.games[room_jid]['started'] = True + self.games[room_jid]["started"] = True self._updatePlayers(room_jid, nicks, False, profile) if self.player_init: # specific data to each player (score, private data) - self.games[room_jid].setdefault('players_data', {}) + self.games[room_jid].setdefault("players_data", {}) for nick in nicks: # The dict must be COPIED otherwise it is shared between all users - self.games[room_jid]['players_data'][nick] = copy.deepcopy(self.player_init) + self.games[room_jid]["players_data"][nick] = copy.deepcopy( + self.player_init + ) def _playerReady(self, player_nick, referee_jid_s, profile_key=C.PROF_KEY_NONE): self.playerReady(player_nick, jid.JID(referee_jid_s), profile_key) @@ -573,9 +645,9 @@ if not profile: log.error(_(u"profile %s is unknown") % profile_key) return - log.debug(u'new player ready: %s' % profile) + log.debug(u"new player ready: %s" % profile) # TODO: we probably need to add the game and room names in the sent message - self.send(referee_jid, 'player_ready', {'player': player_nick}, profile=profile) + self.send(referee_jid, "player_ready", {"player": player_nick}, profile=profile) def newRound(self, room_jid, data, profile): """Launch a new round (reinit the user data) @@ -586,18 +658,22 @@ - msg_elts: dict to map each user to his specific initialization message @param profile """ - log.debug(_(u'new round for %s game') % self.name) + log.debug(_(u"new round for %s game") % self.name) game_data = self.games[room_jid] - players = game_data['players'] - players_data = game_data['players_data'] - game_data['stage'] = "init" + players = game_data["players"] + players_data = game_data["players_data"] + game_data["stage"] = "init" common_data, msg_elts = copy.deepcopy(data) if data is not None else (None, None) if isinstance(msg_elts, dict): for player in players: to_jid = jid.JID(room_jid.userhost() + "/" + player) # FIXME: gof: - elem = msg_elts[player] if isinstance(msg_elts[player], domish.Element) else None + elem = ( + msg_elts[player] + if isinstance(msg_elts[player], domish.Element) + else None + ) self.send(to_jid, elem, profile=profile) elif isinstance(msg_elts, domish.Element): self.send(room_jid, msg_elts, profile=profile) @@ -612,7 +688,7 @@ @return: the created element """ type_ = "normal" if to_jid.resource else "groupchat" - elt = domish.Element((None, 'message')) + elt = domish.Element((None, "message")) elt["to"] = to_jid.full() elt["type"] = type_ elt.addElement(self.ns_tag) @@ -632,9 +708,9 @@ return started_elt idx = 0 for player in players: - player_elt = domish.Element((None, 'player')) + player_elt = domish.Element((None, "player")) player_elt.addContent(player) - player_elt['index'] = str(idx) + player_elt["index"] = str(idx) idx += 1 started_elt.addChild(player_elt) return started_elt @@ -687,7 +763,7 @@ return RoomGameHandler(self) -class RoomGameHandler (XMPPHandler): +class RoomGameHandler(XMPPHandler): implements(iwokkel.IDisco) def __init__(self, plugin_parent): @@ -695,10 +771,14 @@ self.host = plugin_parent.host def connectionInitialized(self): - self.xmlstream.addObserver(self.plugin_parent.request, self.plugin_parent.room_game_cmd, profile=self.parent.profile) + self.xmlstream.addObserver( + self.plugin_parent.request, + self.plugin_parent.room_game_cmd, + profile=self.parent.profile, + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(self.plugin_parent.ns_tag[0])] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_misc_smtp.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_smtp.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.internet import defer from twisted.cred import portal, checkers, credentials @@ -42,7 +43,9 @@ C.PI_DEPENDENCIES: ["Maildir"], C.PI_MAIN: "SMTP_server", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Create a SMTP server that you can use to send your "normal" type messages""") + C.PI_DESCRIPTION: _( + """Create a SMTP server that you can use to send your "normal" type messages""" + ), } @@ -62,7 +65,7 @@ log.info(_("Plugin SMTP Server initialization")) self.host = host - #parameters + # parameters host.memory.updateParams(self.params) port = int(self.host.memory.getParamA("SMTP Port", "Mail Server")) @@ -88,11 +91,20 @@ """handle end of message""" mail = Parser().parsestr("\n".join(self.message)) try: - self.host._sendMessage(parseaddr(mail['to'].decode('utf-8', 'replace'))[1], mail.get_payload().decode('utf-8', 'replace'), # TODO: manage other charsets - subject=mail['subject'].decode('utf-8', 'replace'), mess_type='normal', profile_key=self.profile) + self.host._sendMessage( + parseaddr(mail["to"].decode("utf-8", "replace"))[1], + mail.get_payload().decode( + "utf-8", "replace" + ), # TODO: manage other charsets + subject=mail["subject"].decode("utf-8", "replace"), + mess_type="normal", + profile_key=self.profile, + ) except: exc_type, exc_value, exc_traceback = sys.exc_info() - log.error(_(u"Can't send message: %s") % exc_value) # The email is invalid or incorreclty parsed + log.error( + _(u"Can't send message: %s") % exc_value + ) # The email is invalid or incorreclty parsed return defer.fail() self.message = None return defer.succeed(None) @@ -151,8 +163,8 @@ self.host = host def requestAvatar(self, avatarID, mind, *interfaces): - log.debug('requestAvatar') - profile = avatarID.decode('utf-8') + log.debug("requestAvatar") + profile = avatarID.decode("utf-8") if smtp.IMessageDelivery not in interfaces: raise NotImplementedError return smtp.IMessageDelivery, SatSmtpDelivery(self.host, profile), lambda: None @@ -164,16 +176,19 @@ Check if the profile exists, and if the password is OK Return the profile as avatarId """ + implements(checkers.ICredentialsChecker) - credentialInterfaces = (credentials.IUsernamePassword, - credentials.IUsernameHashedPassword) + credentialInterfaces = ( + credentials.IUsernamePassword, + credentials.IUsernameHashedPassword, + ) def __init__(self, host): self.host = host def _cbPasswordMatch(self, matched, profile): if matched: - return profile.encode('utf-8') + return profile.encode("utf-8") else: return failure.Failure(cred_error.UnauthorizedLogin()) @@ -181,14 +196,15 @@ profiles = self.host.memory.getProfilesList() if not credentials.username in profiles: return defer.fail(cred_error.UnauthorizedLogin()) - d = self.host.memory.asyncGetParamA("Password", "Connection", profile_key=credentials.username) + d = self.host.memory.asyncGetParamA( + "Password", "Connection", profile_key=credentials.username + ) d.addCallback(credentials.checkPassword) d.addCallback(self._cbPasswordMatch, credentials.username) return d class SmtpServerFactory(smtp.SMTPFactory): - def __init__(self, host): self.protocol = smtp.ESMTP self.host = host
--- a/sat/plugins/plugin_misc_static_blog.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_static_blog.py Wed Jun 27 20:14:46 2018 +0200 @@ -18,6 +18,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.core.log import getLogger + log = getLogger(__name__) from sat.core.i18n import _, D_ @@ -35,10 +36,12 @@ C.PI_TYPE: "MISC", C.PI_PROTOCOLS: [], C.PI_DEPENDENCIES: [], - C.PI_RECOMMENDATIONS: ['MISC-ACCOUNT'], # TODO: remove when all blogs can be retrieved + C.PI_RECOMMENDATIONS: [ + "MISC-ACCOUNT" + ], # TODO: remove when all blogs can be retrieved C.PI_MAIN: "StaticBlog", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Plugin for static blogs""") + C.PI_DESCRIPTION: _("""Plugin for static blogs"""), } @@ -57,23 +60,23 @@ </individual> </params> """.format( - category_name = C.STATIC_BLOG_KEY, - category_label = D_(C.STATIC_BLOG_KEY), - title_name = C.STATIC_BLOG_PARAM_TITLE, - title_label = D_('Page title'), - banner_name = C.STATIC_BLOG_PARAM_BANNER, - banner_label = D_('Banner URL'), - background_name = u"Background", - background_label = D_(u"Background image URL"), - keywords_name = C.STATIC_BLOG_PARAM_KEYWORDS, - keywords_label = D_('Keywords'), - description_name = C.STATIC_BLOG_PARAM_DESCRIPTION, - description_label = D_('Description'), + category_name=C.STATIC_BLOG_KEY, + category_label=D_(C.STATIC_BLOG_KEY), + title_name=C.STATIC_BLOG_PARAM_TITLE, + title_label=D_("Page title"), + banner_name=C.STATIC_BLOG_PARAM_BANNER, + banner_label=D_("Banner URL"), + background_name=u"Background", + background_label=D_(u"Background image URL"), + keywords_name=C.STATIC_BLOG_PARAM_KEYWORDS, + keywords_label=D_("Keywords"), + description_name=C.STATIC_BLOG_PARAM_DESCRIPTION, + description_label=D_("Description"), ) def __init__(self, host): try: # TODO: remove this attribute when all blogs can be retrieved - self.domain = host.plugins['MISC-ACCOUNT'].getNewAccountDomain() + self.domain = host.plugins["MISC-ACCOUNT"].getNewAccountDomain() except KeyError: self.domain = None host.memory.updateParams(self.params) @@ -89,7 +92,7 @@ # FIXME: "public_blog" key has been removed # TODO: replace this with a more generic widget call with URIs try: - user_jid = jid.JID(menu_data['jid']) + user_jid = jid.JID(menu_data["jid"]) except KeyError: log.error(_("jid key is not present !")) return defer.fail(exceptions.DataError) @@ -97,7 +100,9 @@ # TODO: remove this check when all blogs can be retrieved if self.domain and user_jid.host != self.domain: info_ui = xml_tools.XMLUI("popup", title=D_("Not available")) - info_ui.addText(D_("Retrieving a blog from an external domain is not implemented yet.")) - return {'xmlui': info_ui.toXml()} + info_ui.addText( + D_("Retrieving a blog from an external domain is not implemented yet.") + ) + return {"xmlui": info_ui.toXml()} return {"public_blog": user_jid.userhost()}
--- a/sat/plugins/plugin_misc_tarot.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_tarot.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.xish import domish from twisted.words.protocols.jabber import jid @@ -32,8 +33,8 @@ import random -NS_CG = 'http://www.goffi.org/protocol/card_game' -CG_TAG = 'card_game' +NS_CG = "http://www.goffi.org/protocol/card_game" +CG_TAG = "card_game" PLUGIN_INFO = { C.PI_NAME: "Tarot cards plugin", @@ -43,54 +44,117 @@ C.PI_DEPENDENCIES: ["XEP-0045", "XEP-0249", "ROOM-GAME"], C.PI_MAIN: "Tarot", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Tarot card game""") + C.PI_DESCRIPTION: _("""Implementation of Tarot card game"""), } class Tarot(object): - def inheritFromRoomGame(self, host): global RoomGame RoomGame = host.plugins["ROOM-GAME"].__class__ - self.__class__ = type(self.__class__.__name__, (self.__class__, RoomGame, object), {}) + self.__class__ = type( + self.__class__.__name__, (self.__class__, RoomGame, object), {} + ) def __init__(self, host): log.info(_("Plugin Tarot initialization")) self._sessions = memory.Sessions() self.inheritFromRoomGame(host) - RoomGame._init_(self, host, PLUGIN_INFO, (NS_CG, CG_TAG), - game_init={'hand_size': 18, 'init_player': 0, 'current_player': None, 'contrat': None, 'stage': None}, - player_init={'score': 0}) - self.contrats = [_('Passe'), _('Petite'), _('Garde'), _('Garde Sans'), _('Garde Contre')] - host.bridge.addMethod("tarotGameLaunch", ".plugin", in_sign='asss', out_sign='', method=self._prepareRoom, async=True) # args: players, room_jid, profile - host.bridge.addMethod("tarotGameCreate", ".plugin", in_sign='sass', out_sign='', method=self._createGame) # args: room_jid, players, profile - host.bridge.addMethod("tarotGameReady", ".plugin", in_sign='sss', out_sign='', method=self._playerReady) # args: player, referee, profile - host.bridge.addMethod("tarotGamePlayCards", ".plugin", in_sign='ssa(ss)s', out_sign='', method=self.play_cards) # args: player, referee, cards, profile - host.bridge.addSignal("tarotGamePlayers", ".plugin", signature='ssass') # args: room_jid, referee, players, profile - host.bridge.addSignal("tarotGameStarted", ".plugin", signature='ssass') # args: room_jid, referee, players, profile - host.bridge.addSignal("tarotGameNew", ".plugin", signature='sa(ss)s') # args: room_jid, hand, profile - host.bridge.addSignal("tarotGameChooseContrat", ".plugin", signature='sss') # args: room_jid, xml_data, profile - host.bridge.addSignal("tarotGameShowCards", ".plugin", signature='ssa(ss)a{ss}s') # args: room_jid, type ["chien", "poignée",...], cards, data[dict], profile - host.bridge.addSignal("tarotGameCardsPlayed", ".plugin", signature='ssa(ss)s') # args: room_jid, player, type ["chien", "poignée",...], cards, data[dict], profile - host.bridge.addSignal("tarotGameYourTurn", ".plugin", signature='ss') # args: room_jid, profile - host.bridge.addSignal("tarotGameScore", ".plugin", signature='ssasass') # args: room_jid, xml_data, winners (list of nicks), loosers (list of nicks), profile - host.bridge.addSignal("tarotGameInvalidCards", ".plugin", signature='ssa(ss)a(ss)s') # args: room_jid, game phase, played_cards, invalid_cards, profile + RoomGame._init_( + self, + host, + PLUGIN_INFO, + (NS_CG, CG_TAG), + game_init={ + "hand_size": 18, + "init_player": 0, + "current_player": None, + "contrat": None, + "stage": None, + }, + player_init={"score": 0}, + ) + self.contrats = [ + _("Passe"), + _("Petite"), + _("Garde"), + _("Garde Sans"), + _("Garde Contre"), + ] + host.bridge.addMethod( + "tarotGameLaunch", + ".plugin", + in_sign="asss", + out_sign="", + method=self._prepareRoom, + async=True, + ) # args: players, room_jid, profile + host.bridge.addMethod( + "tarotGameCreate", + ".plugin", + in_sign="sass", + out_sign="", + method=self._createGame, + ) # args: room_jid, players, profile + host.bridge.addMethod( + "tarotGameReady", + ".plugin", + in_sign="sss", + out_sign="", + method=self._playerReady, + ) # args: player, referee, profile + host.bridge.addMethod( + "tarotGamePlayCards", + ".plugin", + in_sign="ssa(ss)s", + out_sign="", + method=self.play_cards, + ) # args: player, referee, cards, profile + host.bridge.addSignal( + "tarotGamePlayers", ".plugin", signature="ssass" + ) # args: room_jid, referee, players, profile + host.bridge.addSignal( + "tarotGameStarted", ".plugin", signature="ssass" + ) # args: room_jid, referee, players, profile + host.bridge.addSignal( + "tarotGameNew", ".plugin", signature="sa(ss)s" + ) # args: room_jid, hand, profile + host.bridge.addSignal( + "tarotGameChooseContrat", ".plugin", signature="sss" + ) # args: room_jid, xml_data, profile + host.bridge.addSignal( + "tarotGameShowCards", ".plugin", signature="ssa(ss)a{ss}s" + ) # args: room_jid, type ["chien", "poignée",...], cards, data[dict], profile + host.bridge.addSignal( + "tarotGameCardsPlayed", ".plugin", signature="ssa(ss)s" + ) # args: room_jid, player, type ["chien", "poignée",...], cards, data[dict], profile + host.bridge.addSignal( + "tarotGameYourTurn", ".plugin", signature="ss" + ) # args: room_jid, profile + host.bridge.addSignal( + "tarotGameScore", ".plugin", signature="ssasass" + ) # args: room_jid, xml_data, winners (list of nicks), loosers (list of nicks), profile + host.bridge.addSignal( + "tarotGameInvalidCards", ".plugin", signature="ssa(ss)a(ss)s" + ) # args: room_jid, game phase, played_cards, invalid_cards, profile self.deck_ordered = [] - for value in ['excuse'] + map(str, range(1, 22)): + for value in ["excuse"] + map(str, range(1, 22)): self.deck_ordered.append(TarotCard(("atout", value))) for suit in ["pique", "coeur", "carreau", "trefle"]: for value in map(str, range(1, 11)) + ["valet", "cavalier", "dame", "roi"]: self.deck_ordered.append(TarotCard((suit, value))) - self.__choose_contrat_id = host.registerCallback(self._contratChoosed, with_data=True) + self.__choose_contrat_id = host.registerCallback( + self._contratChoosed, with_data=True + ) self.__score_id = host.registerCallback(self._scoreShowed, with_data=True) def __card_list_to_xml(self, cards_list, elt_name): """Convert a card list to domish element""" cards_list_elt = domish.Element((None, elt_name)) for card in cards_list: - card_elt = domish.Element((None, 'card')) - card_elt['suit'] = card.suit - card_elt['value'] = card.value + card_elt = domish.Element((None, "card")) + card_elt["suit"] = card.suit + card_elt["value"] = card.value cards_list_elt.addChild(card_elt) return cards_list_elt @@ -98,14 +162,19 @@ """Convert a domish element with cards to a list of tuples""" cards_list = [] for card in cards_list_elt.elements(): - cards_list.append((card['suit'], card['value'])) + cards_list.append((card["suit"], card["value"])) return cards_list def __ask_contrat(self): """Create a element for asking contrat""" - contrat_elt = domish.Element((None, 'contrat')) - form = data_form.Form('form', title=_('contrat selection')) - field = data_form.Field('list-single', 'contrat', options=map(data_form.Option, self.contrats), required=True) + contrat_elt = domish.Element((None, "contrat")) + form = data_form.Form("form", title=_("contrat selection")) + field = data_form.Field( + "list-single", + "contrat", + options=map(data_form.Option, self.contrats), + required=True, + ) form.addField(field) contrat_elt.addChild(form.toElement()) return contrat_elt @@ -116,18 +185,18 @@ @param winners: list of unicode nicks of winners @param loosers: list of unicode nicks of loosers""" - score_elt = domish.Element((None, 'score')) - form = data_form.Form('form', title=_('scores')) - for line in scores.split('\n'): - field = data_form.Field('fixed', value=line) + score_elt = domish.Element((None, "score")) + form = data_form.Form("form", title=_("scores")) + for line in scores.split("\n"): + field = data_form.Field("fixed", value=line) form.addField(field) score_elt.addChild(form.toElement()) for winner in winners: - winner_elt = domish.Element((None, 'winner')) + winner_elt = domish.Element((None, "winner")) winner_elt.addContent(winner) score_elt.addChild(winner_elt) for looser in loosers: - looser_elt = domish.Element((None, 'looser')) + looser_elt = domish.Element((None, "looser")) looser_elt.addContent(looser) score_elt.addChild(looser_elt) return score_elt @@ -136,11 +205,11 @@ """Create a element for invalid_cards error @param list_cards: list of Card @param game_phase: phase of the game ['ecart', 'play']""" - error_elt = domish.Element((None, 'error')) - played_elt = self.__card_list_to_xml(played_cards, 'played') - invalid_elt = self.__card_list_to_xml(invalid_cards, 'invalid') - error_elt['type'] = 'invalid_cards' - error_elt['phase'] = game_phase + error_elt = domish.Element((None, "error")) + played_elt = self.__card_list_to_xml(played_cards, "played") + invalid_elt = self.__card_list_to_xml(invalid_cards, "invalid") + error_elt["type"] = "invalid_cards" + error_elt["phase"] = game_phase error_elt.addChild(played_elt) error_elt.addChild(invalid_elt) return error_elt @@ -150,23 +219,25 @@ @param next_pl: if given, then next_player is forced to this one """ if next_pl: - game_data['current_player'] = game_data['players'].index(next_pl) + game_data["current_player"] = game_data["players"].index(next_pl) return next_pl else: - pl_idx = game_data['current_player'] = (game_data['current_player'] + 1) % len(game_data['players']) - return game_data['players'][pl_idx] + pl_idx = game_data["current_player"] = ( + game_data["current_player"] + 1 + ) % len(game_data["players"]) + return game_data["players"][pl_idx] def __winner(self, game_data): """give the nick of the player who win this trick""" - players_data = game_data['players_data'] - first = game_data['first_player'] - first_idx = game_data['players'].index(first) + players_data = game_data["players_data"] + first = game_data["first_player"] + first_idx = game_data["players"].index(first) suit_asked = None strongest = None winner = None for idx in [(first_idx + i) % 4 for i in range(4)]: - player = game_data['players'][idx] - card = players_data[player]['played'] + player = game_data["players"][idx] + card = players_data[player]["played"] if card.value == "excuse": continue if suit_asked is None: @@ -183,22 +254,31 @@ @param played: cards currently on the table @param winner: nick of the trick winner""" # TODO: manage the case where excuse is played on the last trick (and lost) - players_data = game_data['players_data'] + players_data = game_data["players_data"] excuse = TarotCard(("atout", "excuse")) # we first check if the Excuse was already played # and if somebody is waiting for a card - for player in game_data['players']: - if players_data[player]['wait_for_low']: + for player in game_data["players"]: + if players_data[player]["wait_for_low"]: # the excuse owner has to give a card to somebody if winner == player: # the excuse owner win the trick, we check if we have something to give for card in played: if card.points == 0.5: - pl_waiting = players_data[player]['wait_for_low'] + pl_waiting = players_data[player]["wait_for_low"] played.remove(card) - players_data[pl_waiting]['levees'].append(card) - log.debug(_(u'Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation') % {"excuse_owner": player, "card_waited": card, "player_waiting": pl_waiting}) + players_data[pl_waiting]["levees"].append(card) + log.debug( + _( + u"Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation" + ) + % { + "excuse_owner": player, + "card_waited": card, + "player_waiting": pl_waiting, + } + ) return return @@ -207,8 +287,8 @@ return excuse_player = None # Who has played the Excuse ? - for player in game_data['players']: - if players_data[player]['played'] == excuse: + for player in game_data["players"]: + if players_data[player]["played"] == excuse: excuse_player = player break @@ -218,7 +298,7 @@ # first we remove the excuse from played cards played.remove(excuse) # then we give it back to the original owner - owner_levees = players_data[excuse_player]['levees'] + owner_levees = players_data[excuse_player]["levees"] owner_levees.append(excuse) # finally we give a low card to the trick winner low_card = None @@ -228,23 +308,43 @@ if owner_levees[card_idx].points == 0.5: low_card = owner_levees[card_idx] del owner_levees[card_idx] - players_data[winner]['levees'].append(low_card) - log.debug(_(u'Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation') % {"excuse_owner": excuse_player, "card_waited": low_card, "player_waiting": winner}) + players_data[winner]["levees"].append(low_card) + log.debug( + _( + u"Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation" + ) + % { + "excuse_owner": excuse_player, + "card_waited": low_card, + "player_waiting": winner, + } + ) break if not low_card: # The player has no low card yet # TODO: manage case when player never win a trick with low card - players_data[excuse_player]['wait_for_low'] = winner - log.debug(_(u"%(excuse_owner)s keep the Excuse but has not card to give, %(winner)s is waiting for one") % {'excuse_owner': excuse_player, 'winner': winner}) + players_data[excuse_player]["wait_for_low"] = winner + log.debug( + _( + u"%(excuse_owner)s keep the Excuse but has not card to give, %(winner)s is waiting for one" + ) + % {"excuse_owner": excuse_player, "winner": winner} + ) def __draw_game(self, game_data): """The game is draw, no score change @param game_data: data of the game @return: tuple with (string victory message, list of winners, list of loosers)""" - players_data = game_data['players_data'] - scores_str = _('Draw game') - scores_str += '\n' - for player in game_data['players']: - scores_str += _(u"\n--\n%(player)s:\nscore for this game ==> %(score_game)i\ntotal score ==> %(total_score)i") % {'player': player, 'score_game': 0, 'total_score': players_data[player]['score']} + players_data = game_data["players_data"] + scores_str = _("Draw game") + scores_str += "\n" + for player in game_data["players"]: + scores_str += _( + u"\n--\n%(player)s:\nscore for this game ==> %(score_game)i\ntotal score ==> %(total_score)i" + ) % { + "player": player, + "score_game": 0, + "total_score": players_data[player]["score"], + } log.debug(scores_str) return (scores_str, [], []) @@ -253,8 +353,8 @@ """The game is finished, time to know who won :) @param game_data: data of the game @return: tuple with (string victory message, list of winners, list of loosers)""" - players_data = game_data['players_data'] - levees = players_data[game_data['attaquant']]['levees'] + players_data = game_data["players_data"] + levees = players_data[game_data["attaquant"]]["levees"] score = 0 nb_bouts = 0 bouts = [] @@ -266,15 +366,15 @@ # We do a basic check on score calculation check_score = 0 - defenseurs = game_data['players'][:] - defenseurs.remove(game_data['attaquant']) + defenseurs = game_data["players"][:] + defenseurs.remove(game_data["attaquant"]) for defenseur in defenseurs: - for card in players_data[defenseur]['levees']: + for card in players_data[defenseur]["levees"]: check_score += card.points - if game_data['contrat'] == "Garde Contre": - for card in game_data['chien']: + if game_data["contrat"] == "Garde Contre": + for card in game_data["chien"]: check_score += card.points - assert (score + check_score == 91) + assert score + check_score == 91 point_limit = None if nb_bouts == 3: @@ -285,38 +385,61 @@ point_limit = 51 else: point_limit = 56 - if game_data['contrat'] == 'Petite': + if game_data["contrat"] == "Petite": contrat_mult = 1 - elif game_data['contrat'] == 'Garde': + elif game_data["contrat"] == "Garde": contrat_mult = 2 - elif game_data['contrat'] == 'Garde Sans': + elif game_data["contrat"] == "Garde Sans": contrat_mult = 4 - elif game_data['contrat'] == 'Garde Contre': + elif game_data["contrat"] == "Garde Contre": contrat_mult = 6 else: - log.error(_('INTERNAL ERROR: contrat not managed (mispelled ?)')) - assert(False) + log.error(_("INTERNAL ERROR: contrat not managed (mispelled ?)")) + assert False - victory = (score >= point_limit) + victory = score >= point_limit margin = abs(score - point_limit) points_defenseur = (margin + 25) * contrat_mult * (-1 if victory else 1) winners = [] loosers = [] player_score = {} - for player in game_data['players']: + for player in game_data["players"]: # TODO: adjust this for 3 and 5 players variants # TODO: manage bonuses (petit au bout, poignée, chelem) - player_score[player] = points_defenseur if player != game_data['attaquant'] else points_defenseur * -3 - players_data[player]['score'] += player_score[player] # we add score of this game to the global score + player_score[player] = ( + points_defenseur + if player != game_data["attaquant"] + else points_defenseur * -3 + ) + players_data[player]["score"] += player_score[ + player + ] # we add score of this game to the global score if player_score[player] > 0: winners.append(player) else: loosers.append(player) - scores_str = _(u'The attacker (%(attaquant)s) makes %(points)i and needs to make %(point_limit)i (%(nb_bouts)s oulder%(plural)s%(separator)s%(bouts)s): (s)he %(victory)s') % {'attaquant': game_data['attaquant'], 'points': score, 'point_limit': point_limit, 'nb_bouts': nb_bouts, 'plural': 's' if nb_bouts > 1 else '', 'separator': ': ' if nb_bouts != 0 else '', 'bouts': ','.join(map(str, bouts)), 'victory': 'wins' if victory else 'looses'} - scores_str += '\n' - for player in game_data['players']: - scores_str += _(u"\n--\n%(player)s:\nscore for this game ==> %(score_game)i\ntotal score ==> %(total_score)i") % {'player': player, 'score_game': player_score[player], 'total_score': players_data[player]['score']} + scores_str = _( + u"The attacker (%(attaquant)s) makes %(points)i and needs to make %(point_limit)i (%(nb_bouts)s oulder%(plural)s%(separator)s%(bouts)s): (s)he %(victory)s" + ) % { + "attaquant": game_data["attaquant"], + "points": score, + "point_limit": point_limit, + "nb_bouts": nb_bouts, + "plural": "s" if nb_bouts > 1 else "", + "separator": ": " if nb_bouts != 0 else "", + "bouts": ",".join(map(str, bouts)), + "victory": "wins" if victory else "looses", + } + scores_str += "\n" + for player in game_data["players"]: + scores_str += _( + u"\n--\n%(player)s:\nscore for this game ==> %(score_game)i\ntotal score ==> %(total_score)i" + ) % { + "player": player, + "score_game": player_score[player], + "total_score": players_data[player]["score"], + } log.debug(scores_str) return (scores_str, winners, loosers) @@ -327,35 +450,37 @@ @param cards: cards the player want to play @return forbidden_cards cards or empty list if cards are ok""" forbidden_cards = [] - if game_data['stage'] == 'ecart': + if game_data["stage"] == "ecart": for card in cards: if card.bout or card.value == "roi": forbidden_cards.append(card) # TODO: manage case where atouts (trumps) are in the dog - elif game_data['stage'] == 'play': + elif game_data["stage"] == "play": biggest_atout = None suit_asked = None - players = game_data['players'] - players_data = game_data['players_data'] - idx = players.index(game_data['first_player']) - current_idx = game_data['current_player'] + players = game_data["players"] + players_data = game_data["players_data"] + idx = players.index(game_data["first_player"]) + current_idx = game_data["current_player"] current_player = players[current_idx] if idx == current_idx: # the player is the first to play, he can play what he wants return forbidden_cards - while (idx != current_idx): + while idx != current_idx: player = players[idx] - played_card = players_data[player]['played'] + played_card = players_data[player]["played"] if not suit_asked and played_card.value != "excuse": suit_asked = played_card.suit if played_card.suit == "atout" and played_card > biggest_atout: biggest_atout = played_card idx = (idx + 1) % len(players) - has_suit = False # True if there is one card of the asked suit in the hand of the player + has_suit = ( + False + ) # True if there is one card of the asked suit in the hand of the player has_atout = False biggest_hand_atout = None - for hand_card in game_data['hand'][current_player]: + for hand_card in game_data["hand"][current_player]: if hand_card.suit == suit_asked: has_suit = True if hand_card.suit == "atout": @@ -371,19 +496,28 @@ if card.suit != suit_asked and card.suit != "atout" and has_atout: forbidden_cards.append(card) return forbidden_cards - if card.suit == "atout" and card < biggest_atout and biggest_hand_atout > biggest_atout and card.value != "excuse": + if ( + card.suit == "atout" + and card < biggest_atout + and biggest_hand_atout > biggest_atout + and card.value != "excuse" + ): forbidden_cards.append(card) else: - log.error(_('Internal error: unmanaged game stage')) + log.error(_("Internal error: unmanaged game stage")) return forbidden_cards def __start_play(self, room_jid, game_data, profile): """Start the game (tell to the first player after dealer to play""" - game_data['stage'] = "play" - next_player_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(game_data['players']) # the player after the dealer start - game_data['first_player'] = next_player = game_data['players'][next_player_idx] + game_data["stage"] = "play" + next_player_idx = game_data["current_player"] = ( + game_data["init_player"] + 1 + ) % len( + game_data["players"] + ) # the player after the dealer start + game_data["first_player"] = next_player = game_data["players"][next_player_idx] to_jid = jid.JID(room_jid.userhost() + "/" + next_player) # FIXME: gof: - self.send(to_jid, 'your_turn', profile=profile) + self.send(to_jid, "your_turn", profile=profile) def _contratChoosed(self, raw_data, profile): """Will be called when the contrat is selected @@ -397,13 +531,22 @@ # TODO: send error dialog return defer.succeed({}) - room_jid = session_data['room_jid'] - referee_jid = self.games[room_jid]['referee'] + room_jid = session_data["room_jid"] + referee_jid = self.games[room_jid]["referee"] player = self.host.plugins["XEP-0045"].getRoomNick(room_jid, profile) data = xml_tools.XMLUIResult2DataFormResult(raw_data) - contrat = data['contrat'] - log.debug(_(u'contrat [%(contrat)s] choosed by %(profile)s') % {'contrat': contrat, 'profile': profile}) - d = self.send(referee_jid, ('', 'contrat_choosed'), {'player': player}, content=contrat, profile=profile) + contrat = data["contrat"] + log.debug( + _(u"contrat [%(contrat)s] choosed by %(profile)s") + % {"contrat": contrat, "profile": profile} + ) + d = self.send( + referee_jid, + ("", "contrat_choosed"), + {"player": player}, + content=contrat, + profile=profile, + ) d.addCallback(lambda ignore: {}) del self._sessions[raw_data["session_id"]] return d @@ -420,7 +563,7 @@ # TODO: send error dialog return defer.succeed({}) - room_jid_s = session_data['room_jid'].userhost() + room_jid_s = session_data["room_jid"].userhost() # XXX: empty hand means to the frontend "reset the display"... self.host.bridge.tarotGameNew(room_jid_s, [], profile) del self._sessions[raw_data["session_id"]] @@ -437,38 +580,44 @@ if not profile: log.error(_(u"profile %s is unknown") % profile_key) return - log.debug(_(u'Cards played by %(profile)s: [%(cards)s]') % {'profile': profile, 'cards': cards}) - elem = self.__card_list_to_xml(TarotCard.from_tuples(cards), 'cards_played') - self.send(jid.JID(referee), elem, {'player': player}, profile=profile) + log.debug( + _(u"Cards played by %(profile)s: [%(cards)s]") + % {"profile": profile, "cards": cards} + ) + elem = self.__card_list_to_xml(TarotCard.from_tuples(cards), "cards_played") + self.send(jid.JID(referee), elem, {"player": player}, profile=profile) def newRound(self, room_jid, profile): game_data = self.games[room_jid] - players = game_data['players'] - game_data['first_player'] = None # first player for the current trick - game_data['contrat'] = None - common_data = {'contrat': None, - 'levees': [], # cards won - 'played': None, # card on the table - 'wait_for_low': None # Used when a player wait for a low card because of excuse - } + players = game_data["players"] + game_data["first_player"] = None # first player for the current trick + game_data["contrat"] = None + common_data = { + "contrat": None, + "levees": [], # cards won + "played": None, # card on the table + "wait_for_low": None, # Used when a player wait for a low card because of excuse + } - hand = game_data['hand'] = {} - hand_size = game_data['hand_size'] - chien = game_data['chien'] = [] + hand = game_data["hand"] = {} + hand_size = game_data["hand_size"] + chien = game_data["chien"] = [] deck = self.deck_ordered[:] random.shuffle(deck) for i in range(4): hand[players[i]] = deck[0:hand_size] del deck[0:hand_size] chien.extend(deck) - del(deck[:]) + del (deck[:]) msg_elts = {} for player in players: - msg_elts[player] = self.__card_list_to_xml(hand[player], 'hand') + msg_elts[player] = self.__card_list_to_xml(hand[player], "hand") RoomGame.newRound(self, room_jid, (common_data, msg_elts), profile) - pl_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(players) # the player after the dealer start + pl_idx = game_data["current_player"] = (game_data["init_player"] + 1) % len( + players + ) # the player after the dealer start player = players[pl_idx] to_jid = jid.JID(room_jid.userhost() + "/" + player) # FIXME: gof: self.send(to_jid, self.__ask_contrat(), profile=profile) @@ -478,52 +627,70 @@ @param mess_elt: instance of twisted.words.xish.domish.Element """ client = self.host.getClient(profile) - from_jid = jid.JID(mess_elt['from']) + from_jid = jid.JID(mess_elt["from"]) room_jid = jid.JID(from_jid.userhost()) nick = self.host.plugins["XEP-0045"].getRoomNick(client, room_jid) game_elt = mess_elt.firstChildElement() game_data = self.games[room_jid] is_player = self.isPlayer(room_jid, nick) - if 'players_data' in game_data: - players_data = game_data['players_data'] + if "players_data" in game_data: + players_data = game_data["players_data"] for elt in game_elt.elements(): - if not is_player and (elt.name not in ('started', 'players')): + if not is_player and (elt.name not in ("started", "players")): continue # user is in the room but not playing - if elt.name in ('started', 'players'): # new game created and/or players list updated + if elt.name in ( + "started", + "players", + ): # new game created and/or players list updated players = [] for player in elt.elements(): players.append(unicode(player)) - signal = self.host.bridge.tarotGameStarted if elt.name == 'started' else self.host.bridge.tarotGamePlayers + signal = ( + self.host.bridge.tarotGameStarted + if elt.name == "started" + else self.host.bridge.tarotGamePlayers + ) signal(room_jid.userhost(), from_jid.full(), players, profile) - elif elt.name == 'player_ready': # ready to play - player = elt['player'] - status = self.games[room_jid]['status'] - nb_players = len(self.games[room_jid]['players']) - status[player] = 'ready' - log.debug(_(u'Player %(player)s is ready to start [status: %(status)s]') % {'player': player, 'status': status}) - if status.values().count('ready') == nb_players: # everybody is ready, we can start the game + elif elt.name == "player_ready": # ready to play + player = elt["player"] + status = self.games[room_jid]["status"] + nb_players = len(self.games[room_jid]["players"]) + status[player] = "ready" + log.debug( + _(u"Player %(player)s is ready to start [status: %(status)s]") + % {"player": player, "status": status} + ) + if ( + status.values().count("ready") == nb_players + ): # everybody is ready, we can start the game self.newRound(room_jid, profile) - elif elt.name == 'hand': # a new hand has been received - self.host.bridge.tarotGameNew(room_jid.userhost(), self.__xml_to_list(elt), profile) + elif elt.name == "hand": # a new hand has been received + self.host.bridge.tarotGameNew( + room_jid.userhost(), self.__xml_to_list(elt), profile + ) - elif elt.name == 'contrat': # it's time to choose contrat + elif elt.name == "contrat": # it's time to choose contrat form = data_form.Form.fromElement(elt.firstChildElement()) session_id, session_data = self._sessions.newSession(profile=profile) session_data["room_jid"] = room_jid - xml_data = xml_tools.dataForm2XMLUI(form, self.__choose_contrat_id, session_id).toXml() - self.host.bridge.tarotGameChooseContrat(room_jid.userhost(), xml_data, profile) + xml_data = xml_tools.dataForm2XMLUI( + form, self.__choose_contrat_id, session_id + ).toXml() + self.host.bridge.tarotGameChooseContrat( + room_jid.userhost(), xml_data, profile + ) - elif elt.name == 'contrat_choosed': + elif elt.name == "contrat_choosed": # TODO: check we receive the contrat from the right person # TODO: use proper XEP-0004 way for answering form - player = elt['player'] - players_data[player]['contrat'] = unicode(elt) - contrats = [players_data[p]['contrat'] for p in game_data['players']] + player = elt["player"] + players_data[player]["contrat"] = unicode(elt) + contrats = [players_data[p]["contrat"] for p in game_data["players"]] if contrats.count(None): # not everybody has choosed his contrat, it's next one turn player = self.__next_player(game_data) @@ -531,8 +698,8 @@ self.send(to_jid, self.__ask_contrat(), profile=profile) else: best_contrat = [None, "Passe"] - for player in game_data['players']: - contrat = players_data[player]['contrat'] + for player in game_data["players"]: + contrat = players_data[player]["contrat"] idx_best = self.contrats.index(best_contrat[1]) idx_pl = self.contrats.index(contrat) if idx_pl > idx_best: @@ -541,132 +708,194 @@ if best_contrat[1] == "Passe": log.debug(_("Everybody is passing, round ended")) to_jid = jid.JID(room_jid.userhost()) - self.send(to_jid, self.__give_scores(*self.__draw_game(game_data)), profile=profile) - game_data['init_player'] = (game_data['init_player'] + 1) % len(game_data['players']) # we change the dealer - for player in game_data['players']: - game_data['status'][player] = "init" + self.send( + to_jid, + self.__give_scores(*self.__draw_game(game_data)), + profile=profile, + ) + game_data["init_player"] = (game_data["init_player"] + 1) % len( + game_data["players"] + ) # we change the dealer + for player in game_data["players"]: + game_data["status"][player] = "init" return - log.debug(_(u"%(player)s win the bid with %(contrat)s") % {'player': best_contrat[0], 'contrat': best_contrat[1]}) - game_data['contrat'] = best_contrat[1] + log.debug( + _(u"%(player)s win the bid with %(contrat)s") + % {"player": best_contrat[0], "contrat": best_contrat[1]} + ) + game_data["contrat"] = best_contrat[1] - if game_data['contrat'] == "Garde Sans" or game_data['contrat'] == "Garde Contre": + if ( + game_data["contrat"] == "Garde Sans" + or game_data["contrat"] == "Garde Contre" + ): self.__start_play(room_jid, game_data, profile) - game_data['attaquant'] = best_contrat[0] + game_data["attaquant"] = best_contrat[0] else: # Time to show the chien to everybody to_jid = jid.JID(room_jid.userhost()) # FIXME: gof: - elem = self.__card_list_to_xml(game_data['chien'], 'chien') - self.send(to_jid, elem, {'attaquant': best_contrat[0]}, profile=profile) + elem = self.__card_list_to_xml(game_data["chien"], "chien") + self.send( + to_jid, elem, {"attaquant": best_contrat[0]}, profile=profile + ) # the attacker (attaquant) get the chien - game_data['hand'][best_contrat[0]].extend(game_data['chien']) - del game_data['chien'][:] + game_data["hand"][best_contrat[0]].extend(game_data["chien"]) + del game_data["chien"][:] - if game_data['contrat'] == "Garde Sans": + if game_data["contrat"] == "Garde Sans": # The chien go into attaquant's (attacker) levees - players_data[best_contrat[0]]['levees'].extend(game_data['chien']) - del game_data['chien'][:] + players_data[best_contrat[0]]["levees"].extend(game_data["chien"]) + del game_data["chien"][:] - elif elt.name == 'chien': # we have received the chien + elif elt.name == "chien": # we have received the chien log.debug(_("tarot: chien received")) - data = {"attaquant": elt['attaquant']} - game_data['stage'] = "ecart" - game_data['attaquant'] = elt['attaquant'] - self.host.bridge.tarotGameShowCards(room_jid.userhost(), "chien", self.__xml_to_list(elt), data, profile) + data = {"attaquant": elt["attaquant"]} + game_data["stage"] = "ecart" + game_data["attaquant"] = elt["attaquant"] + self.host.bridge.tarotGameShowCards( + room_jid.userhost(), "chien", self.__xml_to_list(elt), data, profile + ) - elif elt.name == 'cards_played': - if game_data['stage'] == "ecart": + elif elt.name == "cards_played": + if game_data["stage"] == "ecart": # TODO: show atouts (trumps) if player put some in écart - assert (game_data['attaquant'] == elt['player']) # TODO: throw an xml error here + assert ( + game_data["attaquant"] == elt["player"] + ) # TODO: throw an xml error here list_cards = TarotCard.from_tuples(self.__xml_to_list(elt)) # we now check validity of card invalid_cards = self.__invalid_cards(game_data, list_cards) if invalid_cards: - elem = self.__invalid_cards_elt(list_cards, invalid_cards, game_data['stage']) - self.send(jid.JID(room_jid.userhost() + '/' + elt['player']), elem, profile=profile) + elem = self.__invalid_cards_elt( + list_cards, invalid_cards, game_data["stage"] + ) + self.send( + jid.JID(room_jid.userhost() + "/" + elt["player"]), + elem, + profile=profile, + ) return # FIXME: gof: manage Garde Sans & Garde Contre cases - players_data[elt['player']]['levees'].extend(list_cards) # we add the chien to attaquant's levées + players_data[elt["player"]]["levees"].extend( + list_cards + ) # we add the chien to attaquant's levées for card in list_cards: - game_data['hand'][elt['player']].remove(card) + game_data["hand"][elt["player"]].remove(card) self.__start_play(room_jid, game_data, profile) - elif game_data['stage'] == "play": - current_player = game_data['players'][game_data['current_player']] + elif game_data["stage"] == "play": + current_player = game_data["players"][game_data["current_player"]] cards = TarotCard.from_tuples(self.__xml_to_list(elt)) - if mess_elt['type'] == 'groupchat': - self.host.bridge.tarotGameCardsPlayed(room_jid.userhost(), elt['player'], self.__xml_to_list(elt), profile) + if mess_elt["type"] == "groupchat": + self.host.bridge.tarotGameCardsPlayed( + room_jid.userhost(), + elt["player"], + self.__xml_to_list(elt), + profile, + ) else: # we first check validity of card invalid_cards = self.__invalid_cards(game_data, cards) if invalid_cards: - elem = self.__invalid_cards_elt(cards, invalid_cards, game_data['stage']) - self.send(jid.JID(room_jid.userhost() + '/' + current_player), elem, profile=profile) + elem = self.__invalid_cards_elt( + cards, invalid_cards, game_data["stage"] + ) + self.send( + jid.JID(room_jid.userhost() + "/" + current_player), + elem, + profile=profile, + ) return # the card played is ok, we forward it to everybody # first we remove it from the hand and put in on the table - game_data['hand'][current_player].remove(cards[0]) - players_data[current_player]['played'] = cards[0] + game_data["hand"][current_player].remove(cards[0]) + players_data[current_player]["played"] = cards[0] # then we forward the message self.send(room_jid, elt, profile=profile) # Did everybody played ? - played = [players_data[player]['played'] for player in game_data['players']] + played = [ + players_data[player]["played"] + for player in game_data["players"] + ] if all(played): # everybody has played winner = self.__winner(game_data) - log.debug(_(u'The winner of this trick is %s') % winner) + log.debug(_(u"The winner of this trick is %s") % winner) # the winner win the trick self.__excuse_hack(game_data, played, winner) - players_data[elt['player']]['levees'].extend(played) + players_data[elt["player"]]["levees"].extend(played) # nothing left on the table - for player in game_data['players']: - players_data[player]['played'] = None - if len(game_data['hand'][current_player]) == 0: + for player in game_data["players"]: + players_data[player]["played"] = None + if len(game_data["hand"][current_player]) == 0: # no card left: the game is finished - elem = self.__give_scores(*self.__calculate_scores(game_data)) + elem = self.__give_scores( + *self.__calculate_scores(game_data) + ) self.send(room_jid, elem, profile=profile) - game_data['init_player'] = (game_data['init_player'] + 1) % len(game_data['players']) # we change the dealer - for player in game_data['players']: - game_data['status'][player] = "init" + game_data["init_player"] = ( + game_data["init_player"] + 1 + ) % len( + game_data["players"] + ) # we change the dealer + for player in game_data["players"]: + game_data["status"][player] = "init" return # next player is the winner - next_player = game_data['first_player'] = self.__next_player(game_data, winner) + next_player = game_data["first_player"] = self.__next_player( + game_data, winner + ) else: next_player = self.__next_player(game_data) # finally, we tell to the next player to play to_jid = jid.JID(room_jid.userhost() + "/" + next_player) - self.send(to_jid, 'your_turn', profile=profile) + self.send(to_jid, "your_turn", profile=profile) - elif elt.name == 'your_turn': + elif elt.name == "your_turn": self.host.bridge.tarotGameYourTurn(room_jid.userhost(), profile) - elif elt.name == 'score': - form_elt = elt.elements(name='x', uri='jabber:x:data').next() + elif elt.name == "score": + form_elt = elt.elements(name="x", uri="jabber:x:data").next() winners = [] loosers = [] - for winner in elt.elements(name='winner', uri=NS_CG): + for winner in elt.elements(name="winner", uri=NS_CG): winners.append(unicode(winner)) - for looser in elt.elements(name='looser', uri=NS_CG): + for looser in elt.elements(name="looser", uri=NS_CG): loosers.append(unicode(looser)) form = data_form.Form.fromElement(form_elt) session_id, session_data = self._sessions.newSession(profile=profile) session_data["room_jid"] = room_jid - xml_data = xml_tools.dataForm2XMLUI(form, self.__score_id, session_id).toXml() - self.host.bridge.tarotGameScore(room_jid.userhost(), xml_data, winners, loosers, profile) - elif elt.name == 'error': - if elt['type'] == 'invalid_cards': - played_cards = self.__xml_to_list(elt.elements(name='played', uri=NS_CG).next()) - invalid_cards = self.__xml_to_list(elt.elements(name='invalid', uri=NS_CG).next()) - self.host.bridge.tarotGameInvalidCards(room_jid.userhost(), elt['phase'], played_cards, invalid_cards, profile) + xml_data = xml_tools.dataForm2XMLUI( + form, self.__score_id, session_id + ).toXml() + self.host.bridge.tarotGameScore( + room_jid.userhost(), xml_data, winners, loosers, profile + ) + elif elt.name == "error": + if elt["type"] == "invalid_cards": + played_cards = self.__xml_to_list( + elt.elements(name="played", uri=NS_CG).next() + ) + invalid_cards = self.__xml_to_list( + elt.elements(name="invalid", uri=NS_CG).next() + ) + self.host.bridge.tarotGameInvalidCards( + room_jid.userhost(), + elt["phase"], + played_cards, + invalid_cards, + profile, + ) else: - log.error(_(u'Unmanaged error type: %s') % elt['type']) + log.error(_(u"Unmanaged error type: %s") % elt["type"]) else: - log.error(_(u'Unmanaged card game element: %s') % elt.name) + log.error(_(u"Unmanaged card game element: %s") % elt.name) def getSyncDataForPlayer(self, room_jid, nick): return []
--- a/sat/plugins/plugin_misc_text_commands.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_text_commands.py Wed Jun 27 20:14:46 2018 +0200 @@ -23,6 +23,7 @@ from twisted.words.protocols.jabber import jid from twisted.internet import defer from sat.core.log import getLogger + log = getLogger(__name__) from twisted.python import failure from collections import OrderedDict @@ -35,26 +36,29 @@ C.PI_DEPENDENCIES: [], C.PI_MAIN: "TextCommands", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""IRC like text commands""") + C.PI_DESCRIPTION: _("""IRC like text commands"""), } class InvalidCommandSyntax(Exception): """Throwed while parsing @command in docstring if syntax is invalid""" + pass CMD_KEY = "@command" -CMD_TYPES = ('group', 'one2one', 'all') +CMD_TYPES = ("group", "one2one", "all") FEEDBACK_INFO_TYPE = "TEXT_CMD" class TextCommands(object): - #FIXME: doc strings for commands have to be translatable + # FIXME: doc strings for commands have to be translatable # plugins need a dynamic translation system (translation # should be downloadable independently) - HELP_SUGGESTION = _("Type '/help' to get a list of the available commands. If you didn't want to use a command, please start your message with '//' to escape the slash.") + HELP_SUGGESTION = _( + "Type '/help' to get a list of the available commands. If you didn't want to use a command, please start your message with '//' to escape the slash." + ) def __init__(self, host): log.info(_("Text commands initialization")) @@ -78,9 +82,7 @@ - "args" (default: ""): the arguments available, using syntax specified in documentation. - "doc_arg_[name]": the doc of [name] argument """ - data = OrderedDict([('doc_short_help', ""), - ('type', 'all'), - ('args', '')]) + data = OrderedDict([("doc_short_help", ""), ("type", "all"), ("args", "")]) docstring = cmd.__doc__ if docstring is None: log.warning(u"Not docstring found for command {}".format(cmd_name)) @@ -90,18 +92,22 @@ data["doc_short_help"] = doc_data[0] try: - cmd_indent = 0 # >0 when @command is found are we are parsing it + cmd_indent = 0 # >0 when @command is found are we are parsing it for line in doc_data: stripped = line.strip() - if cmd_indent and line[cmd_indent:cmd_indent+5] == " -": + if cmd_indent and line[cmd_indent : cmd_indent + 5] == " -": colon_idx = line.find(":") if colon_idx == -1: - raise InvalidCommandSyntax("No colon found in argument description") - arg_name = line[cmd_indent+6:colon_idx].strip() + raise InvalidCommandSyntax( + "No colon found in argument description" + ) + arg_name = line[cmd_indent + 6 : colon_idx].strip() if not arg_name: - raise InvalidCommandSyntax("No name found in argument description") - arg_help = line[colon_idx+1:].strip() + raise InvalidCommandSyntax( + "No name found in argument description" + ) + arg_help = line[colon_idx + 1 :].strip() data["doc_arg_{}".format(arg_name)] = arg_help elif cmd_indent: # we are parsing command and indent level is not good, it's finished @@ -113,31 +119,36 @@ colon_idx = stripped.find(":") if colon_idx == -1: raise InvalidCommandSyntax("missing colon") - type_data = stripped[len(CMD_KEY):colon_idx].strip() + type_data = stripped[len(CMD_KEY) : colon_idx].strip() if len(type_data) == 0: - type_data="(all)" - elif len(type_data) <= 2 or type_data[0] != '(' or type_data[-1] != ')': + type_data = "(all)" + elif ( + len(type_data) <= 2 or type_data[0] != "(" or type_data[-1] != ")" + ): raise InvalidCommandSyntax("Bad type data syntax") type_ = type_data[1:-1] if type_ not in CMD_TYPES: raise InvalidCommandSyntax("Unknown type {}".format(type_)) data["type"] = type_ - #args - data["args"] = stripped[colon_idx+1:].strip() + # args + data["args"] = stripped[colon_idx + 1 :].strip() except InvalidCommandSyntax as e: - log.warning(u"Invalid command syntax for command {command}: {message}".format(command=cmd_name, message=e.message)) + log.warning( + u"Invalid command syntax for command {command}: {message}".format( + command=cmd_name, message=e.message + ) + ) return data - def registerTextCommands(self, instance): """ Add a text command @param instance: instance of a class containing text commands """ for attr in dir(instance): - if attr.startswith('cmd_'): + if attr.startswith("cmd_"): cmd = getattr(instance, attr) if not callable(cmd): log.warning(_(u"Skipping not callable [%s] attribute") % attr) @@ -146,13 +157,19 @@ if not cmd_name: log.warning(_("Skipping cmd_ method")) if cmd_name in self._commands: - suff=2 + suff = 2 while (cmd_name + str(suff)) in self._commands: - suff+=1 + suff += 1 new_name = cmd_name + str(suff) - log.warning(_(u"Conflict for command [{old_name}], renaming it to [{new_name}]").format(old_name=cmd_name, new_name=new_name)) + log.warning( + _( + u"Conflict for command [{old_name}], renaming it to [{new_name}]" + ).format(old_name=cmd_name, new_name=new_name) + ) cmd_name = new_name - self._commands[cmd_name] = cmd_data = OrderedDict({'callback':cmd}) # We use an Ordered dict to keep documenation order + self._commands[cmd_name] = cmd_data = OrderedDict( + {"callback": cmd} + ) # We use an Ordered dict to keep documenation order cmd_data.update(self._parseDocString(cmd, cmd_name)) log.info(_("Registered text command [%s]") % cmd_name) @@ -168,7 +185,9 @@ self._whois.append((priority, callback)) self._whois.sort(key=lambda item: item[0], reverse=True) - def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments): + def sendMessageTrigger( + self, client, mess_data, pre_xml_treatments, post_xml_treatments + ): """Install SendMessage command hook """ pre_xml_treatments.addCallback(self._sendMessageCmdHook, client) return True @@ -185,8 +204,8 @@ @param profile: %(doc_profile)s """ try: - msg = mess_data["message"][''] - msg_lang = '' + msg = mess_data["message"][""] + msg_lang = "" except KeyError: try: # we have not default message, we try to take the first found @@ -196,18 +215,18 @@ return mess_data try: - if msg[:2] == '//': + if msg[:2] == "//": # we have a double '/', it's the escape sequence mess_data["message"][msg_lang] = msg[1:] return mess_data - if msg[0] != '/': + if msg[0] != "/": return mess_data except IndexError: return mess_data # we have a command d = None - command = msg[1:].partition(' ')[0].lower() + command = msg[1:].partition(" ")[0].lower() if command.isalpha(): # looks like an actual command, we try to call the corresponding method def retHandling(ret): @@ -229,19 +248,33 @@ self.feedBack(client, u"Command failed {}".format(msg), mess_data) return False - mess_data["unparsed"] = msg[1 + len(command):] # part not yet parsed of the message + mess_data["unparsed"] = msg[ + 1 + len(command) : + ] # part not yet parsed of the message try: cmd_data = self._commands[command] except KeyError: - self.feedBack(client, _("Unknown command /%s. ") % command + self.HELP_SUGGESTION, mess_data) + self.feedBack( + client, + _("Unknown command /%s. ") % command + self.HELP_SUGGESTION, + mess_data, + ) log.debug("text command help message") raise failure.Failure(exceptions.CancelError()) else: if not self._contextValid(mess_data, cmd_data): # The command is not launched in the right context, we throw a message with help instructions - context_txt = _("group discussions") if cmd_data["type"] == "group" else _("one to one discussions") - feedback = _("/{command} command only applies in {context}.").format(command=command, context=context_txt) - self.feedBack(client, u"{} {}".format(feedback, self.HELP_SUGGESTION), mess_data) + context_txt = ( + _("group discussions") + if cmd_data["type"] == "group" + else _("one to one discussions") + ) + feedback = _("/{command} command only applies in {context}.").format( + command=command, context=context_txt + ) + self.feedBack( + client, u"{} {}".format(feedback, self.HELP_SUGGESTION), mess_data + ) log.debug("text command invalid message") raise failure.Failure(exceptions.CancelError()) else: @@ -249,7 +282,9 @@ d.addErrback(genericErrback) d.addCallback(retHandling) - return d or mess_data # if a command is detected, we should have a deferred, else we send the message normally + return ( + d or mess_data + ) # if a command is detected, we should have a deferred, else we send the message normally def _contextValid(self, mess_data, cmd_data): """Tell if a command can be used in the given context @@ -258,8 +293,9 @@ @param cmd_data(dict): command data as returned by self._parseDocString @return (bool): True if command can be used in this context """ - if ((cmd_data['type'] == "group" and mess_data["type"] != "groupchat") or - (cmd_data['type'] == 'one2one' and mess_data["type"] == "groupchat")): + if (cmd_data["type"] == "group" and mess_data["type"] != "groupchat") or ( + cmd_data["type"] == "one2one" and mess_data["type"] == "groupchat" + ): return False return True @@ -270,16 +306,16 @@ or a shortcut (e.g.: sat or sat@ for sat on current service) @param service_jid: jid of the current service (e.g.: chat.jabberfr.org) """ - nb_arobas = arg.count('@') + nb_arobas = arg.count("@") if nb_arobas == 1: - if arg[-1] != '@': + if arg[-1] != "@": return jid.JID(arg) return jid.JID(arg + service_jid) return jid.JID(u"%s@%s" % (arg, service_jid)) def feedBack(self, client, message, mess_data, info_type=FEEDBACK_INFO_TYPE): """Give a message back to the user""" - if mess_data["type"] == 'groupchat': + if mess_data["type"] == "groupchat": to_ = mess_data["to"].userhostJID() else: to_ = client.jid @@ -288,7 +324,7 @@ mess_data["from"] = mess_data["to"] mess_data["to"] = to_ mess_data["type"] = C.MESS_TYPE_INFO - mess_data["message"] = {'': message} + mess_data["message"] = {"": message} mess_data["extra"]["info_type"] = info_type client.messageSendToBridge(mess_data) @@ -303,7 +339,7 @@ entity = mess_data["unparsed"].strip() - if mess_data['type'] == "groupchat": + if mess_data["type"] == "groupchat": room = mess_data["to"].userhostJID() try: if self.host.plugins["XEP-0045"].isNickInRoom(client, room, entity): @@ -325,11 +361,13 @@ if not target_jid.resource: target_jid.resource = self.host.memory.getMainResource(client, target_jid) - whois_msg = [_(u"whois for %(jid)s") % {'jid': target_jid}] + whois_msg = [_(u"whois for %(jid)s") % {"jid": target_jid}] d = defer.succeed(None) for ignore, callback in self._whois: - d.addCallback(lambda ignore: callback(client, whois_msg, mess_data, target_jid)) + d.addCallback( + lambda ignore: callback(client, whois_msg, mess_data, target_jid) + ) def feedBack(ignore): self.feedBack(client, u"\n".join(whois_msg), mess_data) @@ -348,7 +386,9 @@ for doc_name, doc_help in cmd_data.iteritems(): if doc_name.startswith("doc_arg_"): arg_name = doc_name[8:] - strings.append(u"- {name}: {doc_help}".format(name=arg_name, doc_help=_(doc_help))) + strings.append( + u"- {name}: {doc_help}".format(name=arg_name, doc_help=_(doc_help)) + ) return strings @@ -376,7 +416,9 @@ if cmd_name and cmd_name[0] == "/": cmd_name = cmd_name[1:] if cmd_name and cmd_name not in self._commands: - self.feedBack(client, _(u"Invalid command name [{}]\n".format(cmd_name)), mess_data) + self.feedBack( + client, _(u"Invalid command name [{}]\n".format(cmd_name)), mess_data + ) cmd_name = "" if not cmd_name: # we show the global help @@ -387,22 +429,27 @@ cmd_data = self._commands[command] if not self._contextValid(mess_data, cmd_data): continue - spaces = (longuest - len(command)) * ' ' - help_cmds.append(" /{command}: {spaces} {short_help}".format( - command=command, - spaces=spaces, - short_help=cmd_data["doc_short_help"] - )) + spaces = (longuest - len(command)) * " " + help_cmds.append( + " /{command}: {spaces} {short_help}".format( + command=command, + spaces=spaces, + short_help=cmd_data["doc_short_help"], + ) + ) - help_mess = _(u"Text commands available:\n%s") % (u'\n'.join(help_cmds), ) + help_mess = _(u"Text commands available:\n%s") % (u"\n".join(help_cmds),) else: # we show detailled help for a command cmd_data = self._commands[cmd_name] syntax = cmd_data["args"] help_mess = _(u"/{name}: {short_help}\n{syntax}{args_help}").format( name=cmd_name, - short_help=cmd_data['doc_short_help'], - syntax=_(" "*4+"syntax: {}\n").format(syntax) if syntax else "", - args_help=u'\n'.join([u" "*8+"{}".format(line) for line in self._getArgsHelp(cmd_data)])) + short_help=cmd_data["doc_short_help"], + syntax=_(" " * 4 + "syntax: {}\n").format(syntax) if syntax else "", + args_help=u"\n".join( + [u" " * 8 + "{}".format(line) for line in self._getArgsHelp(cmd_data)] + ), + ) self.feedBack(client, help_mess, mess_data)
--- a/sat/plugins/plugin_misc_text_syntaxes.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_text_syntaxes.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,16 +20,20 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.internet import defer from twisted.internet.threads import deferToThread from sat.core import exceptions + try: from lxml import html from lxml.html import clean except ImportError: - raise exceptions.MissingModule(u"Missing module lxml, please download/install it from http://lxml.de/") + raise exceptions.MissingModule( + u"Missing module lxml, please download/install it from http://lxml.de/" + ) from cgi import escape import re @@ -41,15 +45,69 @@ # TODO: check/adapt following list # list initialy based on feedparser list (http://pythonhosted.org/feedparser/html-sanitization.html) -STYLES_WHITELIST = ("azimuth", "background-color", "border-bottom-color", "border-collapse", "border-color", "border-left-color", "border-right-color", "border-top-color", "clear", "color", "cursor", "direction", "display", "elevation", "float", "font", "font-family", "font-size", "font-style", "font-variant", "font-weight", "height", "letter-spacing", "line-height", "overflow", "pause", "pause-after", "pause-before", "pitch", "pitch-range", "richness", "speak", "speak-header", "speak-numeral", "speak-punctuation", "speech-rate", "stress", "text-align", "text-decoration", "text-indent", "unicode-bidi", "vertical-align", "voice-family", "volume", "white-space", "width") +STYLES_WHITELIST = ( + "azimuth", + "background-color", + "border-bottom-color", + "border-collapse", + "border-color", + "border-left-color", + "border-right-color", + "border-top-color", + "clear", + "color", + "cursor", + "direction", + "display", + "elevation", + "float", + "font", + "font-family", + "font-size", + "font-style", + "font-variant", + "font-weight", + "height", + "letter-spacing", + "line-height", + "overflow", + "pause", + "pause-after", + "pause-before", + "pitch", + "pitch-range", + "richness", + "speak", + "speak-header", + "speak-numeral", + "speak-punctuation", + "speech-rate", + "stress", + "text-align", + "text-decoration", + "text-indent", + "unicode-bidi", + "vertical-align", + "voice-family", + "volume", + "white-space", + "width", +) -SAFE_ATTRS = html.defs.safe_attrs.union(('style', 'poster', 'controls')) -STYLES_VALUES_REGEX = r'^(' + '|'.join(['([a-z-]+)', # alphabetical names - '(#[0-9a-f]+)', # hex value - '(\d+(.\d+)? *(|%|em|ex|px|in|cm|mm|pt|pc))', # values with units (or not) - 'rgb\( *((\d+(.\d+)?), *){2}(\d+(.\d+)?) *\)', # rgb function - 'rgba\( *((\d+(.\d+)?), *){3}(\d+(.\d+)?) *\)', # rgba function - ]) + ') *(!important)?$' # we accept "!important" at the end +SAFE_ATTRS = html.defs.safe_attrs.union(("style", "poster", "controls")) +STYLES_VALUES_REGEX = ( + r"^(" + + "|".join( + [ + "([a-z-]+)", # alphabetical names + "(#[0-9a-f]+)", # hex value + "(\d+(.\d+)? *(|%|em|ex|px|in|cm|mm|pt|pc))", # values with units (or not) + "rgb\( *((\d+(.\d+)?), *){2}(\d+(.\d+)?) *\)", # rgb function + "rgba\( *((\d+(.\d+)?), *){3}(\d+(.\d+)?) *\)", # rgba function + ] + ) + + ") *(!important)?$" +) # we accept "!important" at the end STYLES_ACCEPTED_VALUE = re.compile(STYLES_VALUES_REGEX) PLUGIN_INFO = { @@ -60,7 +118,9 @@ C.PI_DEPENDENCIES: [], C.PI_MAIN: "TextSyntaxes", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Management of various text syntaxes (XHTML-IM, Markdown, etc)""") + C.PI_DESCRIPTION: _( + """Management of various text syntaxes (XHTML-IM, Markdown, etc)""" + ), } @@ -91,36 +151,60 @@ """ params_data = { - 'category_name': CATEGORY, - 'category_label': _(CATEGORY), - 'name': NAME, - 'label': _(NAME), - 'syntaxes': syntaxes, - } + "category_name": CATEGORY, + "category_label": _(CATEGORY), + "name": NAME, + "label": _(NAME), + "syntaxes": syntaxes, + } def __init__(self, host): log.info(_("Text syntaxes plugin initialization")) self.host = host - self.addSyntax(self.SYNTAX_XHTML, lambda xhtml: defer.succeed(xhtml), lambda xhtml: defer.succeed(xhtml), - TextSyntaxes.OPT_NO_THREAD) + self.addSyntax( + self.SYNTAX_XHTML, + lambda xhtml: defer.succeed(xhtml), + lambda xhtml: defer.succeed(xhtml), + TextSyntaxes.OPT_NO_THREAD, + ) # TODO: text => XHTML should add <a/> to url like in frontends # it's probably best to move sat_frontends.tools.strings to sat.tools.common or similar - self.addSyntax(self.SYNTAX_TEXT, lambda text: escape(text), lambda xhtml: self._removeMarkups(xhtml), [TextSyntaxes.OPT_HIDDEN]) + self.addSyntax( + self.SYNTAX_TEXT, + lambda text: escape(text), + lambda xhtml: self._removeMarkups(xhtml), + [TextSyntaxes.OPT_HIDDEN], + ) try: import markdown, html2text - def _html2text(html, baseurl=''): + def _html2text(html, baseurl=""): h = html2text.HTML2Text(baseurl=baseurl) h.body_width = 0 # do not truncate the lines, it breaks the long URLs return h.handle(html) - self.addSyntax(self.SYNTAX_MARKDOWN, markdown.markdown, _html2text, [TextSyntaxes.OPT_DEFAULT]) + + self.addSyntax( + self.SYNTAX_MARKDOWN, + markdown.markdown, + _html2text, + [TextSyntaxes.OPT_DEFAULT], + ) except ImportError: log.warning(u"markdown or html2text not found, can't use Markdown syntax") - log.info(u"You can download/install them from https://pythonhosted.org/Markdown/ and https://github.com/Alir3z4/html2text/") - host.bridge.addMethod("syntaxConvert", ".plugin", in_sign='sssbs', out_sign='s', - async=True, method=self.convert) - host.bridge.addMethod("syntaxGet", ".plugin", in_sign='s', out_sign='s', - method=self.getSyntax) + log.info( + u"You can download/install them from https://pythonhosted.org/Markdown/ and https://github.com/Alir3z4/html2text/" + ) + host.bridge.addMethod( + "syntaxConvert", + ".plugin", + in_sign="sssbs", + out_sign="s", + async=True, + method=self.convert, + ) + host.bridge.addMethod( + "syntaxGet", ".plugin", in_sign="s", out_sign="s", method=self.getSyntax + ) def _updateParamOptions(self): data_synt = TextSyntaxes.syntaxes @@ -136,10 +220,10 @@ options = [] for syntax in syntaxes: - selected = 'selected="true"' if syntax == default_synt else '' + selected = 'selected="true"' if syntax == default_synt else "" options.append(u'<option value="%s" %s/>' % (syntax, selected)) - TextSyntaxes.params_data["options"] = u'\n'.join(options) + TextSyntaxes.params_data["options"] = u"\n".join(options) self.host.memory.updateParams(TextSyntaxes.params % TextSyntaxes.params_data) def getCurrentSyntax(self, profile): @@ -148,16 +232,19 @@ @param profile: %(doc_profile)s @return: profile selected syntax """ - return self.host.memory.getParamA(NAME, CATEGORY , profile_key=profile) + return self.host.memory.getParamA(NAME, CATEGORY, profile_key=profile) def _logError(self, failure, action=u"converting syntax"): - log.error(u"Error while {action}: {failure}".format(action=action, failure=failure)) + log.error( + u"Error while {action}: {failure}".format(action=action, failure=failure) + ) return failure def cleanXHTML(self, xhtml): """ Clean XHTML text by removing potentially dangerous/malicious parts @param xhtml: raw xhtml text to clean (or lxml's HtmlElement) """ + def blocking_cleaning(xhtml): """ Clean XHTML and style attributes """ @@ -168,7 +255,7 @@ cleaned_styles = [] for style in styles: try: - key, value = style.split(':') + key, value = style.split(":") except ValueError: continue key = key.lower().strip() @@ -180,7 +267,9 @@ if value == "none": continue cleaned_styles.append((key, value)) - return "; ".join(["%s: %s" % (key_, value_) for key_, value_ in cleaned_styles]) + return "; ".join( + ["%s: %s" % (key_, value_) for key_, value_ in cleaned_styles] + ) if isinstance(xhtml, basestring): xhtml_elt = html.fromstring(xhtml) @@ -189,19 +278,21 @@ else: log.error("Only strings and HtmlElements can be cleaned") raise exceptions.DataError - cleaner = clean.Cleaner(style=False, - add_nofollow=False, - safe_attrs=SAFE_ATTRS) + cleaner = clean.Cleaner( + style=False, add_nofollow=False, safe_attrs=SAFE_ATTRS + ) xhtml_elt = cleaner.clean_html(xhtml_elt) for elt in xhtml_elt.xpath("//*[@style]"): - elt.set("style", clean_style(elt.get('style'))) - return html.tostring(xhtml_elt, encoding=unicode, method='xml') + elt.set("style", clean_style(elt.get("style"))) + return html.tostring(xhtml_elt, encoding=unicode, method="xml") d = deferToThread(blocking_cleaning, xhtml) d.addErrback(self._logError, action=u"cleaning syntax") return d - def convert(self, text, syntax_from, syntax_to=_SYNTAX_XHTML, safe=True, profile=None): + def convert( + self, text, syntax_from, syntax_to=_SYNTAX_XHTML, safe=True, profile=None + ): """Convert a text between two syntaxes @param text: text to convert @@ -235,7 +326,7 @@ else: d = deferToThread(syntaxes[syntax_from]["to"], text) - #TODO: keep only body element and change it to a div here ? + # TODO: keep only body element and change it to a div here ? if safe: d.addCallback(self.cleanXHTML) @@ -249,7 +340,7 @@ d.addCallback(lambda text: text.rstrip()) return d - def addSyntax(self, name, to_xhtml_cb, from_xhtml_cb, flags = None): + def addSyntax(self, name, to_xhtml_cb, from_xhtml_cb, flags=None): """Add a new syntax to the manager @param name: unique name of the syntax @@ -262,13 +353,24 @@ """ flags = flags if flags is not None else [] if TextSyntaxes.OPT_HIDDEN in flags and TextSyntaxes.OPT_DEFAULT in flags: - raise ValueError(u"{} and {} are mutually exclusive".format(TextSyntaxes.OPT_HIDDEN, TextSyntaxes.OPT_DEFAULT)) + raise ValueError( + u"{} and {} are mutually exclusive".format( + TextSyntaxes.OPT_HIDDEN, TextSyntaxes.OPT_DEFAULT + ) + ) syntaxes = TextSyntaxes.syntaxes key = name.lower().strip() if key in syntaxes: - raise exceptions.ConflictError(u"This syntax key already exists: {}".format(key)) - syntaxes[key] = {"name": name, "to": to_xhtml_cb, "from": from_xhtml_cb, "flags": flags} + raise exceptions.ConflictError( + u"This syntax key already exists: {}".format(key) + ) + syntaxes[key] = { + "name": name, + "to": to_xhtml_cb, + "from": from_xhtml_cb, + "flags": flags, + } if TextSyntaxes.OPT_DEFAULT in flags: TextSyntaxes.default_syntaxe = key @@ -290,6 +392,6 @@ @param xhtml: the XHTML string to be cleaned @return: the cleaned string """ - cleaner = clean.Cleaner(kill_tags=['style']) + cleaner = clean.Cleaner(kill_tags=["style"]) cleaned = cleaner.clean_html(html.fromstring(xhtml)) return html.tostring(cleaned, encoding=unicode, method="text")
--- a/sat/plugins/plugin_misc_tickets.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_tickets.py Wed Jun 27 20:14:46 2018 +0200 @@ -24,9 +24,10 @@ from sat.tools import utils import shortuuid from sat.core.log import getLogger + log = getLogger(__name__) -NS_TICKETS = 'org.salut-a-toi.tickets:0' +NS_TICKETS = "org.salut-a-toi.tickets:0" PLUGIN_INFO = { C.PI_NAME: _("Tickets management"), @@ -36,49 +37,84 @@ C.PI_DEPENDENCIES: ["XEP-0060", "PUBSUB_SCHEMA", "XEP-0277", "IDENTITY"], C.PI_MAIN: "Tickets", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Tickets management plugin""") + C.PI_DESCRIPTION: _("""Tickets management plugin"""), } class Tickets(object): - def __init__(self, host): log.info(_(u"Tickets plugin initialization")) self.host = host - host.registerNamespace('tickets', NS_TICKETS) + host.registerNamespace("tickets", NS_TICKETS) self._p = self.host.plugins["XEP-0060"] self._s = self.host.plugins["PUBSUB_SCHEMA"] self._m = self.host.plugins["XEP-0277"] - host.bridge.addMethod("ticketsGet", ".plugin", - in_sign='ssiassa{ss}s', out_sign='(asa{ss})', - method=utils.partial( - self._s._get, - default_node=NS_TICKETS, - form_ns=NS_TICKETS, - filters = {u'author': self._s.valueOrPublisherFilter, - u'created': self._s.dateFilter, - u'updated': self._s.dateFilter, - }), + host.bridge.addMethod( + "ticketsGet", + ".plugin", + in_sign="ssiassa{ss}s", + out_sign="(asa{ss})", + method=utils.partial( + self._s._get, + default_node=NS_TICKETS, + form_ns=NS_TICKETS, + filters={ + u"author": self._s.valueOrPublisherFilter, + u"created": self._s.dateFilter, + u"updated": self._s.dateFilter, + }, + ), + async=True, + ) + host.bridge.addMethod( + "ticketSet", + ".plugin", + in_sign="ssa{sas}ssa{ss}s", + out_sign="s", + method=self._set, + async=True, + ) + host.bridge.addMethod( + "ticketsSchemaGet", + ".plugin", + in_sign="sss", + out_sign="s", + method=utils.partial(self._s._getUISchema, default_node=NS_TICKETS), + async=True, + ) - async=True - ) - host.bridge.addMethod("ticketSet", ".plugin", - in_sign='ssa{sas}ssa{ss}s', out_sign='s', - method=self._set, - async=True) - host.bridge.addMethod("ticketsSchemaGet", ".plugin", - in_sign='sss', out_sign='s', - method=utils.partial(self._s._getUISchema, default_node=NS_TICKETS), - async=True) - - def _set(self, service, node, values, schema=None, item_id=None, extra=None, profile_key=C.PROF_KEY_NONE): - client, service, node, schema, item_id, extra = self._s.prepareBridgeSet(service, node, schema, item_id, extra, profile_key) - d = self.set(client, service, node, values, schema, item_id, extra, deserialise=True) - d.addCallback(lambda ret: ret or u'') + def _set( + self, + service, + node, + values, + schema=None, + item_id=None, + extra=None, + profile_key=C.PROF_KEY_NONE, + ): + client, service, node, schema, item_id, extra = self._s.prepareBridgeSet( + service, node, schema, item_id, extra, profile_key + ) + d = self.set( + client, service, node, values, schema, item_id, extra, deserialise=True + ) + d.addCallback(lambda ret: ret or u"") return d @defer.inlineCallbacks - def set(self, client, service, node, values, schema=None, item_id=None, extra=None, deserialise=False, form_ns=NS_TICKETS): + def set( + self, + client, + service, + node, + values, + schema=None, + item_id=None, + extra=None, + deserialise=False, + form_ns=NS_TICKETS, + ): """Publish a tickets @param node(unicode, None): Pubsub node to use @@ -102,15 +138,25 @@ # we need to use uuid for comments node, because we don't know item id in advance # (we don't want to set it ourselves to let the server choose, so we can have # a nicer id if serial ids is activated) - comments_node = self._m.getCommentsNode(node + u'_' + unicode(shortuuid.uuid())) - options = {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN, - self._p.OPT_PERSIST_ITEMS: 1, - self._p.OPT_MAX_ITEMS: -1, - self._p.OPT_DELIVER_PAYLOADS: 1, - self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, - self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN, - } + comments_node = self._m.getCommentsNode( + node + u"_" + unicode(shortuuid.uuid()) + ) + options = { + self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN, + self._p.OPT_PERSIST_ITEMS: 1, + self._p.OPT_MAX_ITEMS: -1, + self._p.OPT_DELIVER_PAYLOADS: 1, + self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, + self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN, + } yield self._p.createNode(client, comments_service, comments_node, options) - values['comments_uri'] = uri.buildXMPPUri(u'pubsub', subtype='microblog', path=comments_service.full(), node=comments_node) - item_id = yield self._s.set(client, service, node, values, schema, item_id, extra, deserialise, form_ns) + values["comments_uri"] = uri.buildXMPPUri( + u"pubsub", + subtype="microblog", + path=comments_service.full(), + node=comments_node, + ) + item_id = yield self._s.set( + client, service, node, values, schema, item_id, extra, deserialise, form_ns + ) defer.returnValue(item_id)
--- a/sat/plugins/plugin_misc_upload.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_upload.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from sat.tools import xml_tools @@ -35,13 +36,13 @@ C.PI_TYPE: C.PLUG_TYPE_MISC, C.PI_MAIN: "UploadPlugin", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""File upload management""") + C.PI_DESCRIPTION: _("""File upload management"""), } -UPLOADING = D_(u'Please select a file to upload') -UPLOADING_TITLE = D_(u'File upload') -BOOL_OPTIONS = ('ignore_tls_errors',) +UPLOADING = D_(u"Please select a file to upload") +UPLOADING_TITLE = D_(u"File upload") +BOOL_OPTIONS = ("ignore_tls_errors",) class UploadPlugin(object): @@ -50,10 +51,19 @@ def __init__(self, host): log.info(_("plugin Upload initialization")) self.host = host - host.bridge.addMethod("fileUpload", ".plugin", in_sign='sssa{ss}s', out_sign='a{ss}', method=self._fileUpload, async=True) + host.bridge.addMethod( + "fileUpload", + ".plugin", + in_sign="sssa{ss}s", + out_sign="a{ss}", + method=self._fileUpload, + async=True, + ) self._upload_callbacks = [] - def _fileUpload(self, filepath, filename, upload_jid_s='', options=None, profile=C.PROF_KEY_NONE): + def _fileUpload( + self, filepath, filename, upload_jid_s="", options=None, profile=C.PROF_KEY_NONE + ): client = self.host.getClient(profile) upload_jid = jid.JID(upload_jid_s) if upload_jid_s else None if options is None: @@ -65,7 +75,9 @@ except KeyError: pass - return self.fileUpload(client, filepath, filename or None, upload_jid, options or None) + return self.fileUpload( + client, filepath, filename or None, upload_jid, options or None + ) def fileUpload(self, client, filepath, filename, upload_jid, options): """Send a file using best available method @@ -73,14 +85,19 @@ parameters are the same as for [upload] @return (dict): action dictionary, with progress id in case of success, else xmlui message """ + def uploadCb(data): progress_id, dummy = data - return {'progress': progress_id} + return {"progress": progress_id} def uploadEb(fail): msg = unicode(fail) log.warning(msg) - return {'xmlui': xml_tools.note(u"Can't upload file", msg, C.XMLUI_DATA_LVL_WARNING).toXml()} + return { + "xmlui": xml_tools.note( + u"Can't upload file", msg, C.XMLUI_DATA_LVL_WARNING + ).toXml() + } d = self.upload(client, filepath, filename, upload_jid, options) d.addCallback(uploadCb) @@ -111,10 +128,14 @@ try: upload_jid = yield available_cb(upload_jid, client.profile) except exceptions.NotFound: - continue # no entity managing this extension found + continue # no entity managing this extension found - log.info(u"{name} method will be used to upload the file".format(name=method_name)) - progress_id_d, download_d = yield upload_cb(filepath, filename, upload_jid, options, client.profile) + log.info( + u"{name} method will be used to upload the file".format(name=method_name) + ) + progress_id_d, download_d = yield upload_cb( + filepath, filename, upload_jid, options, client.profile + ) progress_id = yield progress_id_d defer.returnValue((progress_id, download_d)) @@ -137,7 +158,9 @@ assert method_name for data in self._upload_callbacks: if method_name == data[0]: - raise exceptions.ConflictError(u'A method with this name is already registered') + raise exceptions.ConflictError( + u"A method with this name is already registered" + ) self._upload_callbacks.append((method_name, available_cb, upload_cb, priority)) self._upload_callbacks.sort(key=lambda data: data[3], reverse=True)
--- a/sat/plugins/plugin_misc_watched.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_watched.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from sat.tools import xml_tools @@ -33,7 +34,9 @@ C.PI_DEPENDENCIES: [], C.PI_MAIN: "Watched", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Watch for entities presence, and send notification accordingly""") + C.PI_DESCRIPTION: _( + """Watch for entities presence, and send notification accordingly""" + ), } @@ -51,11 +54,9 @@ </category> </individual> </params> - """.format(category_name=CATEGORY, - category_label=_(CATEGORY), - name=NAME, - label=_(NAME), - ) + """.format( + category_name=CATEGORY, category_label=_(CATEGORY), name=NAME, label=_(NAME) + ) def __init__(self, host): log.info(_("Watched initialisation")) @@ -76,6 +77,13 @@ if old_show == C.PRESENCE_UNAVAILABLE: watched = self.host.memory.getParamA(NAME, CATEGORY, profile_key=profile) if entity in watched or entity.userhostJID() in watched: - self.host.actionNew({'xmlui': xml_tools.note(_(NOTIF).format(entity=entity.full())).toXml()}, profile=profile) + self.host.actionNew( + { + "xmlui": xml_tools.note( + _(NOTIF).format(entity=entity.full()) + ).toXml() + }, + profile=profile, + ) return True
--- a/sat/plugins/plugin_misc_welcome.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_welcome.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.tools import xml_tools @@ -30,7 +31,9 @@ C.PI_TYPE: C.PLUG_TYPE_MISC, C.PI_MAIN: "Welcome", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Plugin which manage welcome message and things to to on first connection.""") + C.PI_DESCRIPTION: _( + """Plugin which manage welcome message and things to to on first connection.""" + ), } @@ -40,7 +43,8 @@ WELCOME_MSG_TITLE = D_(u"Welcome to Libervia/Salut à Toi") # XXX: this message is mainly targetting libervia new users for now # (i.e.: it may look weird on other frontends) -WELCOME_MSG = D_(u"""Welcome to a free (as in freedom) network! +WELCOME_MSG = D_( + u"""Welcome to a free (as in freedom) network! If you have any trouble, or you want to help us for the bug hunting, you can contact us in real time chat by using the “Help / Official chat room” menu. @@ -49,7 +53,8 @@ We hope that you'll enjoy using this project. The Libervia/Salut à Toi Team -""") +""" +) PARAMS = """ @@ -60,11 +65,12 @@ </category> </individual> </params> - """.format(category=WELCOME_PARAM_CATEGORY, name=WELCOME_PARAM_NAME, label=WELCOME_PARAM_LABEL) + """.format( + category=WELCOME_PARAM_CATEGORY, name=WELCOME_PARAM_NAME, label=WELCOME_PARAM_LABEL +) class Welcome(object): - def __init__(self, host): log.info(_("plugin Welcome initialization")) self.host = host @@ -73,7 +79,12 @@ def profileConnected(self, client): # XXX: if you wan to try first_start again, you'll have to remove manually # the welcome value from your profile params in sat.db - welcome = self.host.memory.params.getParamA(WELCOME_PARAM_NAME, WELCOME_PARAM_CATEGORY, use_default=False, profile_key=client.profile) + welcome = self.host.memory.params.getParamA( + WELCOME_PARAM_NAME, + WELCOME_PARAM_CATEGORY, + use_default=False, + profile_key=client.profile, + ) if welcome is None: first_start = True welcome = True @@ -82,12 +93,12 @@ if welcome: xmlui = xml_tools.note(WELCOME_MSG, WELCOME_MSG_TITLE) - self.host.actionNew({'xmlui': xmlui.toXml()}, profile=client.profile) - self.host.memory.setParam(WELCOME_PARAM_NAME, C.BOOL_FALSE, WELCOME_PARAM_CATEGORY, profile_key=client.profile) + self.host.actionNew({"xmlui": xmlui.toXml()}, profile=client.profile) + self.host.memory.setParam( + WELCOME_PARAM_NAME, + C.BOOL_FALSE, + WELCOME_PARAM_CATEGORY, + profile_key=client.profile, + ) self.host.trigger.point("WELCOME", first_start, welcome, client.profile) - - - - -
--- a/sat/plugins/plugin_misc_xmllog.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_misc_xmllog.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.xish import domish from twisted.words.xish import xmlstream @@ -32,11 +33,12 @@ C.PI_DEPENDENCIES: [], C.PI_MAIN: "XmlLog", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _(u"""Send raw XML logs to bridge""") + C.PI_DESCRIPTION: _(u"""Send raw XML logs to bridge"""), } host = None + def send(self, obj): global host if isinstance(obj, basestring): @@ -44,7 +46,7 @@ elif isinstance(obj, domish.Element): log = obj.toXml() else: - log.error(_(u'INTERNAL ERROR: Unmanaged XML type')) + log.error(_(u"INTERNAL ERROR: Unmanaged XML type")) host.bridge.xmlLog("OUT", log, self._profile) return self._original_send(obj) @@ -65,18 +67,22 @@ </category> </general> </params> - """ % {"label_xmllog": _("Activate XML log")} + """ % { + "label_xmllog": _("Activate XML log") + } def __init__(self, host_): log.info(_("Plugin XML Log initialization")) global host host = host_ - #parameters + # parameters host.memory.updateParams(self.params) - #bridge - host.bridge.addSignal("xmlLog", ".plugin", signature='sss') # args: direction("IN" or "OUT"), xml_data, profile + # bridge + host.bridge.addSignal( + "xmlLog", ".plugin", signature="sss" + ) # args: direction("IN" or "OUT"), xml_data, profile self.do_log = host.memory.getParamA("Xml log", "Debug") if self.do_log: @@ -85,7 +91,7 @@ XmlStream._original_onElement = XmlStream.onElement XmlStream.send = send XmlStream.onElement = onElement - XmlStream._profile = '' + XmlStream._profile = "" host.trigger.add("XML Initialized", self.setProfile) log.info(_(u"XML log activated"))
--- a/sat/plugins/plugin_sec_otr.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_sec_otr.py Wed Jun 27 20:14:46 2018 +0200 @@ -24,6 +24,7 @@ from sat.core.constants import Const as C from sat.core.log import getLogger from sat.core import exceptions + log = getLogger(__name__) from sat.tools import xml_tools from twisted.words.protocols.jabber import jid @@ -44,27 +45,27 @@ C.PI_DEPENDENCIES: [u"XEP-0280", u"XEP-0334"], C.PI_MAIN: u"OTR", C.PI_HANDLER: u"no", - C.PI_DESCRIPTION: _(u"""Implementation of OTR""") + C.PI_DESCRIPTION: _(u"""Implementation of OTR"""), } NS_OTR = "otr_plugin" PRIVATE_KEY = "PRIVATE KEY" -OTR_MENU = D_(u'OTR') -AUTH_TXT = D_(u"To authenticate your correspondent, you need to give your below fingerprint *BY AN EXTERNAL CANAL* (i.e. not in this chat), and check that the one he gives you is the same as below. If there is a mismatch, there can be a spy between you!") -DROP_TXT = D_(u"You private key is used to encrypt messages for your correspondent, nobody except you must know it, if you are in doubt, you should drop it!\n\nAre you sure you want to drop your private key?") +OTR_MENU = D_(u"OTR") +AUTH_TXT = D_( + u"To authenticate your correspondent, you need to give your below fingerprint *BY AN EXTERNAL CANAL* (i.e. not in this chat), and check that the one he gives you is the same as below. If there is a mismatch, there can be a spy between you!" +) +DROP_TXT = D_( + u"You private key is used to encrypt messages for your correspondent, nobody except you must know it, if you are in doubt, you should drop it!\n\nAre you sure you want to drop your private key?" +) # NO_LOG_AND = D_(u"/!\\Your history is not logged anymore, and") # FIXME: not used at the moment NO_ADV_FEATURES = D_(u"Some of advanced features are disabled !") -DEFAULT_POLICY_FLAGS = { - 'ALLOW_V1':False, - 'ALLOW_V2':True, - 'REQUIRE_ENCRYPTION':True, -} +DEFAULT_POLICY_FLAGS = {"ALLOW_V1": False, "ALLOW_V2": True, "REQUIRE_ENCRYPTION": True} -OTR_STATE_TRUSTED = 'trusted' -OTR_STATE_UNTRUSTED = 'untrusted' -OTR_STATE_UNENCRYPTED = 'unencrypted' -OTR_STATE_ENCRYPTED = 'encrypted' +OTR_STATE_TRUSTED = "trusted" +OTR_STATE_UNTRUSTED = "untrusted" +OTR_STATE_UNENCRYPTED = "unencrypted" +OTR_STATE_ENCRYPTED = "encrypted" class Context(potr.context.Context): @@ -89,25 +90,25 @@ message data when an encrypted message is going to be sent """ assert isinstance(self.peer, jid.JID) - msg = msg_str.decode('utf-8') + msg = msg_str.decode("utf-8") client = self.user.client - log.debug(u'injecting encrypted message to {to}'.format(to=self.peer)) + log.debug(u"injecting encrypted message to {to}".format(to=self.peer)) if appdata is None: mess_data = { - 'from': client.jid, - 'to': self.peer, - 'uid': unicode(uuid.uuid4()), - 'message': {'': msg}, - 'subject': {}, - 'type': 'chat', - 'extra': {}, - 'timestamp': time.time(), - } + "from": client.jid, + "to": self.peer, + "uid": unicode(uuid.uuid4()), + "message": {"": msg}, + "subject": {}, + "type": "chat", + "extra": {}, + "timestamp": time.time(), + } client.generateMessageXML(mess_data) - client.send(mess_data['xml']) + client.send(mess_data["xml"]) else: - message_elt = appdata[u'xml'] - assert message_elt.name == u'message' + message_elt = appdata[u"xml"] + assert message_elt.name == u"message" message_elt.addElement("body", content=msg) def setState(self, state): @@ -117,8 +118,12 @@ log.debug(u"setState: %s (old_state=%s)" % (state, old_state)) if state == potr.context.STATE_PLAINTEXT: - feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % {'other_jid': self.peer.full()} - self.host.bridge.otrState(OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile) + feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % { + "other_jid": self.peer.full() + } + self.host.bridge.otrState( + OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile + ) elif state == potr.context.STATE_ENCRYPTED: try: trusted = self.getCurrentTrust() @@ -127,18 +132,27 @@ trusted_str = _(u"trusted") if trusted else _(u"untrusted") if old_state == potr.context.STATE_ENCRYPTED: - feedback = D_(u"{trusted} OTR conversation with {other_jid} REFRESHED").format( - trusted = trusted_str, - other_jid = self.peer.full()) + feedback = D_( + u"{trusted} OTR conversation with {other_jid} REFRESHED" + ).format(trusted=trusted_str, other_jid=self.peer.full()) else: - feedback = D_(u"{trusted} encrypted OTR conversation started with {other_jid}\n{extra_info}").format( - trusted = trusted_str, - other_jid = self.peer.full(), - extra_info = NO_ADV_FEATURES) - self.host.bridge.otrState(OTR_STATE_ENCRYPTED, self.peer.full(), client.profile) + feedback = D_( + u"{trusted} encrypted OTR conversation started with {other_jid}\n{extra_info}" + ).format( + trusted=trusted_str, + other_jid=self.peer.full(), + extra_info=NO_ADV_FEATURES, + ) + self.host.bridge.otrState( + OTR_STATE_ENCRYPTED, self.peer.full(), client.profile + ) elif state == potr.context.STATE_FINISHED: - feedback = D_(u"OTR conversation with {other_jid} is FINISHED").format(other_jid = self.peer.full()) - self.host.bridge.otrState(OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile) + feedback = D_(u"OTR conversation with {other_jid} is FINISHED").format( + other_jid=self.peer.full() + ) + self.host.bridge.otrState( + OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile + ) else: log.error(D_(u"Unknown OTR state")) return @@ -176,37 +190,42 @@ log.debug(u"savePrivkey") if self.privkey is None: raise exceptions.InternalError(_(u"Save is called but privkey is None !")) - priv_key = self.privkey.serializePrivateKey().encode('hex') + priv_key = self.privkey.serializePrivateKey().encode("hex") d = self.host.memory.encryptValue(priv_key, self.client.profile) + def save_encrypted_key(encrypted_priv_key): self.client._otr_data[PRIVATE_KEY] = encrypted_priv_key + d.addCallback(save_encrypted_key) def loadTrusts(self): - trust_data = self.client._otr_data.get('trust', {}) + trust_data = self.client._otr_data.get("trust", {}) for jid_, jid_data in trust_data.iteritems(): for fingerprint, trust_level in jid_data.iteritems(): - log.debug(u'setting trust for {jid}: [{fingerprint}] = "{trust_level}"'.format(jid=jid_, fingerprint=fingerprint, trust_level=trust_level)) + log.debug( + u'setting trust for {jid}: [{fingerprint}] = "{trust_level}"'.format( + jid=jid_, fingerprint=fingerprint, trust_level=trust_level + ) + ) self.trusts.setdefault(jid.JID(jid_), {})[fingerprint] = trust_level def saveTrusts(self): log.debug(u"saving trusts for {profile}".format(profile=self.client.profile)) - log.debug(u"trusts = {}".format(self.client._otr_data['trust'])) - self.client._otr_data.force('trust') + log.debug(u"trusts = {}".format(self.client._otr_data["trust"])) + self.client._otr_data.force("trust") def setTrust(self, other_jid, fingerprint, trustLevel): try: - trust_data = self.client._otr_data['trust'] + trust_data = self.client._otr_data["trust"] except KeyError: trust_data = {} - self.client._otr_data['trust'] = trust_data + self.client._otr_data["trust"] = trust_data jid_data = trust_data.setdefault(other_jid.full(), {}) jid_data[fingerprint] = trustLevel super(Account, self).setTrust(other_jid, fingerprint, trustLevel) class ContextManager(object): - def __init__(self, host, client): self.host = host self.account = Account(host, client) @@ -214,7 +233,9 @@ def startContext(self, other_jid): assert isinstance(other_jid, jid.JID) - context = self.contexts.setdefault(other_jid, Context(self.host, self.account, other_jid)) + context = self.contexts.setdefault( + other_jid, Context(self.host, self.account, other_jid) + ) return context def getContextForUser(self, other): @@ -225,23 +246,51 @@ class OTR(object): - def __init__(self, host): log.info(_(u"OTR plugin initialization")) self.host = host self.context_managers = {} - self.skipped_profiles = set() # FIXME: OTR should not be skipped per profile, this need to be refactored - self._p_hints = host.plugins[u'XEP-0334'] - self._p_carbons = host.plugins[u'XEP-0280'] + self.skipped_profiles = ( + set() + ) # FIXME: OTR should not be skipped per profile, this need to be refactored + self._p_hints = host.plugins[u"XEP-0334"] + self._p_carbons = host.plugins[u"XEP-0280"] host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000) host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100000) host.trigger.add("sendMessageData", self._sendMessageDataTrigger) - host.bridge.addMethod("skipOTR", ".plugin", in_sign='s', out_sign='', method=self._skipOTR) # FIXME: must be removed, must be done on per-message basis - host.bridge.addSignal("otrState", ".plugin", signature='sss') # args: state, destinee_jid, profile - host.importMenu((OTR_MENU, D_(u"Start/Refresh")), self._otrStartRefresh, security_limit=0, help_string=D_(u"Start or refresh an OTR session"), type_=C.MENU_SINGLE) - host.importMenu((OTR_MENU, D_(u"End session")), self._otrSessionEnd, security_limit=0, help_string=D_(u"Finish an OTR session"), type_=C.MENU_SINGLE) - host.importMenu((OTR_MENU, D_(u"Authenticate")), self._otrAuthenticate, security_limit=0, help_string=D_(u"Authenticate user/see your fingerprint"), type_=C.MENU_SINGLE) - host.importMenu((OTR_MENU, D_(u"Drop private key")), self._dropPrivKey, security_limit=0, type_=C.MENU_SINGLE) + host.bridge.addMethod( + "skipOTR", ".plugin", in_sign="s", out_sign="", method=self._skipOTR + ) # FIXME: must be removed, must be done on per-message basis + host.bridge.addSignal( + "otrState", ".plugin", signature="sss" + ) # args: state, destinee_jid, profile + host.importMenu( + (OTR_MENU, D_(u"Start/Refresh")), + self._otrStartRefresh, + security_limit=0, + help_string=D_(u"Start or refresh an OTR session"), + type_=C.MENU_SINGLE, + ) + host.importMenu( + (OTR_MENU, D_(u"End session")), + self._otrSessionEnd, + security_limit=0, + help_string=D_(u"Finish an OTR session"), + type_=C.MENU_SINGLE, + ) + host.importMenu( + (OTR_MENU, D_(u"Authenticate")), + self._otrAuthenticate, + security_limit=0, + help_string=D_(u"Authenticate user/see your fingerprint"), + type_=C.MENU_SINGLE, + ) + host.importMenu( + (OTR_MENU, D_(u"Drop private key")), + self._dropPrivKey, + security_limit=0, + type_=C.MENU_SINGLE, + ) host.trigger.add("presenceReceived", self._presenceReceivedTrigger) def _skipOTR(self, profile): @@ -263,8 +312,12 @@ yield client._otr_data.load() encrypted_priv_key = client._otr_data.get(PRIVATE_KEY, None) if encrypted_priv_key is not None: - priv_key = yield self.host.memory.decryptValue(encrypted_priv_key, client.profile) - ctxMng.account.privkey = potr.crypt.PK.parsePrivateKey(priv_key.decode('hex'))[0] + priv_key = yield self.host.memory.decryptValue( + encrypted_priv_key, client.profile + ) + ctxMng.account.privkey = potr.crypt.PK.parsePrivateKey( + priv_key.decode("hex") + )[0] else: ctxMng.account.privkey = None ctxMng.account.loadTrusts() @@ -285,7 +338,7 @@ """ client = self.host.getClient(profile) try: - to_jid = jid.JID(menu_data['jid']) + to_jid = jid.JID(menu_data["jid"]) except KeyError: log.error(_(u"jid key is not present !")) return defer.fail(exceptions.DataError) @@ -298,9 +351,11 @@ @param to_jid(jid.JID): jid to start encrypted session with """ if not to_jid.resource: - to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored + to_jid.resource = self.host.memory.getMainResource( + client, to_jid + ) # FIXME: temporary and unsecure, must be changed when frontends are refactored otrctx = client._otr_context_manager.getContextForUser(to_jid) - query = otrctx.sendMessage(0, '?OTRv?') + query = otrctx.sendMessage(0, "?OTRv?") otrctx.inject(query) def _otrSessionEnd(self, menu_data, profile): @@ -311,7 +366,7 @@ """ client = self.host.getClient(profile) try: - to_jid = jid.JID(menu_data['jid']) + to_jid = jid.JID(menu_data["jid"]) except KeyError: log.error(_(u"jid key is not present !")) return defer.fail(exceptions.DataError) @@ -321,7 +376,9 @@ def endSession(self, client, to_jid): """End an OTR session""" if not to_jid.resource: - to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored + to_jid.resource = self.host.memory.getMainResource( + client, to_jid + ) # FIXME: temporary and unsecure, must be changed when frontends are refactored otrctx = client._otr_context_manager.getContextForUser(to_jid) otrctx.disconnect() return {} @@ -334,7 +391,7 @@ """ client = self.host.getClient(profile) try: - to_jid = jid.JID(menu_data['jid']) + to_jid = jid.JID(menu_data["jid"]) except KeyError: log.error(_(u"jid key is not present !")) return defer.fail(exceptions.DataError) @@ -343,65 +400,94 @@ def authenticate(self, client, to_jid): """Authenticate other user and see our own fingerprint""" if not to_jid.resource: - to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored + to_jid.resource = self.host.memory.getMainResource( + client, to_jid + ) # FIXME: temporary and unsecure, must be changed when frontends are refactored ctxMng = client._otr_context_manager otrctx = ctxMng.getContextForUser(to_jid) priv_key = ctxMng.account.privkey if priv_key is None: # we have no private key yet - dialog = xml_tools.XMLUI(C.XMLUI_DIALOG, - dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, - C.XMLUI_DATA_MESS: _(u"You have no private key yet, start an OTR conversation to have one"), - C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING - }, - title = _(u"No private key"), - ) - return {'xmlui': dialog.toXml()} + dialog = xml_tools.XMLUI( + C.XMLUI_DIALOG, + dialog_opt={ + C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, + C.XMLUI_DATA_MESS: _( + u"You have no private key yet, start an OTR conversation to have one" + ), + C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING, + }, + title=_(u"No private key"), + ) + return {"xmlui": dialog.toXml()} other_fingerprint = otrctx.getCurrentKey() if other_fingerprint is None: # we have a private key, but not the fingerprint of our correspondent - dialog = xml_tools.XMLUI(C.XMLUI_DIALOG, - dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, - C.XMLUI_DATA_MESS: _(u"Your fingerprint is:\n{fingerprint}\n\nStart an OTR conversation to have your correspondent one.").format(fingerprint=priv_key), - C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO - }, - title = _(u"Fingerprint"), - ) - return {'xmlui': dialog.toXml()} + dialog = xml_tools.XMLUI( + C.XMLUI_DIALOG, + dialog_opt={ + C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, + C.XMLUI_DATA_MESS: _( + u"Your fingerprint is:\n{fingerprint}\n\nStart an OTR conversation to have your correspondent one." + ).format(fingerprint=priv_key), + C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO, + }, + title=_(u"Fingerprint"), + ) + return {"xmlui": dialog.toXml()} def setTrust(raw_data, profile): # This method is called when authentication form is submited data = xml_tools.XMLUIResult2DataFormResult(raw_data) - if data['match'] == 'yes': + if data["match"] == "yes": otrctx.setCurrentTrust(OTR_STATE_TRUSTED) note_msg = _(u"Your correspondent {correspondent} is now TRUSTED") - self.host.bridge.otrState(OTR_STATE_TRUSTED, to_jid.full(), client.profile) + self.host.bridge.otrState( + OTR_STATE_TRUSTED, to_jid.full(), client.profile + ) else: - otrctx.setCurrentTrust('') + otrctx.setCurrentTrust("") note_msg = _(u"Your correspondent {correspondent} is now UNTRUSTED") - self.host.bridge.otrState(OTR_STATE_UNTRUSTED, to_jid.full(), client.profile) - note = xml_tools.XMLUI(C.XMLUI_DIALOG, dialog_opt = { - C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, - C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.peer)} - ) - return {'xmlui': note.toXml()} + self.host.bridge.otrState( + OTR_STATE_UNTRUSTED, to_jid.full(), client.profile + ) + note = xml_tools.XMLUI( + C.XMLUI_DIALOG, + dialog_opt={ + C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, + C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.peer), + }, + ) + return {"xmlui": note.toXml()} submit_id = self.host.registerCallback(setTrust, with_data=True, one_shot=True) trusted = bool(otrctx.getCurrentTrust()) - xmlui = xml_tools.XMLUI(C.XMLUI_FORM, title=_('Authentication (%s)') % to_jid.full(), submit_id=submit_id) + xmlui = xml_tools.XMLUI( + C.XMLUI_FORM, + title=_("Authentication (%s)") % to_jid.full(), + submit_id=submit_id, + ) xmlui.addText(_(AUTH_TXT)) xmlui.addDivider() - xmlui.addText(D_(u"Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key)) - xmlui.addText(D_(u"Your correspondent fingerprint should be:\n{fingerprint}").format(fingerprint=other_fingerprint)) - xmlui.addDivider('blank') - xmlui.changeContainer('pairs') - xmlui.addLabel(D_(u'Is your correspondent fingerprint the same as here ?')) - xmlui.addList("match", [('yes', _('yes')),('no', _('no'))], ['yes' if trusted else 'no']) - return {'xmlui': xmlui.toXml()} + xmlui.addText( + D_(u"Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key) + ) + xmlui.addText( + D_(u"Your correspondent fingerprint should be:\n{fingerprint}").format( + fingerprint=other_fingerprint + ) + ) + xmlui.addDivider("blank") + xmlui.changeContainer("pairs") + xmlui.addLabel(D_(u"Is your correspondent fingerprint the same as here ?")) + xmlui.addList( + "match", [("yes", _("yes")), ("no", _("no"))], ["yes" if trusted else "no"] + ) + return {"xmlui": xmlui.toXml()} def _dropPrivKey(self, menu_data, profile): """Drop our private Key @@ -411,47 +497,69 @@ """ client = self.host.getClient(profile) try: - to_jid = jid.JID(menu_data['jid']) + to_jid = jid.JID(menu_data["jid"]) if not to_jid.resource: - to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored + to_jid.resource = self.host.memory.getMainResource( + client, to_jid + ) # FIXME: temporary and unsecure, must be changed when frontends are refactored except KeyError: log.error(_(u"jid key is not present !")) return defer.fail(exceptions.DataError) ctxMng = client._otr_context_manager if ctxMng.account.privkey is None: - return {'xmlui': xml_tools.note(_(u"You don't have a private key yet !")).toXml()} + return { + "xmlui": xml_tools.note(_(u"You don't have a private key yet !")).toXml() + } def dropKey(data, profile): - if C.bool(data['answer']): + if C.bool(data["answer"]): # we end all sessions for context in ctxMng.contexts.values(): context.disconnect() ctxMng.account.privkey = None ctxMng.account.getPrivkey() # as account.privkey is None, getPrivkey will generate a new key, and save it - return {'xmlui': xml_tools.note(D_(u"Your private key has been dropped")).toXml()} + return { + "xmlui": xml_tools.note( + D_(u"Your private key has been dropped") + ).toXml() + } return {} submit_id = self.host.registerCallback(dropKey, with_data=True, one_shot=True) - confirm = xml_tools.XMLUI(C.XMLUI_DIALOG, title=_(u'Confirm private key drop'), dialog_opt = {'type': C.XMLUI_DIALOG_CONFIRM, 'message': _(DROP_TXT)}, submit_id = submit_id) - return {'xmlui': confirm.toXml()} + confirm = xml_tools.XMLUI( + C.XMLUI_DIALOG, + title=_(u"Confirm private key drop"), + dialog_opt={"type": C.XMLUI_DIALOG_CONFIRM, "message": _(DROP_TXT)}, + submit_id=submit_id, + ) + return {"xmlui": confirm.toXml()} def _receivedTreatment(self, data, client): - from_jid = data['from'] + from_jid = data["from"] log.debug(u"_receivedTreatment [from_jid = %s]" % from_jid) otrctx = client._otr_context_manager.getContextForUser(from_jid) try: - message = data['message'].itervalues().next() # FIXME: Q&D fix for message refactoring, message is now a dict - res = otrctx.receiveMessage(message.encode('utf-8')) + message = ( + data["message"].itervalues().next() + ) # FIXME: Q&D fix for message refactoring, message is now a dict + res = otrctx.receiveMessage(message.encode("utf-8")) except potr.context.UnencryptedMessage: encrypted = False if otrctx.state == potr.context.STATE_ENCRYPTED: - log.warning(u"Received unencrypted message in an encrypted context (from {jid})".format( - jid = from_jid.full())) + log.warning( + u"Received unencrypted message in an encrypted context (from {jid})".format( + jid=from_jid.full() + ) + ) - feedback=D_(u"WARNING: received unencrypted data in a supposedly encrypted context"), + feedback = ( + D_( + u"WARNING: received unencrypted data in a supposedly encrypted context" + ), + ) client.feedback(from_jid, feedback) except StopIteration: return data @@ -462,17 +570,25 @@ if res[0] != None: # decrypted messages handling. # receiveMessage() will return a tuple, the first part of which will be the decrypted message - data['message'] = {'':res[0].decode('utf-8')} # FIXME: Q&D fix for message refactoring, message is now a dict + data["message"] = { + "": res[0].decode("utf-8") + } # FIXME: Q&D fix for message refactoring, message is now a dict try: # we want to keep message in history, even if no store is requested in message hints - del data[u'history'] + del data[u"history"] except KeyError: pass # TODO: add skip history as an option, but by default we don't skip it # data[u'history'] = C.HISTORY_SKIP # we send the decrypted message to frontends, but we don't want it in history else: - log.warning(u"An encrypted message was expected, but got {}".format(data['message'])) - raise failure.Failure(exceptions.CancelError('Cancelled by OTR')) # no message at all (no history, no signal) + log.warning( + u"An encrypted message was expected, but got {}".format( + data["message"] + ) + ) + raise failure.Failure( + exceptions.CancelError("Cancelled by OTR") + ) # no message at all (no history, no signal) return data def _receivedTreatmentForSkippedProfiles(self, data): @@ -480,21 +596,23 @@ but we still need to check if the message must be stored in history or not """ - # XXX: FIXME: this should not be done on a per-profile basis, but per-message + # XXX: FIXME: this should not be done on a per-profile basis, but per-message try: - message = data['message'].itervalues().next().encode('utf-8') # FIXME: Q&D fix for message refactoring, message is now a dict + message = ( + data["message"].itervalues().next().encode("utf-8") + ) # FIXME: Q&D fix for message refactoring, message is now a dict except StopIteration: return data if message.startswith(potr.proto.OTRTAG): - # FIXME: it may be better to cancel the message and send it direclty to bridge + # FIXME: it may be better to cancel the message and send it direclty to bridge # this is used by Libervia, but this may send garbage message to other frontends # if they are used at the same time as Libervia. # Hard to avoid with decryption on Libervia though. - data[u'history'] = C.HISTORY_SKIP + data[u"history"] = C.HISTORY_SKIP return data def MessageReceivedTrigger(self, client, message_elt, post_treat): - if message_elt.getAttribute('type') == C.MESS_TYPE_GROUPCHAT: + if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: # OTR is not possible in group chats return True if client.profile in self.skipped_profiles: @@ -504,51 +622,65 @@ return True def _sendMessageDataTrigger(self, client, mess_data): - if not 'OTR' in mess_data: + if not "OTR" in mess_data: return - otrctx = mess_data['OTR'] - message_elt = mess_data['xml'] - to_jid = mess_data['to'] + otrctx = mess_data["OTR"] + message_elt = mess_data["xml"] + to_jid = mess_data["to"] if otrctx.state == potr.context.STATE_ENCRYPTED: log.debug(u"encrypting message") body = None for child in list(message_elt.children): - if child.name == 'body': + if child.name == "body": # we remove all unencrypted body, # and will only encrypt the first one if body is None: body = child message_elt.children.remove(child) - elif child.name == 'html': + elif child.name == "html": # we don't want any XHTML-IM element message_elt.children.remove(child) if body is None: log.warning(u"No message found") else: self._p_carbons.setPrivate(message_elt) - otrctx.sendMessage(0, unicode(body).encode('utf-8'), appdata=mess_data) + otrctx.sendMessage(0, unicode(body).encode("utf-8"), appdata=mess_data) else: - feedback = D_(u"Your message was not sent because your correspondent closed the encrypted conversation on his/her side. " - u"Either close your own side, or refresh the session.") + feedback = D_( + u"Your message was not sent because your correspondent closed the encrypted conversation on his/her side. " + u"Either close your own side, or refresh the session." + ) log.warning(_(u"Message discarded because closed encryption channel")) client.feedback(to_jid, feedback) - raise failure.Failure(exceptions.CancelError(u'Cancelled by OTR plugin')) + raise failure.Failure(exceptions.CancelError(u"Cancelled by OTR plugin")) - def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments): - if mess_data['type'] == 'groupchat': + def sendMessageTrigger( + self, client, mess_data, pre_xml_treatments, post_xml_treatments + ): + if mess_data["type"] == "groupchat": return True - if client.profile in self.skipped_profiles: # FIXME: should not be done on a per-profile basis + if ( + client.profile in self.skipped_profiles + ): # FIXME: should not be done on a per-profile basis return True - to_jid = copy.copy(mess_data['to']) + to_jid = copy.copy(mess_data["to"]) if not to_jid.resource: - to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: full jid may not be known + to_jid.resource = self.host.memory.getMainResource( + client, to_jid + ) # FIXME: full jid may not be known otrctx = client._otr_context_manager.getContextForUser(to_jid) if otrctx.state != potr.context.STATE_PLAINTEXT: self._p_hints.addHint(mess_data, self._p_hints.HINT_NO_COPY) self._p_hints.addHint(mess_data, self._p_hints.HINT_NO_PERMANENT_STORE) - mess_data['OTR'] = otrctx # this indicate that encryption is needed in sendMessageData trigger - if not mess_data['to'].resource: # if not resource was given, we force it here - mess_data['to'] = to_jid + mess_data[ + "OTR" + ] = ( + otrctx + ) # this indicate that encryption is needed in sendMessageData trigger + if not mess_data[ + "to" + ].resource: # if not resource was given, we force it here + mess_data["to"] = to_jid return True def _presenceReceivedTrigger(self, entity, show, priority, statuses, profile): @@ -557,7 +689,9 @@ client = self.host.getClient(profile) if not entity.resource: try: - entity.resource = self.host.memory.getMainResource(client, entity) # FIXME: temporary and unsecure, must be changed when frontends are refactored + entity.resource = self.host.memory.getMainResource( + client, entity + ) # FIXME: temporary and unsecure, must be changed when frontends are refactored except exceptions.UnknownEntityError: return True # entity was not connected if entity in client._otr_context_manager.contexts:
--- a/sat/plugins/plugin_syntax_wiki_dotclear.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_syntax_wiki_dotclear.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core.i18n import _ from sat.core.log import getLogger + log = getLogger(__name__) from sat.core.constants import Const as C from sat.core import exceptions @@ -36,61 +37,70 @@ C.PI_DEPENDENCIES: ["TEXT-SYNTAXES"], C.PI_MAIN: "DCWikiSyntax", C.PI_HANDLER: "", - C.PI_DESCRIPTION: _("""Implementation of Dotclear wiki syntax""") + C.PI_DESCRIPTION: _("""Implementation of Dotclear wiki syntax"""), } -NOTE_TPL = u'[{}]' # Note template -NOTE_A_REV_TPL = u'rev_note_{}' -NOTE_A_TPL = u'note_{}' +NOTE_TPL = u"[{}]" # Note template +NOTE_A_REV_TPL = u"rev_note_{}" +NOTE_A_TPL = u"note_{}" ESCAPE_CHARS_BASE = r"(?P<escape_char>[][{}%|\\/*#@{{}}~$-])" -ESCAPE_CHARS_EXTRA = r"!?_+'()" # These chars are not escaped in XHTML => dc_wiki conversion, - # but are used in the other direction -ESCAPE_CHARS = ESCAPE_CHARS_BASE.format('') -FLAG_UL = 'ul' # must be the name of the element -FLAG_OL = 'ol' -ELT_WITH_STYLE = ('img', 'div') # elements where a style attribute is expected +ESCAPE_CHARS_EXTRA = ( + r"!?_+'()" +) # These chars are not escaped in XHTML => dc_wiki conversion, +# but are used in the other direction +ESCAPE_CHARS = ESCAPE_CHARS_BASE.format("") +FLAG_UL = "ul" # must be the name of the element +FLAG_OL = "ol" +ELT_WITH_STYLE = ("img", "div") # elements where a style attribute is expected -wiki = [r'\\' + ESCAPE_CHARS_BASE.format(ESCAPE_CHARS_EXTRA), - r"^!!!!!(?P<h1_title>.+?)$", - r"^!!!!(?P<h2_title>.+?)$", - r"^!!!(?P<h3_title>.+?)$", - r"^!!(?P<h4_title>.+?)$", - r"^!(?P<h5_title>.+?)$", - r"^----$(?P<horizontal_rule>)", - r"^\*(?P<list_bullet>.*?)$", - r"^#(?P<list_ordered>.*?)$", - r"^ (?P<preformated>.*?)$", - r"^> +?(?P<quote>.*?)$", - r"''(?P<emphasis>.+?)''", - r"__(?P<strong_emphasis>.+?)__", - r"%%%(?P<line_break>)", - r"\+\+(?P<insertion>.+?)\+\+", - r"--(?P<deletion>.+?)--", - r"\[(?P<link>.+?)\]", - r"\(\((?P<image>.+?)\)\)", - r"~(?P<anchor>.+?)~", - r"\?\?(?P<acronym>.+?\|.+?)\?\?", - r"{{(?P<inline_quote>.+?)}}", - r"@@(?P<code>.+?)@@", - r"\$\$(?P<footnote>.+?)\$\$", - r"(?P<text>.+?)", - ] +wiki = [ + r"\\" + ESCAPE_CHARS_BASE.format(ESCAPE_CHARS_EXTRA), + r"^!!!!!(?P<h1_title>.+?)$", + r"^!!!!(?P<h2_title>.+?)$", + r"^!!!(?P<h3_title>.+?)$", + r"^!!(?P<h4_title>.+?)$", + r"^!(?P<h5_title>.+?)$", + r"^----$(?P<horizontal_rule>)", + r"^\*(?P<list_bullet>.*?)$", + r"^#(?P<list_ordered>.*?)$", + r"^ (?P<preformated>.*?)$", + r"^> +?(?P<quote>.*?)$", + r"''(?P<emphasis>.+?)''", + r"__(?P<strong_emphasis>.+?)__", + r"%%%(?P<line_break>)", + r"\+\+(?P<insertion>.+?)\+\+", + r"--(?P<deletion>.+?)--", + r"\[(?P<link>.+?)\]", + r"\(\((?P<image>.+?)\)\)", + r"~(?P<anchor>.+?)~", + r"\?\?(?P<acronym>.+?\|.+?)\?\?", + r"{{(?P<inline_quote>.+?)}}", + r"@@(?P<code>.+?)@@", + r"\$\$(?P<footnote>.+?)\$\$", + r"(?P<text>.+?)", +] -wiki_re = re.compile('|'.join(wiki), re.MULTILINE | re.DOTALL) -wiki_block_level_re = re.compile(r"^///html(?P<html>.+?)///\n\n|(?P<paragraph>.+?)(?:\n{2,}|\Z)", re.MULTILINE | re.DOTALL) +wiki_re = re.compile("|".join(wiki), re.MULTILINE | re.DOTALL) +wiki_block_level_re = re.compile( + r"^///html(?P<html>.+?)///\n\n|(?P<paragraph>.+?)(?:\n{2,}|\Z)", + re.MULTILINE | re.DOTALL, +) class DCWikiParser(object): - def __init__(self): self._footnotes = None for i in xrange(5): - setattr(self, - 'parser_h{}_title'.format(i), - lambda string, parent, i=i: self._parser_title(string, parent, 'h{}'.format(i))) + setattr( + self, + "parser_h{}_title".format(i), + lambda string, parent, i=i: self._parser_title( + string, parent, "h{}".format(i) + ), + ) def parser_paragraph(self, string, parent): - p_elt = parent.addElement('p') + p_elt = parent.addElement("p") self._parse(string, p_elt) def parser_html(self, string, parent): @@ -101,7 +111,7 @@ log.warning(u"Error while parsing HTML content, ignoring it: {}".format(e)) return children = list(div_elt.elements()) - if len(children) == 1 and children[0].name == 'div': + if len(children) == 1 and children[0].name == "div": div_elt = children[0] parent.addChild(div_elt) @@ -113,128 +123,130 @@ elt.addContent(string) def parser_horizontal_rule(self, string, parent): - parent.addElement('hr') + parent.addElement("hr") def _parser_list(self, string, parent, list_type): depth = 0 - while string[depth:depth+1] == '*': - depth +=1 + while string[depth : depth + 1] == "*": + depth += 1 string = string[depth:].lstrip() - for i in xrange(depth+1): + for i in xrange(depth + 1): list_elt = getattr(parent, list_type) if not list_elt: parent = parent.addElement(list_type) else: parent = list_elt - li_elt = parent.addElement('li') + li_elt = parent.addElement("li") self._parse(string, li_elt) def parser_list_bullet(self, string, parent): - self._parser_list(string, parent, 'ul') + self._parser_list(string, parent, "ul") def parser_list_ordered(self, string, parent): - self._parser_list(string, parent, 'ol') + self._parser_list(string, parent, "ol") def parser_preformated(self, string, parent): pre_elt = parent.pre if pre_elt is None: - pre_elt = parent.addElement('pre') + pre_elt = parent.addElement("pre") else: # we are on a new line, and this is important for <pre/> - pre_elt.addContent('\n') + pre_elt.addContent("\n") pre_elt.addContent(string) def parser_quote(self, string, parent): blockquote_elt = parent.blockquote if blockquote_elt is None: - blockquote_elt = parent.addElement('blockquote') + blockquote_elt = parent.addElement("blockquote") p_elt = blockquote_elt.p if p_elt is None: - p_elt = blockquote_elt.addElement('p') + p_elt = blockquote_elt.addElement("p") else: - string = u'\n' + string + string = u"\n" + string self._parse(string, p_elt) def parser_emphasis(self, string, parent): - em_elt = parent.addElement('em') + em_elt = parent.addElement("em") self._parse(string, em_elt) def parser_strong_emphasis(self, string, parent): - strong_elt = parent.addElement('strong') + strong_elt = parent.addElement("strong") self._parse(string, strong_elt) def parser_line_break(self, string, parent): - parent.addElement('br') + parent.addElement("br") def parser_insertion(self, string, parent): - ins_elt = parent.addElement('ins') + ins_elt = parent.addElement("ins") self._parse(string, ins_elt) def parser_deletion(self, string, parent): - del_elt = parent.addElement('del') + del_elt = parent.addElement("del") self._parse(string, del_elt) def parser_link(self, string, parent): - url_data = string.split(u'|') - a_elt = parent.addElement('a') + url_data = string.split(u"|") + a_elt = parent.addElement("a") length = len(url_data) if length == 1: url = url_data[0] - a_elt['href'] = url + a_elt["href"] = url a_elt.addContent(url) else: name = url_data[0] url = url_data[1] - a_elt['href'] = url + a_elt["href"] = url a_elt.addContent(name) if length >= 3: - a_elt['lang'] = url_data[2] + a_elt["lang"] = url_data[2] if length >= 4: - a_elt['title'] = url_data[3] + a_elt["title"] = url_data[3] if length > 4: log.warning(u"too much data for url, ignoring extra data") def parser_image(self, string, parent): - image_data = string.split(u'|') - img_elt = parent.addElement('img') + image_data = string.split(u"|") + img_elt = parent.addElement("img") - for idx, attribute in enumerate(('src', 'alt', 'position', 'longdesc')): + for idx, attribute in enumerate(("src", "alt", "position", "longdesc")): try: data = image_data[idx] except IndexError: break - if attribute != 'position': + if attribute != "position": img_elt[attribute] = data else: data = data.lower() - if data in ('l', 'g'): - img_elt['style'] = "display:block; float:left; margin:0 1em 1em 0" - elif data in ('r', 'd'): - img_elt['style'] = "display:block; float:right; margin:0 0 1em 1em" - elif data == 'c': - img_elt['style'] = "display:block; margin-left:auto; margin-right:auto" + if data in ("l", "g"): + img_elt["style"] = "display:block; float:left; margin:0 1em 1em 0" + elif data in ("r", "d"): + img_elt["style"] = "display:block; float:right; margin:0 0 1em 1em" + elif data == "c": + img_elt[ + "style" + ] = "display:block; margin-left:auto; margin-right:auto" else: log.warning(u"bad position argument for image, ignoring it") def parser_anchor(self, string, parent): - a_elt = parent.addElement('a') - a_elt['id'] = string + a_elt = parent.addElement("a") + a_elt["id"] = string def parser_acronym(self, string, parent): - acronym, title = string.split(u'|',1) - acronym_elt = parent.addElement('acronym', content=acronym) - acronym_elt['title'] = title + acronym, title = string.split(u"|", 1) + acronym_elt = parent.addElement("acronym", content=acronym) + acronym_elt["title"] = title def parser_inline_quote(self, string, parent): - quote_data = string.split(u'|') + quote_data = string.split(u"|") quote = quote_data[0] - q_elt = parent.addElement('q', content=quote) - for idx, attribute in enumerate(('lang', 'cite'), 1): + q_elt = parent.addElement("q", content=quote) + for idx, attribute in enumerate(("lang", "cite"), 1): try: data = quote_data[idx] except IndexError: @@ -242,21 +254,21 @@ q_elt[attribute] = data def parser_code(self, string, parent): - parent.addElement('code', content=string) + parent.addElement("code", content=string) def parser_footnote(self, string, parent): idx = len(self._footnotes) + 1 note_txt = NOTE_TPL.format(idx) - sup_elt = parent.addElement('sup') - sup_elt['class'] = 'note' - a_elt = sup_elt.addElement('a', content=note_txt) - a_elt['id'] = NOTE_A_REV_TPL.format(idx) - a_elt['href'] = u'#{}'.format(NOTE_A_TPL.format(idx)) + sup_elt = parent.addElement("sup") + sup_elt["class"] = "note" + a_elt = sup_elt.addElement("a", content=note_txt) + a_elt["id"] = NOTE_A_REV_TPL.format(idx) + a_elt["href"] = u"#{}".format(NOTE_A_TPL.format(idx)) - p_elt = domish.Element((None, 'p')) - a_elt = p_elt.addElement('a', content=note_txt) - a_elt['id'] = NOTE_A_TPL.format(idx) - a_elt['href'] = u'#{}'.format(NOTE_A_REV_TPL.format(idx)) + p_elt = domish.Element((None, "p")) + a_elt = p_elt.addElement("a", content=note_txt) + a_elt["id"] = NOTE_A_TPL.format(idx) + a_elt["href"] = u"#{}".format(NOTE_A_REV_TPL.format(idx)) self._parse(string, p_elt) # footnotes are actually added at the end of the parsing self._footnotes.append(p_elt) @@ -273,7 +285,7 @@ return matched = match.group(match.lastgroup) try: - parser = getattr(self, 'parser_{}'.format(match.lastgroup)) + parser = getattr(self, "parser_{}".format(match.lastgroup)) except AttributeError: log.warning(u"No parser found for {}".format(match.lastgroup)) # parent.addContent(string) @@ -282,46 +294,46 @@ def parse(self, string): self._footnotes = [] - div_elt = domish.Element((None, 'div')) + div_elt = domish.Element((None, "div")) self._parse(string, parent=div_elt, block_level=True) if self._footnotes: - foot_div_elt = div_elt.addElement('div') - foot_div_elt['class'] = 'footnotes' + foot_div_elt = div_elt.addElement("div") + foot_div_elt["class"] = "footnotes" # we add a simple horizontal rule which can be customized # with footnotes class, instead of a text which would need # to be translated - foot_div_elt.addElement('hr') + foot_div_elt.addElement("hr") for elt in self._footnotes: foot_div_elt.addChild(elt) return div_elt class XHTMLParser(object): - def __init__(self): self.flags = None self.toto = 0 - self.footnotes = None # will hold a map from url to buffer id - for i in xrange(1,6): - setattr(self, - 'parser_h{}'.format(i), - lambda elt, buf, level=i: self.parserHeading(elt, buf, level) - ) + self.footnotes = None # will hold a map from url to buffer id + for i in xrange(1, 6): + setattr( + self, + "parser_h{}".format(i), + lambda elt, buf, level=i: self.parserHeading(elt, buf, level), + ) def parser_a(self, elt, buf): try: - url = elt['href'] + url = elt["href"] except KeyError: # probably an anchor try: - id_ = elt['id'] + id_ = elt["id"] if not id_: # we don't want empty values raise KeyError except KeyError: self.parserGeneric(elt, buf) else: - buf.append(u'~~{}~~'.format(id_)) + buf.append(u"~~{}~~".format(id_)) return link_data = [url] @@ -329,52 +341,54 @@ if name != url: link_data.insert(0, name) - lang = elt.getAttribute('lang') - title = elt.getAttribute('title') + lang = elt.getAttribute("lang") + title = elt.getAttribute("title") if lang is not None: link_data.append(lang) elif title is not None: - link_data.appand(u'') + link_data.appand(u"") if title is not None: link_data.append(title) - buf.append(u'[') - buf.append(u'|'.join(link_data)) - buf.append(u']') + buf.append(u"[") + buf.append(u"|".join(link_data)) + buf.append(u"]") def parser_acronym(self, elt, buf): try: - title = elt['title'] + title = elt["title"] except KeyError: log.debug(u"Acronyme without title, using generic parser") self.parserGeneric(elt, buf) return - buf.append(u'??{}|{}??'.format(unicode(elt), title)) + buf.append(u"??{}|{}??".format(unicode(elt), title)) def parser_blockquote(self, elt, buf): # we remove wrapping <p> to avoid empty line with "> " - children = list([child for child in elt.children if unicode(child).strip() not in ('', '\n')]) - if len(children) == 1 and children[0].name == 'p': + children = list( + [child for child in elt.children if unicode(child).strip() not in ("", "\n")] + ) + if len(children) == 1 and children[0].name == "p": elt = children[0] tmp_buf = [] self.parseChildren(elt, tmp_buf) - blockquote = u'> ' + u'\n> '.join(u''.join(tmp_buf).split('\n')) + blockquote = u"> " + u"\n> ".join(u"".join(tmp_buf).split("\n")) buf.append(blockquote) def parser_br(self, elt, buf): - buf.append(u'%%%') + buf.append(u"%%%") def parser_code(self, elt, buf): - buf.append(u'@@') + buf.append(u"@@") self.parseChildren(elt, buf) - buf.append(u'@@') + buf.append(u"@@") def parser_del(self, elt, buf): - buf.append(u'--') + buf.append(u"--") self.parseChildren(elt, buf) - buf.append(u'--') + buf.append(u"--") def parser_div(self, elt, buf): - if elt.getAttribute('class') == 'footnotes': + if elt.getAttribute("class") == "footnotes": self.parserFootnote(elt, buf) else: self.parseChildren(elt, buf, block=True) @@ -387,56 +401,56 @@ def parser_h6(self, elt, buf): # XXX: <h6/> heading is not managed by wiki syntax # so we handle it with a <h5/> - elt = copy.copy(elt) # we don't want to change to original element - elt.name = 'h5' + elt = copy.copy(elt) # we don't want to change to original element + elt.name = "h5" self._parse(elt, buf) def parser_hr(self, elt, buf): - buf.append(u'\n----\n') + buf.append(u"\n----\n") def parser_img(self, elt, buf): try: - url = elt['src'] + url = elt["src"] except KeyError: log.warning(u"Ignoring <img/> without src") return - image_data=[url] + image_data = [url] - alt = elt.getAttribute('alt') - style = elt.getAttribute('style', '') - desc = elt.getAttribute('longdesc') + alt = elt.getAttribute("alt") + style = elt.getAttribute("style", "") + desc = elt.getAttribute("longdesc") - if '0 1em 1em 0' in style: - position = 'L' - elif '0 0 1em 1em' in style: - position = 'R' - elif 'auto' in style: - position = 'C' + if "0 1em 1em 0" in style: + position = "L" + elif "0 0 1em 1em" in style: + position = "R" + elif "auto" in style: + position = "C" else: position = None if alt: image_data.append(alt) elif position or desc: - image_data.append(u'') + image_data.append(u"") if position: image_data.append(position) elif desc: - image_data.append(u'') + image_data.append(u"") if desc: image_data.append(desc) - buf.append(u'((') - buf.append(u'|'.join(image_data)) - buf.append(u'))') + buf.append(u"((") + buf.append(u"|".join(image_data)) + buf.append(u"))") def parser_ins(self, elt, buf): - buf.append(u'++') + buf.append(u"++") self.parseChildren(elt, buf) - buf.append(u'++') + buf.append(u"++") def parser_li(self, elt, buf): flag = None @@ -447,11 +461,11 @@ if current_flag is None: current_flag = flag if flag == current_flag: - bullets.append(u'*' if flag == FLAG_UL else u'#') + bullets.append(u"*" if flag == FLAG_UL else u"#") else: break - if flag != current_flag and buf[-1] == u' ': + if flag != current_flag and buf[-1] == u" ": # this trick is to avoid a space when we switch # from (un)ordered to the other type on the same row # e.g. *# unorder + ordered item @@ -459,64 +473,75 @@ buf.extend(bullets) - buf.append(u' ') + buf.append(u" ") self.parseChildren(elt, buf) - buf.append(u'\n') + buf.append(u"\n") def parser_ol(self, elt, buf): self.parserList(elt, buf, FLAG_OL) def parser_p(self, elt, buf): self.parseChildren(elt, buf) - buf.append(u'\n\n') + buf.append(u"\n\n") def parser_pre(self, elt, buf): - pre = u''.join([child.toXml() if domish.IElement.providedBy(child) else unicode(child) for child in elt.children]) - pre = u' ' + u'\n '.join(pre.split('\n')) + pre = u"".join( + [ + child.toXml() if domish.IElement.providedBy(child) else unicode(child) + for child in elt.children + ] + ) + pre = u" " + u"\n ".join(pre.split("\n")) buf.append(pre) def parser_q(self, elt, buf): - quote_data=[unicode(elt)] + quote_data = [unicode(elt)] - lang = elt.getAttribute('lang') - cite = elt.getAttribute('url') + lang = elt.getAttribute("lang") + cite = elt.getAttribute("url") if lang: quote_data.append(lang) elif cite: - quote_data.append(u'') + quote_data.append(u"") if cite: quote_data.append(cite) - buf.append(u'{{') - buf.append(u'|'.join(quote_data)) - buf.append(u'}}') + buf.append(u"{{") + buf.append(u"|".join(quote_data)) + buf.append(u"}}") def parser_span(self, elt, buf): self.parseChildren(elt, buf, block=True) def parser_strong(self, elt, buf): - buf.append(u'__') + buf.append(u"__") self.parseChildren(elt, buf) - buf.append(u'__') + buf.append(u"__") def parser_sup(self, elt, buf): # sup is mainly used for footnotes, so we check if we have an anchor inside - children = list([child for child in elt.children if unicode(child).strip() not in ('', '\n')]) - if (len(children) == 1 and domish.IElement.providedBy(children[0]) - and children[0].name == 'a' and '#' in children[0].getAttribute('href', '')): - url = children[0]['href'] - note_id = url[url.find('#')+1:] + children = list( + [child for child in elt.children if unicode(child).strip() not in ("", "\n")] + ) + if ( + len(children) == 1 + and domish.IElement.providedBy(children[0]) + and children[0].name == "a" + and "#" in children[0].getAttribute("href", "") + ): + url = children[0]["href"] + note_id = url[url.find("#") + 1 :] if not note_id: log.warning("bad link found in footnote") self.parserGeneric(elt, buf) return # this looks like a footnote - buf.append(u'$$') - buf.append(u' ') # placeholder + buf.append(u"$$") + buf.append(u" ") # placeholder self.footnotes[note_id] = len(buf) - 1 - buf.append(u'$$') + buf.append(u"$$") else: self.parserGeneric(elt, buf) @@ -537,39 +562,41 @@ raise exceptions.InternalError(u"flag has been removed by an other parser") def parserHeading(self, elt, buf, level): - buf.append((6-level) * u'!') + buf.append((6 - level) * u"!") for child in elt.children: # we ignore other elements for a Hx title self.parserText(child, buf) - buf.append(u'\n') + buf.append(u"\n") def parserFootnote(self, elt, buf): for elt in elt.elements(): # all children other than <p/> are ignored - if elt.name == 'p': + if elt.name == "p": a_elt = elt.a if a_elt is None: - log.warning(u"<p/> element doesn't contain <a/> in footnote, ignoring it") + log.warning( + u"<p/> element doesn't contain <a/> in footnote, ignoring it" + ) continue try: - note_idx = self.footnotes[a_elt['id']] + note_idx = self.footnotes[a_elt["id"]] except KeyError: log.warning(u"Note id doesn't match any known note, ignoring it") # we create a dummy element to parse all children after the <a/> - dummy_elt = domish.Element((None, 'note')) + dummy_elt = domish.Element((None, "note")) a_idx = elt.children.index(a_elt) - dummy_elt.children = elt.children[a_idx+1:] + dummy_elt.children = elt.children[a_idx + 1 :] note_buf = [] self.parseChildren(dummy_elt, note_buf) # now we can replace the placeholder - buf[note_idx] = u''.join(note_buf) + buf[note_idx] = u"".join(note_buf) def parserText(self, txt, buf, keep_whitespaces=False): txt = unicode(txt) if not keep_whitespaces: # we get text and only let one inter word space - txt = u' '.join(txt.split()) - txt = re.sub(ESCAPE_CHARS, r'\\\1', txt) + txt = u" ".join(txt.split()) + txt = re.sub(ESCAPE_CHARS, r"\\\1", txt) if txt: buf.append(txt) return txt @@ -582,9 +609,9 @@ def parseChildren(self, elt, buf, block=False): first_visible = True for child in elt.children: - if not block and not first_visible and buf and buf[-1][-1] not in (' ','\n'): + if not block and not first_visible and buf and buf[-1][-1] not in (" ", "\n"): # we add separation if it isn't already there - buf.append(u' ') + buf.append(u" ") if domish.IElement.providedBy(child): self._parse(child, buf) first_visible = False @@ -595,7 +622,7 @@ def _parse(self, elt, buf): elt_name = elt.name.lower() - style = elt.getAttribute('style') + style = elt.getAttribute("style") if style and elt_name not in ELT_WITH_STYLE: # if we have style we use generic parser to put raw HTML # to avoid losing it @@ -604,7 +631,9 @@ try: parser = getattr(self, "parser_{}".format(elt_name)) except AttributeError: - log.debug("Can't find parser for {} element, using generic one".format(elt.name)) + log.debug( + "Can't find parser for {} element, using generic one".format(elt.name) + ) parser = self.parserGeneric parser(elt, buf) @@ -613,7 +642,7 @@ self.footnotes = {} buf = [] self._parse(elt, buf) - return u''.join(buf) + return u"".join(buf) def parseString(self, string): wrapped_html = u"<div>{}</div>".format(string) @@ -623,7 +652,7 @@ log.warning(u"Error while parsing HTML content: {}".format(e)) return children = list(div_elt.elements()) - if len(children) == 1 and children[0].name == 'div': + if len(children) == 1 and children[0].name == "div": div_elt = children[0] return self.parse(div_elt) @@ -637,7 +666,9 @@ self._dc_parser = DCWikiParser() self._xhtml_parser = XHTMLParser() self._stx = self.host.plugins["TEXT-SYNTAXES"] - self._stx.addSyntax(self.SYNTAX_NAME, self.parseWiki, self.parseXHTML, [self._stx.OPT_NO_THREAD]) + self._stx.addSyntax( + self.SYNTAX_NAME, self.parseWiki, self.parseXHTML, [self._stx.OPT_NO_THREAD] + ) def parseWiki(self, wiki_stx): div_elt = self._dc_parser.parse(wiki_stx)
--- a/sat/plugins/plugin_tickets_import.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_tickets_import.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core.constants import Const as C from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) from twisted.internet import defer from sat.tools.common import uri @@ -34,15 +35,17 @@ C.PI_DEPENDENCIES: ["IMPORT", "XEP-0060", "XEP-0277", "PUBSUB_SCHEMA"], C.PI_MAIN: "TicketsImportPlugin", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _(u"""Tickets import management: -This plugin manage the different tickets importers which can register to it, and handle generic importing tasks.""") + C.PI_DESCRIPTION: _( + u"""Tickets import management: +This plugin manage the different tickets importers which can register to it, and handle generic importing tasks.""" + ), } -OPT_MAPPING = 'mapping' -FIELDS_LIST = (u'labels', u'cc_emails') # fields which must have a list as value -FIELDS_DATE = (u'created', u'updated') +OPT_MAPPING = "mapping" +FIELDS_LIST = (u"labels", u"cc_emails") # fields which must have a list as value +FIELDS_DATE = (u"created", u"updated") -NS_TICKETS = 'org.salut-a-toi.tickets:0' +NS_TICKETS = "org.salut-a-toi.tickets:0" class TicketsImportPlugin(object): @@ -54,13 +57,15 @@ log.info(_("plugin Tickets Import initialization")) self.host = host self._importers = {} - self._p = host.plugins['XEP-0060'] - self._m = host.plugins['XEP-0277'] - self._s = host.plugins['PUBSUB_SCHEMA'] - host.plugins['IMPORT'].initialize(self, u'tickets') + self._p = host.plugins["XEP-0060"] + self._m = host.plugins["XEP-0277"] + self._s = host.plugins["PUBSUB_SCHEMA"] + host.plugins["IMPORT"].initialize(self, u"tickets") @defer.inlineCallbacks - def importItem(self, client, item_import_data, session, options, return_data, service, node): + def importItem( + self, client, item_import_data, session, options, return_data, service, node + ): """ @param item_import_data(dict): no key is mandatory, but if a key doesn't exists in dest form, it will be ignored. @@ -101,20 +106,24 @@ If you specify several import ticket key to the same dest key, the values will be joined with line feeds """ - if 'comments_uri' in item_import_data: - raise exceptions.DataError(_(u'comments_uri key will be generated and must not be used by importer')) + if "comments_uri" in item_import_data: + raise exceptions.DataError( + _(u"comments_uri key will be generated and must not be used by importer") + ) for key in FIELDS_LIST: if not isinstance(item_import_data.get(key, []), list): - raise exceptions.DataError(_(u'{key} must be a list').format(key=key)) + raise exceptions.DataError(_(u"{key} must be a list").format(key=key)) for key in FIELDS_DATE: try: item_import_data[key] = utils.xmpp_date(item_import_data[key]) except KeyError: continue - if session[u'root_node'] is None: - session[u'root_node'] = NS_TICKETS - if not 'schema' in session: - session['schema'] = yield self._s.getSchemaForm(client, service, node or session[u'root_node']) + if session[u"root_node"] is None: + session[u"root_node"] = NS_TICKETS + if not "schema" in session: + session["schema"] = yield self._s.getSchemaForm( + client, service, node or session[u"root_node"] + ) defer.returnValue(item_import_data) @defer.inlineCallbacks @@ -122,40 +131,53 @@ # TODO: force "open" permission (except if private, check below) # TODO: handle "private" metadata, to have non public access for node # TODO: node access/publish model should be customisable - comments = ticket_data.get('comments', []) + comments = ticket_data.get("comments", []) service = yield self._m.getCommentsService(client) - node = self._m.getCommentsNode(session['root_node'] + u'_' + ticket_data['id']) - node_options = {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN, - self._p.OPT_PERSIST_ITEMS: 1, - self._p.OPT_MAX_ITEMS: -1, - self._p.OPT_DELIVER_PAYLOADS: 1, - self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, - self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN, - } + node = self._m.getCommentsNode(session["root_node"] + u"_" + ticket_data["id"]) + node_options = { + self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN, + self._p.OPT_PERSIST_ITEMS: 1, + self._p.OPT_MAX_ITEMS: -1, + self._p.OPT_DELIVER_PAYLOADS: 1, + self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, + self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN, + } yield self._p.createIfNewNode(client, service, node, options=node_options) - ticket_data['comments_uri'] = uri.buildXMPPUri(u'pubsub', subtype='microblog', path=service.full(), node=node) + ticket_data["comments_uri"] = uri.buildXMPPUri( + u"pubsub", subtype="microblog", path=service.full(), node=node + ) for comment in comments: - if 'updated' not in comment and 'published' in comment: + if "updated" not in comment and "published" in comment: # we don't want an automatic update date - comment['updated'] = comment['published'] + comment["updated"] = comment["published"] yield self._m.send(client, comment, service, node) def publishItem(self, client, ticket_data, service, node, session): if node is None: node = NS_TICKETS - id_ = ticket_data.pop('id', None) - log.debug(u"uploading item [{id}]: {title}".format(id=id_, title=ticket_data.get('title',''))) - return self._s.sendDataFormItem(client, service, node, ticket_data, session['schema'], id_) + id_ = ticket_data.pop("id", None) + log.debug( + u"uploading item [{id}]: {title}".format( + id=id_, title=ticket_data.get("title", "") + ) + ) + return self._s.sendDataFormItem( + client, service, node, ticket_data, session["schema"], id_ + ) def itemFilters(self, client, ticket_data, session, options): mapping = options.get(OPT_MAPPING) if mapping is not None: if not isinstance(mapping, dict): - raise exceptions.DataError(_(u'mapping option must be a dictionary')) + raise exceptions.DataError(_(u"mapping option must be a dictionary")) for source, dest in mapping.iteritems(): if not isinstance(source, unicode) or not isinstance(dest, unicode): - raise exceptions.DataError(_(u'keys and values of mapping must be sources and destinations ticket fields')) + raise exceptions.DataError( + _( + u"keys and values of mapping must be sources and destinations ticket fields" + ) + ) if source in ticket_data: value = ticket_data.pop(source) if dest in FIELDS_LIST: @@ -163,6 +185,6 @@ values.append(value) else: if dest in ticket_data: - ticket_data[dest] = ticket_data[dest] + u'\n' + value + ticket_data[dest] = ticket_data[dest] + u"\n" + value else: ticket_data[dest] = value
--- a/sat/plugins/plugin_tickets_import_bugzilla.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_tickets_import_bugzilla.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,8 +20,10 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions + # from twisted.internet import threads from twisted.internet import defer import os.path @@ -36,25 +38,27 @@ C.PI_DEPENDENCIES: ["TICKETS_IMPORT"], C.PI_MAIN: "BugzillaImport", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Tickets importer for Bugzilla""") + C.PI_DESCRIPTION: _("""Tickets importer for Bugzilla"""), } SHORT_DESC = D_(u"import tickets from Bugzilla xml export file") -LONG_DESC = D_(u"""This importer handle Bugzilla xml export file. +LONG_DESC = D_( + u"""This importer handle Bugzilla xml export file. To use it, you'll need to export tickets using XML. Tickets will be uploaded with the same ID as for Bugzilla, any existing ticket with this ID will be replaced. location: you must use the absolute path to your .xml file -""") +""" +) STATUS_MAP = { - 'NEW': 'queued', - 'ASSIGNED': 'started', - 'RESOLVED': 'review', - 'CLOSED': 'closed', - 'REOPENED': 'started' # we loose data here because there is no need on basic workflow to have a reopened status + "NEW": "queued", + "ASSIGNED": "started", + "RESOLVED": "review", + "CLOSED": "closed", + "REOPENED": "started", # we loose data here because there is no need on basic workflow to have a reopened status } @@ -65,67 +69,73 @@ tickets = [] root = etree.parse(file_path) - for bug in root.xpath('bug'): + for bug in root.xpath("bug"): ticket = {} - ticket['id'] = bug.findtext('bug_id') - ticket['created'] = date_utils.date_parse(bug.findtext('creation_ts')) - ticket['updated'] = date_utils.date_parse(bug.findtext('delta_ts')) - ticket['title'] = bug.findtext('short_desc') - reporter_elt = bug.find('reporter') - ticket['author'] = reporter_elt.get('name') - if ticket['author'] is None: - if '@' in reporter_elt.text: - ticket['author'] = reporter_elt.text[:reporter_elt.text.find('@')].title() + ticket["id"] = bug.findtext("bug_id") + ticket["created"] = date_utils.date_parse(bug.findtext("creation_ts")) + ticket["updated"] = date_utils.date_parse(bug.findtext("delta_ts")) + ticket["title"] = bug.findtext("short_desc") + reporter_elt = bug.find("reporter") + ticket["author"] = reporter_elt.get("name") + if ticket["author"] is None: + if "@" in reporter_elt.text: + ticket["author"] = reporter_elt.text[ + : reporter_elt.text.find("@") + ].title() else: - ticket['author'] = u'no name' - ticket['author_email'] = reporter_elt.text - assigned_to_elt = bug.find('assigned_to') - ticket['assigned_to_name'] = assigned_to_elt.get('name') - ticket['assigned_to_email'] = assigned_to_elt.text - ticket['cc_emails'] = [e.text for e in bug.findall('cc')] - ticket['priority'] = bug.findtext('priority').lower().strip() - ticket['severity'] = bug.findtext('bug_severity').lower().strip() - ticket['product'] = bug.findtext('product') - ticket['component'] = bug.findtext('component') - ticket['version'] = bug.findtext('version') - ticket['platform'] = bug.findtext('rep_platform') - ticket['os'] = bug.findtext('op_sys') - ticket['status'] = STATUS_MAP.get(bug.findtext('bug_status'), 'queued') - ticket['milestone'] = bug.findtext('target_milestone') - + ticket["author"] = u"no name" + ticket["author_email"] = reporter_elt.text + assigned_to_elt = bug.find("assigned_to") + ticket["assigned_to_name"] = assigned_to_elt.get("name") + ticket["assigned_to_email"] = assigned_to_elt.text + ticket["cc_emails"] = [e.text for e in bug.findall("cc")] + ticket["priority"] = bug.findtext("priority").lower().strip() + ticket["severity"] = bug.findtext("bug_severity").lower().strip() + ticket["product"] = bug.findtext("product") + ticket["component"] = bug.findtext("component") + ticket["version"] = bug.findtext("version") + ticket["platform"] = bug.findtext("rep_platform") + ticket["os"] = bug.findtext("op_sys") + ticket["status"] = STATUS_MAP.get(bug.findtext("bug_status"), "queued") + ticket["milestone"] = bug.findtext("target_milestone") body = None comments = [] - for longdesc in bug.findall('long_desc'): + for longdesc in bug.findall("long_desc"): if body is None: - body = longdesc.findtext('thetext') + body = longdesc.findtext("thetext") else: - who = longdesc.find('who') - comment = {'id': longdesc.findtext('commentid'), - 'author_email': who.text, - 'published': date_utils.date_parse(longdesc.findtext('bug_when')), - 'author': who.get('name', who.text), - 'content': longdesc.findtext('thetext')} + who = longdesc.find("who") + comment = { + "id": longdesc.findtext("commentid"), + "author_email": who.text, + "published": date_utils.date_parse(longdesc.findtext("bug_when")), + "author": who.get("name", who.text), + "content": longdesc.findtext("thetext"), + } comments.append(comment) - ticket['body'] = body - ticket['comments'] = comments + ticket["body"] = body + ticket["comments"] = comments tickets.append(ticket) - tickets.sort(key = lambda t: int(t['id'])) + tickets.sort(key=lambda t: int(t["id"])) return (tickets, len(tickets)) class BugzillaImport(object): - def __init__(self, host): log.info(_(u"Bugilla Import plugin initialization")) self.host = host - host.plugins['TICKETS_IMPORT'].register('bugzilla', self.Import, SHORT_DESC, LONG_DESC) + host.plugins["TICKETS_IMPORT"].register( + "bugzilla", self.Import, SHORT_DESC, LONG_DESC + ) def Import(self, client, location, options=None): if not os.path.isabs(location): - raise exceptions.DataError(u"An absolute path to XML data need to be given as location") + raise exceptions.DataError( + u"An absolute path to XML data need to be given as location" + ) bugzilla_parser = BugzillaParser() # d = threads.deferToThread(bugzilla_parser.parse, location) d = defer.maybeDeferred(bugzilla_parser.parse, location)
--- a/sat/plugins/plugin_tmp_directory_subscription.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_tmp_directory_subscription.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) @@ -33,7 +34,7 @@ C.PI_RECOMMENDATIONS: [], C.PI_MAIN: "DirectorySubscription", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Implementation of directory subscription""") + C.PI_DESCRIPTION: _("""Implementation of directory subscription"""), } @@ -42,11 +43,15 @@ class DirectorySubscription(object): - def __init__(self, host): log.info(_("Directory subscription plugin initialization")) self.host = host - host.importMenu((D_("Service"), D_("Directory subscription")), self.subscribe, security_limit=1, help_string=D_("User directory subscription")) + host.importMenu( + (D_("Service"), D_("Directory subscription")), + self.subscribe, + security_limit=1, + help_string=D_("User directory subscription"), + ) def subscribe(self, raw_data, profile): """Request available commands on the jabber search service associated to profile's host. @@ -59,7 +64,9 @@ def got_services(services): service_jid = services[0] - session_id, session_data = self.host.plugins["XEP-0050"].requesting.newSession(profile=profile) + session_id, session_data = self.host.plugins[ + "XEP-0050" + ].requesting.newSession(profile=profile) session_data["jid"] = service_jid session_data["node"] = CMD_UPDATE_SUBSCRIBTION data = {"session_id": session_id}
--- a/sat/plugins/plugin_xep_0020.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0020.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from twisted.words.xish import domish @@ -33,7 +34,7 @@ from wokkel import disco, iwokkel, data_form -NS_FEATURE_NEG = 'http://jabber.org/protocol/feature-neg' +NS_FEATURE_NEG = "http://jabber.org/protocol/feature-neg" PLUGIN_INFO = { C.PI_NAME: "XEP 0020 Plugin", @@ -42,12 +43,11 @@ C.PI_PROTOCOLS: ["XEP-0020"], C.PI_MAIN: "XEP_0020", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Feature Negotiation""") + C.PI_DESCRIPTION: _("""Implementation of Feature Negotiation"""), } class XEP_0020(object): - def __init__(self, host): log.info(_("Plugin XEP_0020 initialization")) @@ -62,7 +62,7 @@ @raise exceptions.NotFound: no feature element found """ try: - feature_elt = elt.elements(NS_FEATURE_NEG, 'feature').next() + feature_elt = elt.elements(NS_FEATURE_NEG, "feature").next() except StopIteration: raise exceptions.NotFound return feature_elt @@ -100,7 +100,11 @@ values = form.fields[field].values result[field] = values[0] if values else None if len(values) > 1: - log.warning(_(u"More than one value choosed for {}, keeping the first one").format(field)) + log.warning( + _( + u"More than one value choosed for {}, keeping the first one" + ).format(field) + ) return result def negotiate(self, feature_elt, name, negotiable_values, namespace): @@ -126,8 +130,8 @@ @param options(dict): dict with feature as key and choosed option as value @param namespace (None, unicode): form namespace or None to ignore """ - feature_elt = domish.Element((NS_FEATURE_NEG, 'feature')) - x_form = data_form.Form('submit', formNamespace=namespace) + feature_elt = domish.Element((NS_FEATURE_NEG, "feature")) + x_form = data_form.Form("submit", formNamespace=namespace) x_form.makeFields(options) feature_elt.addChild(x_form.toElement()) return feature_elt @@ -138,11 +142,16 @@ @param options_dict(dict): dict with feature as key and iterable of acceptable options as value @param namespace(None, unicode): feature namespace """ - feature_elt = domish.Element((NS_FEATURE_NEG, 'feature')) - x_form = data_form.Form('form', formNamespace=namespace) + feature_elt = domish.Element((NS_FEATURE_NEG, "feature")) + x_form = data_form.Form("form", formNamespace=namespace) for field in options_dict: - x_form.addField(data_form.Field('list-single', field, - options=[data_form.Option(option) for option in options_dict[field]])) + x_form.addField( + data_form.Field( + "list-single", + field, + options=[data_form.Option(option) for option in options_dict[field]], + ) + ) feature_elt.addChild(x_form.toElement()) return feature_elt @@ -150,8 +159,8 @@ class XEP_0020_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_FEATURE_NEG)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0033.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0033.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from wokkel import disco, iwokkel @@ -27,6 +28,7 @@ from twisted.words.protocols.jabber.jid import JID from twisted.python import failure import copy + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: @@ -61,7 +63,7 @@ C.PI_DEPENDENCIES: [], C.PI_MAIN: "XEP_0033", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Extended Stanza Addressing""") + C.PI_DESCRIPTION: _("""Implementation of Extended Stanza Addressing"""), } @@ -69,38 +71,65 @@ """ Implementation for XEP 0033 """ + def __init__(self, host): log.info(_("Extended Stanza Addressing plugin initialization")) self.host = host self.internal_data = {} - host.trigger.add("sendMessage", self.sendMessageTrigger, trigger.TriggerManager.MIN_PRIORITY) + host.trigger.add( + "sendMessage", self.sendMessageTrigger, trigger.TriggerManager.MIN_PRIORITY + ) host.trigger.add("MessageReceived", self.messageReceivedTrigger) - def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments): + def sendMessageTrigger( + self, client, mess_data, pre_xml_treatments, post_xml_treatments + ): """Process the XEP-0033 related data to be sent""" profile = client.profile def treatment(mess_data): - if not 'address' in mess_data['extra']: + if not "address" in mess_data["extra"]: return mess_data def discoCallback(entities): if not entities: - log.warning(_("XEP-0033 is being used but the server doesn't support it!")) - raise failure.Failure(exceptions.CancelError(u'Cancelled by XEP-0033')) + log.warning( + _("XEP-0033 is being used but the server doesn't support it!") + ) + raise failure.Failure( + exceptions.CancelError(u"Cancelled by XEP-0033") + ) if mess_data["to"] not in entities: - expected = _(' or ').join([entity.userhost() for entity in entities]) - log.warning(_(u"Stanzas using XEP-0033 should be addressed to %(expected)s, not %(current)s!") % {'expected': expected, 'current': mess_data["to"]}) - log.warning(_(u"TODO: addressing has been fixed by the backend... fix it in the frontend!")) + expected = _(" or ").join([entity.userhost() for entity in entities]) + log.warning( + _( + u"Stanzas using XEP-0033 should be addressed to %(expected)s, not %(current)s!" + ) + % {"expected": expected, "current": mess_data["to"]} + ) + log.warning( + _( + u"TODO: addressing has been fixed by the backend... fix it in the frontend!" + ) + ) mess_data["to"] = list(entities)[0].userhostJID() - element = mess_data['xml'].addElement('addresses', NS_ADDRESS) - entries = [entry.split(':') for entry in mess_data['extra']['address'].split('\n') if entry != ''] + element = mess_data["xml"].addElement("addresses", NS_ADDRESS) + entries = [ + entry.split(":") + for entry in mess_data["extra"]["address"].split("\n") + if entry != "" + ] for type_, jid_ in entries: - element.addChild(domish.Element((None, 'address'), None, {'type': type_, 'jid': jid_})) + element.addChild( + domish.Element( + (None, "address"), None, {"type": type_, "jid": jid_} + ) + ) # when the prosody plugin is completed, we can immediately return mess_data from here self.sendAndStoreMessage(mess_data, entries, profile) log.debug("XEP-0033 took over") - raise failure.Failure(exceptions.CancelError(u'Cancelled by XEP-0033')) + raise failure.Failure(exceptions.CancelError(u"Cancelled by XEP-0033")) + d = self.host.findFeaturesSet(client, [NS_ADDRESS]) d.addCallbacks(discoCallback, lambda dummy: discoCallback(None)) return d @@ -122,6 +151,7 @@ - change the messageNew signal to eventually pass more than one recipient """ client = self.host.getClient(profile) + def send(mess_data, skip_send=False): d = defer.Deferred() if not skip_send: @@ -133,13 +163,13 @@ def discoCallback(entities, to_jid_s): history_data = copy.deepcopy(mess_data) - history_data['to'] = JID(to_jid_s) - history_data['xml']['to'] = to_jid_s + history_data["to"] = JID(to_jid_s) + history_data["xml"]["to"] = to_jid_s if entities: if entities not in self.internal_data[timestamp]: sent_data = copy.deepcopy(mess_data) - sent_data['to'] = JID(JID(to_jid_s).host) - sent_data['xml']['to'] = JID(to_jid_s).host + sent_data["to"] = JID(JID(to_jid_s).host) + sent_data["xml"]["to"] = JID(to_jid_s).host send(sent_data) self.internal_data[timestamp].append(entities) # we still need to fill the history and signal the echo... @@ -156,8 +186,12 @@ defer_list = [] for type_, jid_ in entries: d = defer.Deferred() - d.addCallback(self.host.findFeaturesSet, client=client, jid_=JID(JID(jid_).host)) - d.addCallbacks(discoCallback, errback, callbackArgs=[jid_], errbackArgs=[jid_]) + d.addCallback( + self.host.findFeaturesSet, client=client, jid_=JID(JID(jid_).host) + ) + d.addCallbacks( + discoCallback, errback, callbackArgs=[jid_], errbackArgs=[jid_] + ) d.callback([NS_ADDRESS]) defer_list.append(d) d = defer.Deferred().addCallback(lambda dummy: self.internal_data.pop(timestamp)) @@ -165,17 +199,21 @@ def messageReceivedTrigger(self, client, message, post_treat): """In order to save the addressing information in the history""" + def post_treat_addr(data, addresses): - data['extra']['addresses'] = "" + data["extra"]["addresses"] = "" for address in addresses: # Depending how message has been constructed, we could get here # some noise like "\n " instead of an address element. if isinstance(address, domish.Element): - data['extra']['addresses'] += '%s:%s\n' % (address['type'], address['jid']) + data["extra"]["addresses"] += "%s:%s\n" % ( + address["type"], + address["jid"], + ) return data try: - addresses = message.elements(NS_ADDRESS, 'addresses').next() + addresses = message.elements(NS_ADDRESS, "addresses").next() except StopIteration: pass # no addresses else: @@ -194,8 +232,8 @@ self.host = plugin_parent.host self.profile = profile - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_ADDRESS)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0047.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0047.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,6 +19,7 @@ from sat.core.i18n import _ from sat.core.log import getLogger + log = getLogger(__name__) from sat.core.constants import Const as C from sat.core import exceptions @@ -40,15 +41,15 @@ except ImportError: from wokkel.subprotocols import XMPPHandler -MESSAGE = '/message' +MESSAGE = "/message" IQ_SET = '/iq[@type="set"]' -NS_IBB = 'http://jabber.org/protocol/ibb' +NS_IBB = "http://jabber.org/protocol/ibb" IBB_OPEN = IQ_SET + '/open[@xmlns="' + NS_IBB + '"]' IBB_CLOSE = IQ_SET + '/close[@xmlns="' + NS_IBB + '" and @sid="{}"]' IBB_IQ_DATA = IQ_SET + '/data[@xmlns="' + NS_IBB + '" and @sid="{}"]' IBB_MESSAGE_DATA = MESSAGE + '/data[@xmlns="' + NS_IBB + '" and @sid="{}"]' TIMEOUT = 120 # timeout for workflow -DEFER_KEY = 'finished' # key of the deferred used to track session end +DEFER_KEY = "finished" # key of the deferred used to track session end PLUGIN_INFO = { C.PI_NAME: "In-Band Bytestream Plugin", @@ -58,7 +59,7 @@ C.PI_PROTOCOLS: ["XEP-0047"], C.PI_MAIN: "XEP_0047", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of In-Band Bytestreams""") + C.PI_DESCRIPTION: _("""Implementation of In-Band Bytestreams"""), } @@ -82,8 +83,11 @@ @param sid(unicode): session id of client.xep_0047_current_stream @param client: %(doc_client)s """ - log.info(u"In-Band Bytestream: TimeOut reached for id {sid} [{profile}]" - .format(sid=sid, profile=client.profile)) + log.info( + u"In-Band Bytestream: TimeOut reached for id {sid} [{profile}]".format( + sid=sid, profile=client.profile + ) + ) self._killSession(sid, client, "TIMEOUT") def _killSession(self, sid, client, failure_reason=None): @@ -101,14 +105,14 @@ return try: - observer_cb = session['observer_cb'] + observer_cb = session["observer_cb"] except KeyError: pass else: client.xmlstream.removeObserver(session["event_data"], observer_cb) - if session['timer'].active(): - session['timer'].cancel() + if session["timer"].active(): + session["timer"].cancel() del client.xep_0047_current_stream[sid] @@ -136,15 +140,15 @@ @return (dict): session data """ if sid in client.xep_0047_current_stream: - raise exceptions.ConflictError(u'A session with this id already exists !') - session_data = client.xep_0047_current_stream[sid] = \ - {'id': sid, - DEFER_KEY: defer.Deferred(), - 'to': to_jid, - 'stream_object': stream_object, - 'seq': -1, - 'timer': reactor.callLater(TIMEOUT, self._timeOut, sid, client), - } + raise exceptions.ConflictError(u"A session with this id already exists !") + session_data = client.xep_0047_current_stream[sid] = { + "id": sid, + DEFER_KEY: defer.Deferred(), + "to": to_jid, + "stream_object": stream_object, + "seq": -1, + "timer": reactor.callLater(TIMEOUT, self._timeOut, sid, client), + } return session_data @@ -155,19 +159,21 @@ """ log.debug(_(u"IBB stream opening")) iq_elt.handled = True - open_elt = iq_elt.elements(NS_IBB, 'open').next() - block_size = open_elt.getAttribute('block-size') - sid = open_elt.getAttribute('sid') - stanza = open_elt.getAttribute('stanza', 'iq') + open_elt = iq_elt.elements(NS_IBB, "open").next() + block_size = open_elt.getAttribute("block-size") + sid = open_elt.getAttribute("sid") + stanza = open_elt.getAttribute("stanza", "iq") if not sid or not block_size or int(block_size) > 65535: - return self._sendError('not-acceptable', sid or None, iq_elt, client) + return self._sendError("not-acceptable", sid or None, iq_elt, client) if not sid in client.xep_0047_current_stream: log.warning(_(u"Ignoring unexpected IBB transfer: %s" % sid)) - return self._sendError('not-acceptable', sid or None, iq_elt, client) + return self._sendError("not-acceptable", sid or None, iq_elt, client) session_data = client.xep_0047_current_stream[sid] - if session_data["to"] != jid.JID(iq_elt['from']): - log.warning(_("sended jid inconsistency (man in the middle attack attempt ?)")) - return self._sendError('not-acceptable', sid, iq_elt, client) + if session_data["to"] != jid.JID(iq_elt["from"]): + log.warning( + _("sended jid inconsistency (man in the middle attack attempt ?)") + ) + return self._sendError("not-acceptable", sid, iq_elt, client) # at this stage, the session looks ok and will be accepted @@ -175,7 +181,9 @@ session_data["timer"].reset(TIMEOUT) # we save the xmlstream, events and observer data to allow observer removal - session_data["event_data"] = event_data = (IBB_MESSAGE_DATA if stanza == 'message' else IBB_IQ_DATA).format(sid) + session_data["event_data"] = event_data = ( + IBB_MESSAGE_DATA if stanza == "message" else IBB_IQ_DATA + ).format(sid) session_data["observer_cb"] = observer_cb = self._onIBBData event_close = IBB_CLOSE.format(sid) # we now set the stream observer to look after data packet @@ -184,7 +192,7 @@ client.xmlstream.addObserver(event_data, observer_cb, client=client) client.xmlstream.addOnetimeObserver(event_close, self._onIBBClose, client=client) # finally, we send the accept stanza - iq_result_elt = xmlstream.toResponse(iq_elt, 'result') + iq_result_elt = xmlstream.toResponse(iq_elt, "result") client.send(iq_result_elt) def _onIBBClose(self, iq_elt, client): @@ -194,11 +202,11 @@ """ iq_elt.handled = True log.debug(_("IBB stream closing")) - close_elt = iq_elt.elements(NS_IBB, 'close').next() + close_elt = iq_elt.elements(NS_IBB, "close").next() # XXX: this observer is only triggered on valid sid, so we don't need to check it - sid = close_elt['sid'] + sid = close_elt["sid"] - iq_result_elt = xmlstream.toResponse(iq_elt, 'result') + iq_result_elt = xmlstream.toResponse(iq_elt, "result") client.send(iq_result_elt) self._killSession(sid, client) @@ -209,29 +217,33 @@ @param element(domish.Element): <iq> or <message> stanza """ element.handled = True - data_elt = element.elements(NS_IBB, 'data').next() - sid = data_elt['sid'] + data_elt = element.elements(NS_IBB, "data").next() + sid = data_elt["sid"] try: session_data = client.xep_0047_current_stream[sid] except KeyError: log.warning(_(u"Received data for an unknown session id")) - return self._sendError('item-not-found', None, element, client) + return self._sendError("item-not-found", None, element, client) from_jid = session_data["to"] stream_object = session_data["stream_object"] - if from_jid.full() != element['from']: - log.warning(_(u"sended jid inconsistency (man in the middle attack attempt ?)\ninitial={initial}\ngiven={given}").format(initial=from_jid, given=element['from'])) - if element.name == 'iq': - self._sendError('not-acceptable', sid, element, client) + if from_jid.full() != element["from"]: + log.warning( + _( + u"sended jid inconsistency (man in the middle attack attempt ?)\ninitial={initial}\ngiven={given}" + ).format(initial=from_jid, given=element["from"]) + ) + if element.name == "iq": + self._sendError("not-acceptable", sid, element, client) return session_data["seq"] = (session_data["seq"] + 1) % 65535 if int(data_elt.getAttribute("seq", -1)) != session_data["seq"]: log.warning(_(u"Sequence error")) - if element.name == 'iq': - reason = 'not-acceptable' + if element.name == "iq": + reason = "not-acceptable" self._sendError(reason, sid, element, client) self.terminateStream(session_data, client, reason) return @@ -245,14 +257,14 @@ except TypeError: # The base64 data is invalid log.warning(_(u"Invalid base64 data")) - if element.name == 'iq': - self._sendError('not-acceptable', sid, element, client) + if element.name == "iq": + self._sendError("not-acceptable", sid, element, client) self.terminateStream(session_data, client, reason) return # we can now ack success - if element.name == 'iq': - iq_result_elt = xmlstream.toResponse(element, 'result') + if element.name == "iq": + iq_result_elt = xmlstream.toResponse(element, "result") client.send(iq_result_elt) def _sendError(self, error_condition, sid, iq_elt, client): @@ -264,7 +276,11 @@ @param client: %(doc_client)s """ iq_elt = error.StanzaError(error_condition).toResponse(iq_elt) - log.warning(u"Error while managing in-band bytestream session, cancelling: {}".format(error_condition)) + log.warning( + u"Error while managing in-band bytestream session, cancelling: {}".format( + error_condition + ) + ) if sid is not None: self._killSession(sid, client, error_condition) client.send(iq_elt) @@ -285,11 +301,11 @@ session_data["block_size"] = block_size iq_elt = client.IQ() - iq_elt['to'] = to_jid.full() - open_elt = iq_elt.addElement((NS_IBB, 'open')) - open_elt['block-size'] = str(block_size) - open_elt['sid'] = sid - open_elt['stanza'] = 'iq' # TODO: manage <message> stanza ? + iq_elt["to"] = to_jid.full() + open_elt = iq_elt.addElement((NS_IBB, "open")) + open_elt["block-size"] = str(block_size) + open_elt["sid"] = sid + open_elt["stanza"] = "iq" # TODO: manage <message> stanza ? args = [session_data, client] d = iq_elt.send() d.addCallbacks(self._IQDataStreamCb, self._IQDataStreamEb, args, None, args) @@ -307,11 +323,11 @@ buffer_ = session_data["stream_object"].read(session_data["block_size"]) if buffer_: next_iq_elt = client.IQ() - next_iq_elt['to'] = session_data["to"].full() - data_elt = next_iq_elt.addElement((NS_IBB, 'data')) - seq = session_data['seq'] = (session_data['seq'] + 1) % 65535 - data_elt['seq'] = unicode(seq) - data_elt['sid'] = session_data['id'] + next_iq_elt["to"] = session_data["to"].full() + data_elt = next_iq_elt.addElement((NS_IBB, "data")) + seq = session_data["seq"] = (session_data["seq"] + 1) % 65535 + data_elt["seq"] = unicode(seq) + data_elt["sid"] = session_data["id"] data_elt.addContent(base64.b64encode(buffer_)) args = [session_data, client] d = next_iq_elt.send() @@ -334,11 +350,11 @@ @param failure_reason(unicode, None): reason of the failure, or None if steam was successful """ iq_elt = client.IQ() - iq_elt['to'] = session_data["to"].full() - close_elt = iq_elt.addElement((NS_IBB, 'close')) - close_elt['sid'] = session_data['id'] + iq_elt["to"] = session_data["to"].full() + close_elt = iq_elt.addElement((NS_IBB, "close")) + close_elt["sid"] = session_data["id"] iq_elt.send() - self._killSession(session_data['id'], client, failure_reason) + self._killSession(session_data["id"], client, failure_reason) class XEP_0047_handler(XMPPHandler): @@ -348,10 +364,12 @@ self.plugin_parent = parent def connectionInitialized(self): - self.xmlstream.addObserver(IBB_OPEN, self.plugin_parent._onIBBOpen, client=self.parent) + self.xmlstream.addObserver( + IBB_OPEN, self.plugin_parent._onIBBOpen, client=self.parent + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_IBB)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0048.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0048.py Wed Jun 27 20:14:46 2018 +0200 @@ -23,6 +23,7 @@ from sat.memory.persistent import PersistentBinaryDict from sat.tools import xml_tools from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.xish import domish from twisted.words.protocols.jabber import jid @@ -30,7 +31,7 @@ from twisted.internet import defer -NS_BOOKMARKS = 'storage:bookmarks' +NS_BOOKMARKS = "storage:bookmarks" PLUGIN_INFO = { C.PI_NAME: "Bookmarks", @@ -41,28 +42,56 @@ C.PI_RECOMMENDATIONS: ["XEP-0049"], C.PI_MAIN: "XEP_0048", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Implementation of bookmarks""") + C.PI_DESCRIPTION: _("""Implementation of bookmarks"""), } class XEP_0048(object): - MUC_TYPE = 'muc' - URL_TYPE = 'url' - MUC_KEY = 'jid' - URL_KEY = 'url' - MUC_ATTRS = ('autojoin', 'name') - URL_ATTRS = ('name',) + MUC_TYPE = "muc" + URL_TYPE = "url" + MUC_KEY = "jid" + URL_KEY = "url" + MUC_ATTRS = ("autojoin", "name") + URL_ATTRS = ("name",) def __init__(self, host): log.info(_("Bookmarks plugin initialization")) self.host = host # self.__menu_id = host.registerCallback(self._bookmarksMenu, with_data=True) self.__bm_save_id = host.registerCallback(self._bookmarksSaveCb, with_data=True) - host.importMenu((D_("Groups"), D_("Bookmarks")), self._bookmarksMenu, security_limit=0, help_string=D_("Use and manage bookmarks")) - self.__selected_id = host.registerCallback(self._bookmarkSelectedCb, with_data=True) - host.bridge.addMethod("bookmarksList", ".plugin", in_sign='sss', out_sign='a{sa{sa{ss}}}', method=self._bookmarksList, async=True) - host.bridge.addMethod("bookmarksRemove", ".plugin", in_sign='ssss', out_sign='', method=self._bookmarksRemove, async=True) - host.bridge.addMethod("bookmarksAdd", ".plugin", in_sign='ssa{ss}ss', out_sign='', method=self._bookmarksAdd, async=True) + host.importMenu( + (D_("Groups"), D_("Bookmarks")), + self._bookmarksMenu, + security_limit=0, + help_string=D_("Use and manage bookmarks"), + ) + self.__selected_id = host.registerCallback( + self._bookmarkSelectedCb, with_data=True + ) + host.bridge.addMethod( + "bookmarksList", + ".plugin", + in_sign="sss", + out_sign="a{sa{sa{ss}}}", + method=self._bookmarksList, + async=True, + ) + host.bridge.addMethod( + "bookmarksRemove", + ".plugin", + in_sign="ssss", + out_sign="", + method=self._bookmarksRemove, + async=True, + ) + host.bridge.addMethod( + "bookmarksAdd", + ".plugin", + in_sign="ssa{ss}ss", + out_sign="", + method=self._bookmarksAdd, + async=True, + ) try: self.private_plg = self.host.plugins["XEP-0049"] except KeyError: @@ -74,20 +103,22 @@ @defer.inlineCallbacks def profileConnected(self, client): - local = client.bookmarks_local = PersistentBinaryDict(NS_BOOKMARKS, client.profile) + local = client.bookmarks_local = PersistentBinaryDict( + NS_BOOKMARKS, client.profile + ) yield local.load() if not local: local[XEP_0048.MUC_TYPE] = dict() local[XEP_0048.URL_TYPE] = dict() - private = yield self._getServerBookmarks('private', client.profile) + private = yield self._getServerBookmarks("private", client.profile) pubsub = client.bookmarks_pubsub = None for bookmarks in (local, private, pubsub): if bookmarks is not None: for (room_jid, data) in bookmarks[XEP_0048.MUC_TYPE].items(): - if data.get('autojoin', 'false') == 'true': - nick = data.get('nick', client.jid.user) - self.host.plugins['XEP-0045'].join(client, room_jid, nick, {}) + if data.get("autojoin", "false") == "true": + nick = data.get("nick", client.jid.user) + self.host.plugins["XEP-0045"].join(client, room_jid, nick, {}) @defer.inlineCallbacks def _getServerBookmarks(self, storage_type, profile): @@ -101,14 +132,18 @@ @return: data dictionary, or None if feature is not available """ client = self.host.getClient(profile) - if storage_type == 'private': + if storage_type == "private": try: - bookmarks_private_xml = yield self.private_plg.privateXMLGet('storage', NS_BOOKMARKS, profile) - data = client.bookmarks_private = self._bookmarkElt2Dict(bookmarks_private_xml) + bookmarks_private_xml = yield self.private_plg.privateXMLGet( + "storage", NS_BOOKMARKS, profile + ) + data = client.bookmarks_private = self._bookmarkElt2Dict( + bookmarks_private_xml + ) except (StanzaError, AttributeError): log.info(_("Private XML storage not available")) data = client.bookmarks_private = None - elif storage_type == 'pubsub': + elif storage_type == "pubsub": raise NotImplementedError else: raise ValueError("storage_type must be 'private' or 'pubsub'") @@ -124,9 +159,9 @@ @param bookmarks_elt (domish.Element): bookmarks XML @param profile: %(doc_profile)s """ - if storage_type == 'private': + if storage_type == "private": yield self.private_plg.privateXMLStore(bookmarks_elt, profile) - elif storage_type == 'pubsub': + elif storage_type == "pubsub": raise NotImplementedError else: raise ValueError("storage_type must be 'private' or 'pubsub'") @@ -142,12 +177,14 @@ conf_data = {} url_data = {} - conference_elts = storage_elt.elements(NS_BOOKMARKS, 'conference') + conference_elts = storage_elt.elements(NS_BOOKMARKS, "conference") for conference_elt in conference_elts: try: room_jid = jid.JID(conference_elt[XEP_0048.MUC_KEY]) except KeyError: - log.warning ("invalid bookmark found, igoring it:\n%s" % conference_elt.toXml()) + log.warning( + "invalid bookmark found, igoring it:\n%s" % conference_elt.toXml() + ) continue data = conf_data[room_jid] = {} @@ -156,17 +193,19 @@ if conference_elt.hasAttribute(attr): data[attr] = conference_elt[attr] try: - data['nick'] = unicode(conference_elt.elements(NS_BOOKMARKS, 'nick').next()) + data["nick"] = unicode( + conference_elt.elements(NS_BOOKMARKS, "nick").next() + ) except StopIteration: pass # TODO: manage password (need to be secured, see XEP-0049 §4) - url_elts = storage_elt.elements(NS_BOOKMARKS, 'url') + url_elts = storage_elt.elements(NS_BOOKMARKS, "url") for url_elt in url_elts: try: url = url_elt[XEP_0048.URL_KEY] except KeyError: - log.warning ("invalid bookmark found, igoring it:\n%s" % url_elt.toXml()) + log.warning("invalid bookmark found, igoring it:\n%s" % url_elt.toXml()) continue data = url_data[url] = {} for attr in XEP_0048.URL_ATTRS: @@ -185,9 +224,9 @@ """ rooms_data = data.get(XEP_0048.MUC_TYPE, {}) urls_data = data.get(XEP_0048.URL_TYPE, {}) - storage_elt = domish.Element((NS_BOOKMARKS, 'storage')) + storage_elt = domish.Element((NS_BOOKMARKS, "storage")) for room_jid in rooms_data: - conference_elt = storage_elt.addElement('conference') + conference_elt = storage_elt.addElement("conference") conference_elt[XEP_0048.MUC_KEY] = room_jid.full() for attr in XEP_0048.MUC_ATTRS: try: @@ -195,12 +234,12 @@ except KeyError: pass try: - conference_elt.addElement('nick', content=rooms_data[room_jid]['nick']) + conference_elt.addElement("nick", content=rooms_data[room_jid]["nick"]) except KeyError: pass for url in urls_data: - url_elt = storage_elt.addElement('url') + url_elt = storage_elt.addElement("url") url_elt[XEP_0048.URL_KEY] = url for attr in XEP_0048.URL_ATTRS: try: @@ -212,18 +251,20 @@ def _bookmarkSelectedCb(self, data, profile): try: - room_jid_s, nick = data['index'].split(' ', 1) + room_jid_s, nick = data["index"].split(" ", 1) room_jid = jid.JID(room_jid_s) except (KeyError, RuntimeError): log.warning(_("No room jid selected")) return {} client = self.host.getClient(profile) - d = self.host.plugins['XEP-0045'].join(client, room_jid, nick, {}) + d = self.host.plugins["XEP-0045"].join(client, room_jid, nick, {}) + def join_eb(failure): log.warning(u"Error while trying to join room: {}".format(failure)) # FIXME: failure are badly managed in plugin XEP-0045. Plugin XEP-0045 need to be fixed before managing errors correctly here return {} + d.addCallbacks(lambda dummy: {}, join_eb) return d @@ -233,40 +274,54 @@ """ client = self.host.getClient(profile) - xmlui = xml_tools.XMLUI(title=_('Bookmarks manager')) - adv_list = xmlui.changeContainer('advanced_list', columns=3, selectable='single', callback_id=self.__selected_id) - for bookmarks in (client.bookmarks_local, client.bookmarks_private, client.bookmarks_pubsub): + xmlui = xml_tools.XMLUI(title=_("Bookmarks manager")) + adv_list = xmlui.changeContainer( + "advanced_list", + columns=3, + selectable="single", + callback_id=self.__selected_id, + ) + for bookmarks in ( + client.bookmarks_local, + client.bookmarks_private, + client.bookmarks_pubsub, + ): if bookmarks is None: continue - for (room_jid, data) in sorted(bookmarks[XEP_0048.MUC_TYPE].items(), key=lambda item: item[1].get('name',item[0].user)): + for (room_jid, data) in sorted( + bookmarks[XEP_0048.MUC_TYPE].items(), + key=lambda item: item[1].get("name", item[0].user), + ): room_jid_s = room_jid.full() - adv_list.setRowIndex(u'%s %s' % (room_jid_s, data.get('nick') or client.jid.user)) - xmlui.addText(data.get('name','')) + adv_list.setRowIndex( + u"%s %s" % (room_jid_s, data.get("nick") or client.jid.user) + ) + xmlui.addText(data.get("name", "")) xmlui.addJid(room_jid) - if data.get('autojoin', 'false') == 'true': - xmlui.addText('autojoin') + if data.get("autojoin", "false") == "true": + xmlui.addText("autojoin") else: xmlui.addEmpty() adv_list.end() - xmlui.addDivider('dash') + xmlui.addDivider("dash") xmlui.addText(_("add a bookmark")) xmlui.changeContainer("pairs") - xmlui.addLabel(_('Name')) - xmlui.addString('name') - xmlui.addLabel(_('jid')) - xmlui.addString('jid') - xmlui.addLabel(_('Nickname')) - xmlui.addString('nick', client.jid.user) - xmlui.addLabel(_('Autojoin')) - xmlui.addBool('autojoin') + xmlui.addLabel(_("Name")) + xmlui.addString("name") + xmlui.addLabel(_("jid")) + xmlui.addString("jid") + xmlui.addLabel(_("Nickname")) + xmlui.addString("nick", client.jid.user) + xmlui.addLabel(_("Autojoin")) + xmlui.addBool("autojoin") xmlui.changeContainer("vertical") - xmlui.addButton(self.__bm_save_id, _("Save"), ('name', 'jid', 'nick', 'autojoin')) - return {'xmlui': xmlui.toXml()} + xmlui.addButton(self.__bm_save_id, _("Save"), ("name", "jid", "nick", "autojoin")) + return {"xmlui": xmlui.toXml()} def _bookmarksSaveCb(self, data, profile): bm_data = xml_tools.XMLUIResult2DataFormResult(data) try: - location = jid.JID(bm_data.pop('jid')) + location = jid.JID(bm_data.pop("jid")) except KeyError: raise exceptions.InternalError("Can't find mandatory key") d = self.addBookmark(XEP_0048.MUC_TYPE, location, bm_data, profile_key=profile) @@ -274,7 +329,9 @@ return d @defer.inlineCallbacks - def addBookmark(self, type_, location, data, storage_type="auto", profile_key=C.PROF_KEY_NONE): + def addBookmark( + self, type_, location, data, storage_type="auto", profile_key=C.PROF_KEY_NONE + ): """Store a new bookmark @param type_: bookmark type, one of: @@ -293,21 +350,21 @@ - "local": Store in SàT database @param profile_key: %(doc_profile_key)s """ - assert storage_type in ('auto', 'pubsub', 'private', 'local') - if type_ == XEP_0048.URL_TYPE and {'autojoin', 'nick'}.intersection(data.keys()): - raise ValueError("autojoin or nick can't be used with URLs") + assert storage_type in ("auto", "pubsub", "private", "local") + if type_ == XEP_0048.URL_TYPE and {"autojoin", "nick"}.intersection(data.keys()): + raise ValueError("autojoin or nick can't be used with URLs") client = self.host.getClient(profile_key) - if storage_type == 'auto': + if storage_type == "auto": if client.bookmarks_pubsub is not None: - storage_type = 'pubsub' + storage_type = "pubsub" elif client.bookmarks_private is not None: - storage_type = 'private' + storage_type = "private" else: - storage_type = 'local' + storage_type = "local" log.warning(_("Bookmarks will be local only")) log.info(_('Type selected for "auto" storage: %s') % storage_type) - if storage_type == 'local': + if storage_type == "local": client.bookmarks_local[type_][location] = data yield client.bookmarks_local.force(type_) else: @@ -317,7 +374,9 @@ yield self._setServerBookmarks(storage_type, bookmark_elt, client.profile) @defer.inlineCallbacks - def removeBookmark(self, type_, location, storage_type="all", profile_key=C.PROF_KEY_NONE): + def removeBookmark( + self, type_, location, storage_type="all", profile_key=C.PROF_KEY_NONE + ): """Remove a stored bookmark @param type_: bookmark type, one of: @@ -331,26 +390,26 @@ - "local": Store in SàT database @param profile_key: %(doc_profile_key)s """ - assert storage_type in ('all', 'pubsub', 'private', 'local') + assert storage_type in ("all", "pubsub", "private", "local") client = self.host.getClient(profile_key) - if storage_type in ('all', 'local'): + if storage_type in ("all", "local"): try: del client.bookmarks_local[type_][location] yield client.bookmarks_local.force(type_) except KeyError: log.debug("Bookmark is not present in local storage") - if storage_type in ('all', 'private'): - bookmarks = yield self._getServerBookmarks('private', client.profile) + if storage_type in ("all", "private"): + bookmarks = yield self._getServerBookmarks("private", client.profile) try: del bookmarks[type_][location] bookmark_elt = self._dict2BookmarkElt(type_, bookmarks) - yield self._setServerBookmarks('private', bookmark_elt, client.profile) + yield self._setServerBookmarks("private", bookmark_elt, client.profile) except KeyError: log.debug("Bookmark is not present in private storage") - if storage_type == 'pubsub': + if storage_type == "pubsub": raise NotImplementedError def _bookmarksList(self, type_, storage_location, profile_key=C.PROF_KEY_NONE): @@ -381,10 +440,10 @@ ret[_storage_location][bookmark.full()] = data[bookmark].copy() return ret - for _storage_location in ('local', 'private', 'pubsub'): - if storage_location in ('all', _storage_location): + for _storage_location in ("local", "private", "pubsub"): + if storage_location in ("all", _storage_location): ret[_storage_location] = {} - if _storage_location in ('private',): + if _storage_location in ("private",): # we update distant bookmarks, just in case an other client added something d = self._getServerBookmarks(_storage_location, client.profile) else: @@ -394,7 +453,9 @@ return ret_d - def _bookmarksRemove(self, type_, location, storage_location, profile_key=C.PROF_KEY_NONE): + def _bookmarksRemove( + self, type_, location, storage_location, profile_key=C.PROF_KEY_NONE + ): """Return stored bookmarks @param type_: bookmark type, one of: @@ -412,7 +473,9 @@ location = jid.JID(location) return self.removeBookmark(type_, location, storage_location, profile_key) - def _bookmarksAdd(self, type_, location, data, storage_type="auto", profile_key=C.PROF_KEY_NONE): + def _bookmarksAdd( + self, type_, location, data, storage_type="auto", profile_key=C.PROF_KEY_NONE + ): if type_ == XEP_0048.MUC_TYPE: location = jid.JID(location) return self.addBookmark(type_, location, data, storage_type, profile_key) @@ -427,21 +490,26 @@ txt_cmd = self.host.plugins[C.TEXT_CMDS] options = mess_data["unparsed"].strip().split() - if options and options[0] not in ('autojoin', 'remove'): + if options and options[0] not in ("autojoin", "remove"): txt_cmd.feedBack(client, _("Bad arguments"), mess_data) return False room_jid = mess_data["to"].userhostJID() if "remove" in options: - self.removeBookmark(XEP_0048.MUC_TYPE, room_jid, profile_key = client.profile) - txt_cmd.feedBack(client, _("All [%s] bookmarks are being removed") % room_jid.full(), mess_data) + self.removeBookmark(XEP_0048.MUC_TYPE, room_jid, profile_key=client.profile) + txt_cmd.feedBack( + client, + _("All [%s] bookmarks are being removed") % room_jid.full(), + mess_data, + ) return False - data = { "name": room_jid.user, - "nick": client.jid.user, - "autojoin": "true" if "autojoin" in options else "false", - } + data = { + "name": room_jid.user, + "nick": client.jid.user, + "autojoin": "true" if "autojoin" in options else "false", + } self.addBookmark(XEP_0048.MUC_TYPE, room_jid, data, profile_key=client.profile) txt_cmd.feedBack(client, _("Bookmark added"), mess_data)
--- a/sat/plugins/plugin_xep_0049.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0049.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,12 +20,12 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from wokkel import compat from twisted.words.xish import domish - PLUGIN_INFO = { C.PI_NAME: "XEP-0049 Plugin", C.PI_IMPORT_NAME: "XEP-0049", @@ -34,12 +34,12 @@ C.PI_DEPENDENCIES: [], C.PI_MAIN: "XEP_0049", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Implementation of private XML storage""") + C.PI_DESCRIPTION: _("""Implementation of private XML storage"""), } class XEP_0049(object): - NS_PRIVATE = 'jabber:iq:private' + NS_PRIVATE = "jabber:iq:private" def __init__(self, host): log.info(_("Plugin XEP-0049 initialization")) @@ -55,7 +55,7 @@ client = self.host.getClient(profile_key) # XXX: feature announcement in disco#info is not mandatory in XEP-0049, so we have to try to use private XML, and react according to the answer iq_elt = compat.IQ(client.xmlstream) - query_elt = iq_elt.addElement('query', XEP_0049.NS_PRIVATE) + query_elt = iq_elt.addElement("query", XEP_0049.NS_PRIVATE) query_elt.addChild(element) return iq_elt.send() @@ -69,13 +69,14 @@ """ client = self.host.getClient(profile_key) # XXX: see privateXMLStore note about feature checking - iq_elt = compat.IQ(client.xmlstream, 'get') - query_elt = iq_elt.addElement('query', XEP_0049.NS_PRIVATE) + iq_elt = compat.IQ(client.xmlstream, "get") + query_elt = iq_elt.addElement("query", XEP_0049.NS_PRIVATE) query_elt.addElement(node_name, namespace) + def getCb(answer_iq_elt): - answer_query_elt = answer_iq_elt.elements(XEP_0049.NS_PRIVATE, 'query').next() + answer_query_elt = answer_iq_elt.elements(XEP_0049.NS_PRIVATE, "query").next() return answer_query_elt.firstChildElement() + d = iq_elt.send() d.addCallback(getCb) return d -
--- a/sat/plugins/plugin_xep_0050.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0050.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.protocols.jabber import jid from twisted.words.protocols import jabber @@ -41,7 +42,7 @@ from collections import namedtuple try: - from collections import OrderedDict # only available from python 2.7 + from collections import OrderedDict # only available from python 2.7 except ImportError: from ordereddict import OrderedDict @@ -51,12 +52,16 @@ ID_CMD_NODE = disco.DiscoIdentity("automation", "command-node") CMD_REQUEST = IQ_SET + '/command[@xmlns="' + NS_COMMANDS + '"]' -SHOWS = OrderedDict([('default', _('Online')), - ('away', _('Away')), - ('chat', _('Free for chat')), - ('dnd', _('Do not disturb')), - ('xa', _('Left')), - ('disconnect', _('Disconnect'))]) +SHOWS = OrderedDict( + [ + ("default", _("Online")), + ("away", _("Away")), + ("chat", _("Free for chat")), + ("dnd", _("Do not disturb")), + ("xa", _("Left")), + ("disconnect", _("Disconnect")), + ] +) PLUGIN_INFO = { C.PI_NAME: "Ad-Hoc Commands", @@ -65,12 +70,11 @@ C.PI_PROTOCOLS: ["XEP-0050"], C.PI_MAIN: "XEP_0050", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Ad-Hoc Commands""") + C.PI_DESCRIPTION: _("""Implementation of Ad-Hoc Commands"""), } class AdHocError(Exception): - def __init__(self, error_const): """ Error to be used from callback @param error_const: one of XEP_0050.ERROR @@ -78,16 +82,37 @@ assert error_const in XEP_0050.ERROR self.callback_error = error_const + class AdHocCommand(XMPPHandler): implements(iwokkel.IDisco) - def __init__(self, parent, callback, label, node, features, timeout, allowed_jids, allowed_groups, allowed_magics, forbidden_jids, forbidden_groups, client): + def __init__( + self, + parent, + callback, + label, + node, + features, + timeout, + allowed_jids, + allowed_groups, + allowed_magics, + forbidden_jids, + forbidden_groups, + client, + ): self.parent = parent self.callback = callback self.label = label self.node = node self.features = [disco.DiscoFeature(feature) for feature in features] - self.allowed_jids, self.allowed_groups, self.allowed_magics, self.forbidden_jids, self.forbidden_groups = allowed_jids, allowed_groups, allowed_magics, forbidden_jids, forbidden_groups + self.allowed_jids, self.allowed_groups, self.allowed_magics, self.forbidden_jids, self.forbidden_groups = ( + allowed_jids, + allowed_groups, + allowed_magics, + forbidden_jids, + forbidden_groups, + ) self.client = client self.sessions = Sessions(timeout=timeout) @@ -95,7 +120,7 @@ return self.label def isAuthorised(self, requestor): - if '@ALL@' in self.allowed_magics: + if "@ALL@" in self.allowed_magics: return True forbidden = set(self.forbidden_jids) for group in self.forbidden_groups: @@ -107,18 +132,25 @@ try: allowed.update(self.client.roster.getJidsFromGroup(group)) except exceptions.UnknownGroupError: - log.warning(_(u"The groups [%(group)s] is unknown for profile [%(profile)s])" % {'group':group, 'profile':self.client.profile})) + log.warning( + _( + u"The groups [%(group)s] is unknown for profile [%(profile)s])" + % {"group": group, "profile": self.client.profile} + ) + ) if requestor.userhostJID() in allowed: return True return False - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): - if nodeIdentifier != NS_COMMANDS: # FIXME: we should manage other disco nodes here + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): + if ( + nodeIdentifier != NS_COMMANDS + ): # FIXME: we should manage other disco nodes here return [] # identities = [ID_CMD_LIST if self.node == NS_COMMANDS else ID_CMD_NODE] # FIXME return [disco.DiscoFeature(NS_COMMANDS)] + self.features - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] def _sendAnswer(self, callback_data, session_id, request): @@ -134,30 +166,30 @@ @return: deferred """ payload, status, actions, note = callback_data - assert(isinstance(payload, domish.Element) or payload is None) - assert(status in XEP_0050.STATUS) + assert isinstance(payload, domish.Element) or payload is None + assert status in XEP_0050.STATUS if not actions: actions = [XEP_0050.ACTION.EXECUTE] - result = domish.Element((None, 'iq')) - result['type'] = 'result' - result['id'] = request['id'] - result['to'] = request['from'] - command_elt = result.addElement('command', NS_COMMANDS) - command_elt['sessionid'] = session_id - command_elt['node'] = self.node - command_elt['status'] = status + result = domish.Element((None, "iq")) + result["type"] = "result" + result["id"] = request["id"] + result["to"] = request["from"] + command_elt = result.addElement("command", NS_COMMANDS) + command_elt["sessionid"] = session_id + command_elt["node"] = self.node + command_elt["status"] = status if status != XEP_0050.STATUS.CANCELED: if status != XEP_0050.STATUS.COMPLETED: - actions_elt = command_elt.addElement('actions') - actions_elt['execute'] = actions[0] + actions_elt = command_elt.addElement("actions") + actions_elt["execute"] = actions[0] for action in actions: actions_elt.addElement(action) if note is not None: note_type, note_mess = note - note_elt = command_elt.addElement('note', content=note_mess) - note_elt['type'] = note_type + note_elt = command_elt.addElement("note", content=note_mess) + note_elt["type"] = note_type if payload is not None: command_elt.addChild(payload) @@ -181,54 +213,116 @@ def onRequest(self, command_elt, requestor, action, session_id): if not self.isAuthorised(requestor): - return self._sendError(XEP_0050.ERROR.FORBIDDEN, session_id, command_elt.parent) + return self._sendError( + XEP_0050.ERROR.FORBIDDEN, session_id, command_elt.parent + ) if session_id: try: session_data = self.sessions[session_id] except KeyError: - return self._sendError(XEP_0050.ERROR.SESSION_EXPIRED, session_id, command_elt.parent) - if session_data['requestor'] != requestor: - return self._sendError(XEP_0050.ERROR.FORBIDDEN, session_id, command_elt.parent) + return self._sendError( + XEP_0050.ERROR.SESSION_EXPIRED, session_id, command_elt.parent + ) + if session_data["requestor"] != requestor: + return self._sendError( + XEP_0050.ERROR.FORBIDDEN, session_id, command_elt.parent + ) else: session_id, session_data = self.sessions.newSession() - session_data['requestor'] = requestor + session_data["requestor"] = requestor if action == XEP_0050.ACTION.CANCEL: d = defer.succeed((None, XEP_0050.STATUS.CANCELED, None, None)) else: - d = defer.maybeDeferred(self.callback, command_elt, session_data, action, self.node, self.client.profile) + d = defer.maybeDeferred( + self.callback, + command_elt, + session_data, + action, + self.node, + self.client.profile, + ) d.addCallback(self._sendAnswer, session_id, command_elt.parent) - d.addErrback(lambda failure, request: self._sendError(failure.value.callback_error, session_id, request), command_elt.parent) + d.addErrback( + lambda failure, request: self._sendError( + failure.value.callback_error, session_id, request + ), + command_elt.parent, + ) class XEP_0050(object): - STATUS = namedtuple('Status', ('EXECUTING', 'COMPLETED', 'CANCELED'))('executing', 'completed', 'canceled') - ACTION = namedtuple('Action', ('EXECUTE', 'CANCEL', 'NEXT', 'PREV'))('execute', 'cancel', 'next', 'prev') - NOTE = namedtuple('Note', ('INFO','WARN','ERROR'))('info','warn','error') - ERROR = namedtuple('Error', ('MALFORMED_ACTION', 'BAD_ACTION', 'BAD_LOCALE', 'BAD_PAYLOAD', 'BAD_SESSIONID', 'SESSION_EXPIRED', - 'FORBIDDEN', 'ITEM_NOT_FOUND', 'FEATURE_NOT_IMPLEMENTED', 'INTERNAL'))(('bad-request', 'malformed-action'), - ('bad-request', 'bad-action'), ('bad-request', 'bad-locale'), ('bad-request','bad-payload'), - ('bad-request','bad-sessionid'), ('not-allowed','session-expired'), ('forbidden', None), - ('item-not-found', None), ('feature-not-implemented', None), ('internal-server-error', None)) # XEP-0050 §4.4 Table 5 + STATUS = namedtuple("Status", ("EXECUTING", "COMPLETED", "CANCELED"))( + "executing", "completed", "canceled" + ) + ACTION = namedtuple("Action", ("EXECUTE", "CANCEL", "NEXT", "PREV"))( + "execute", "cancel", "next", "prev" + ) + NOTE = namedtuple("Note", ("INFO", "WARN", "ERROR"))("info", "warn", "error") + ERROR = namedtuple( + "Error", + ( + "MALFORMED_ACTION", + "BAD_ACTION", + "BAD_LOCALE", + "BAD_PAYLOAD", + "BAD_SESSIONID", + "SESSION_EXPIRED", + "FORBIDDEN", + "ITEM_NOT_FOUND", + "FEATURE_NOT_IMPLEMENTED", + "INTERNAL", + ), + )( + ("bad-request", "malformed-action"), + ("bad-request", "bad-action"), + ("bad-request", "bad-locale"), + ("bad-request", "bad-payload"), + ("bad-request", "bad-sessionid"), + ("not-allowed", "session-expired"), + ("forbidden", None), + ("item-not-found", None), + ("feature-not-implemented", None), + ("internal-server-error", None), + ) # XEP-0050 §4.4 Table 5 def __init__(self, host): log.info(_("plugin XEP-0050 initialization")) self.host = host self.requesting = Sessions() self.answering = {} - host.bridge.addMethod("adHocRun", ".plugin", in_sign='sss', out_sign='s', - method=self._run, - async=True) - host.bridge.addMethod("adHocList", ".plugin", in_sign='ss', out_sign='s', - method=self._list, - async=True) - self.__requesting_id = host.registerCallback(self._requestingEntity, with_data=True) - host.importMenu((D_("Service"), D_("Commands")), self._commandsMenu, security_limit=2, help_string=D_("Execute ad-hoc commands")) + host.bridge.addMethod( + "adHocRun", + ".plugin", + in_sign="sss", + out_sign="s", + method=self._run, + async=True, + ) + host.bridge.addMethod( + "adHocList", + ".plugin", + in_sign="ss", + out_sign="s", + method=self._list, + async=True, + ) + self.__requesting_id = host.registerCallback( + self._requestingEntity, with_data=True + ) + host.importMenu( + (D_("Service"), D_("Commands")), + self._commandsMenu, + security_limit=2, + help_string=D_("Execute ad-hoc commands"), + ) def getHandler(self, client): return XEP_0050_handler(self) def profileConnected(self, client): - self.addAdHocCommand(self._statusCallback, _("Status"), profile_key=client.profile) + self.addAdHocCommand( + self._statusCallback, _("Status"), profile_key=client.profile + ) def profileDisconnected(self, client): try: @@ -242,7 +336,7 @@ form_ui = xml_tools.XMLUI("form", submit_id=self.__requesting_id) if not no_instructions: - form_ui.addText(_("Please select a command"), 'instructions') + form_ui.addText(_("Please select a command"), "instructions") options = [(item.nodeIdentifier, item.name) for item in items] form_ui.addList("node", options) @@ -254,12 +348,12 @@ @param type_: note type (see XEP-0050 §4.3) @return: a C.XMLUI_DATA_LVL_* constant """ - if type_ == 'error': + if type_ == "error": return C.XMLUI_DATA_LVL_ERROR - elif type_ == 'warn': + elif type_ == "warn": return C.XMLUI_DATA_LVL_WARNING else: - if type_ != 'info': + if type_ != "info": log.warning(_(u"Invalid note type [%s], using info") % type_) return C.XMLUI_DATA_LVL_INFO @@ -269,10 +363,11 @@ @param notes (list): list of tuple (level, message) @return: list of messages """ - lvl_map = {C.XMLUI_DATA_LVL_INFO: '', - C.XMLUI_DATA_LVL_WARNING: "%s: " % _("WARNING"), - C.XMLUI_DATA_LVL_ERROR: "%s: " % _("ERROR") - } + lvl_map = { + C.XMLUI_DATA_LVL_INFO: "", + C.XMLUI_DATA_LVL_WARNING: "%s: " % _("WARNING"), + C.XMLUI_DATA_LVL_ERROR: "%s: " % _("ERROR"), + } return [u"%s%s" % (lvl_map[lvl], msg) for lvl, msg in notes] def _commandsAnswer2XMLUI(self, iq_elt, session_id, session_data): @@ -284,7 +379,7 @@ """ command_elt = iq_elt.elements(NS_COMMANDS, "command").next() - status = command_elt.getAttribute('status', XEP_0050.STATUS.EXECUTING) + status = command_elt.getAttribute("status", XEP_0050.STATUS.EXECUTING) if status in [XEP_0050.STATUS.COMPLETED, XEP_0050.STATUS.CANCELED]: # the command session is finished, we purge our session del self.requesting[session_id] @@ -292,27 +387,35 @@ session_id = None else: return None - remote_session_id = command_elt.getAttribute('sessionid') + remote_session_id = command_elt.getAttribute("sessionid") if remote_session_id: - session_data['remote_id'] = remote_session_id + session_data["remote_id"] = remote_session_id notes = [] - for note_elt in command_elt.elements(NS_COMMANDS, 'note'): - notes.append((self._getDataLvl(note_elt.getAttribute('type', 'info')), - unicode(note_elt))) - for data_elt in command_elt.elements(data_form.NS_X_DATA, 'x'): - if data_elt['type'] in ('form', 'result'): + for note_elt in command_elt.elements(NS_COMMANDS, "note"): + notes.append( + ( + self._getDataLvl(note_elt.getAttribute("type", "info")), + unicode(note_elt), + ) + ) + for data_elt in command_elt.elements(data_form.NS_X_DATA, "x"): + if data_elt["type"] in ("form", "result"): break else: # no matching data element found if status != XEP_0050.STATUS.COMPLETED: - log.warning(_("No known payload found in ad-hoc command result, aborting")) + log.warning( + _("No known payload found in ad-hoc command result, aborting") + ) del self.requesting[session_id] - return xml_tools.XMLUI(C.XMLUI_DIALOG, - dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, - C.XMLUI_DATA_MESS: _("No payload found"), - C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_ERROR, - } - ) + return xml_tools.XMLUI( + C.XMLUI_DIALOG, + dialog_opt={ + C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, + C.XMLUI_DATA_MESS: _("No payload found"), + C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_ERROR, + }, + ) if not notes: # the status is completed, and we have no note to show return None @@ -321,13 +424,14 @@ # if we have more, we show a dialog with "info" level, and all notes merged dlg_level = notes[0][0] if len(notes) == 1 else C.XMLUI_DATA_LVL_INFO return xml_tools.XMLUI( - C.XMLUI_DIALOG, - dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, - C.XMLUI_DATA_MESS: u'\n'.join(self._mergeNotes(notes)), - C.XMLUI_DATA_LVL: dlg_level, - }, - session_id = session_id - ) + C.XMLUI_DIALOG, + dialog_opt={ + C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, + C.XMLUI_DATA_MESS: u"\n".join(self._mergeNotes(notes)), + C.XMLUI_DATA_LVL: dlg_level, + }, + session_id=session_id, + ) if session_id is None: return xml_tools.dataFormEltResult2XMLUI(data_elt) @@ -338,8 +442,8 @@ def _requestingEntity(self, data, profile): def serialise(ret_data): - if 'xmlui' in ret_data: - ret_data['xmlui'] = ret_data['xmlui'].toXml() + if "xmlui" in ret_data: + ret_data["xmlui"] = ret_data["xmlui"].toXml() return ret_data d = self.requestingEntity(data, profile) @@ -354,7 +458,7 @@ @return: callback dict result (with "xmlui" corresponding to the answering dialog, or empty if it's finished without error) """ - if C.bool(data.get('cancelled', C.BOOL_FALSE)): + if C.bool(data.get("cancelled", C.BOOL_FALSE)): return defer.succeed({}) client = self.host.getClient(profile) # TODO: cancel, prev and next are not managed @@ -363,49 +467,53 @@ if "session_id" not in data: # we just had the jid, we now request it for the available commands session_id, session_data = self.requesting.newSession(profile=client.profile) - entity = jid.JID(data[xml_tools.SAT_FORM_PREFIX+'jid']) - session_data['jid'] = entity + entity = jid.JID(data[xml_tools.SAT_FORM_PREFIX + "jid"]) + session_data["jid"] = entity d = self.list(client, entity) def sendItems(xmlui): - xmlui.session_id = session_id # we need to keep track of the session - return {'xmlui': xmlui} + xmlui.session_id = session_id # we need to keep track of the session + return {"xmlui": xmlui} d.addCallback(sendItems) else: # we have started a several forms sessions try: - session_data = self.requesting.profileGet(data["session_id"], client.profile) + session_data = self.requesting.profileGet( + data["session_id"], client.profile + ) except KeyError: - log.warning ("session id doesn't exist, session has probably expired") + log.warning("session id doesn't exist, session has probably expired") # TODO: send error dialog return defer.succeed({}) session_id = data["session_id"] - entity = session_data['jid'] + entity = session_data["jid"] try: - session_data['node'] + session_data["node"] # node has already been received except KeyError: # it's the first time we know the node, we save it in session data - session_data['node'] = data[xml_tools.SAT_FORM_PREFIX+'node'] + session_data["node"] = data[xml_tools.SAT_FORM_PREFIX + "node"] # we request execute node's command - iq_elt = compat.IQ(client.xmlstream, 'set') - iq_elt['to'] = entity.full() + iq_elt = compat.IQ(client.xmlstream, "set") + iq_elt["to"] = entity.full() command_elt = iq_elt.addElement("command", NS_COMMANDS) - command_elt['node'] = session_data['node'] - command_elt['action'] = XEP_0050.ACTION.EXECUTE + command_elt["node"] = session_data["node"] + command_elt["action"] = XEP_0050.ACTION.EXECUTE try: # remote_id is the XEP_0050 sessionid used by answering command # while session_id is our own session id used with the frontend - command_elt['sessionid'] = session_data['remote_id'] + command_elt["sessionid"] = session_data["remote_id"] except KeyError: pass - command_elt.addChild(xml_tools.XMLUIResultToElt(data)) # We add the XMLUI result to the command payload + command_elt.addChild( + xml_tools.XMLUIResultToElt(data) + ) # We add the XMLUI result to the command payload d = iq_elt.send() d.addCallback(self._commandsAnswer2XMLUI, session_id, session_data) - d.addCallback(lambda xmlui: {'xmlui': xmlui} if xmlui is not None else {}) + d.addCallback(lambda xmlui: {"xmlui": xmlui} if xmlui is not None else {}) return d @@ -415,23 +523,27 @@ """ form_ui = xml_tools.XMLUI("form", submit_id=self.__requesting_id) - form_ui.addText(_("Please enter target jid"), 'instructions') + form_ui.addText(_("Please enter target jid"), "instructions") form_ui.changeContainer("pairs") form_ui.addLabel("jid") form_ui.addString("jid", value=self.host.getClient(profile).jid.host) - return {'xmlui': form_ui.toXml()} + return {"xmlui": form_ui.toXml()} def _statusCallback(self, command_elt, session_data, action, node, profile): """ Ad-hoc command used to change the "show" part of status """ - actions = session_data.setdefault('actions',[]) + actions = session_data.setdefault("actions", []) actions.append(action) if len(actions) == 1: # it's our first request, we ask the desired new status status = XEP_0050.STATUS.EXECUTING - form = data_form.Form('form', title=_('status selection')) - show_options = [data_form.Option(name, label) for name, label in SHOWS.items()] - field = data_form.Field('list-single', 'show', options=show_options, required=True) + form = data_form.Form("form", title=_("status selection")) + show_options = [ + data_form.Option(name, label) for name, label in SHOWS.items() + ] + field = data_form.Field( + "list-single", "show", options=show_options, required=True + ) form.addField(field) payload = form.toElement() @@ -440,9 +552,9 @@ elif len(actions) == 2: # we should have the answer here try: - x_elt = command_elt.elements(data_form.NS_X_DATA,'x').next() + x_elt = command_elt.elements(data_form.NS_X_DATA, "x").next() answer_form = data_form.Form.fromElement(x_elt) - show = answer_form['show'] + show = answer_form["show"] except (KeyError, StopIteration): raise AdHocError(XEP_0050.ERROR.BAD_PAYLOAD) if show not in SHOWS: @@ -453,8 +565,8 @@ self.host.setPresence(show=show, profile_key=profile) # job done, we can end the session - form = data_form.Form('form', title=_(u'Updated')) - form.addField(data_form.Field('fixed', u'Status updated')) + form = data_form.Form("form", title=_(u"Updated")) + form.addField(data_form.Field("fixed", u"Status updated")) status = XEP_0050.STATUS.COMPLETED payload = None note = (self.NOTE.INFO, _(u"Status updated")) @@ -463,7 +575,7 @@ return (payload, status, None, note) - def _run(self, service_jid_s='', node='', profile_key=C.PROF_KEY_NONE): + def _run(self, service_jid_s="", node="", profile_key=C.PROF_KEY_NONE): client = self.host.getClient(profile_key) service_jid = jid.JID(service_jid_s) if service_jid_s else None d = self.run(client, service_jid, node or None) @@ -483,13 +595,15 @@ if service_jid is None: service_jid = jid.JID(client.jid.host) session_id, session_data = self.requesting.newSession(profile=client.profile) - session_data['jid'] = service_jid + session_data["jid"] = service_jid if node is None: xmlui = yield self.list(client, service_jid) else: - session_data['node'] = node - cb_data = yield self.requestingEntity({'session_id': session_id}, client.profile) - xmlui = cb_data['xmlui'] + session_data["node"] = node + cb_data = yield self.requestingEntity( + {"session_id": session_id}, client.profile + ) + xmlui = cb_data["xmlui"] xmlui.session_id = session_id defer.returnValue(xmlui) @@ -512,8 +626,20 @@ d.addCallback(self._items2XMLUI, no_instructions) return d - def addAdHocCommand(self, callback, label, node=None, features=None, timeout=600, allowed_jids=None, allowed_groups=None, - allowed_magics=None, forbidden_jids=None, forbidden_groups=None, profile_key=C.PROF_KEY_NONE): + def addAdHocCommand( + self, + callback, + label, + node=None, + features=None, + timeout=600, + allowed_jids=None, + allowed_groups=None, + allowed_magics=None, + forbidden_jids=None, + forbidden_groups=None, + profile_key=C.PROF_KEY_NONE, + ): """Add an ad-hoc command for the current profile @param callback: method associated with this ad-hoc command which return the payload data (see AdHocCommand._sendAnswer), can return a deferred @@ -534,7 +660,7 @@ # FIXME: "@ALL@" for profile_key seems useless and dangerous if node is None: - node = "%s_%s" % ('COMMANDS', uuid4()) + node = "%s_%s" % ("COMMANDS", uuid4()) if features is None: features = [data_form.NS_X_DATA] @@ -544,30 +670,46 @@ if allowed_groups is None: allowed_groups = [] if allowed_magics is None: - allowed_magics = ['@PROFILE_BAREJID@'] + allowed_magics = ["@PROFILE_BAREJID@"] if forbidden_jids is None: forbidden_jids = [] if forbidden_groups is None: forbidden_groups = [] for client in self.host.getClients(profile_key): - #TODO: manage newly created/removed profiles - _allowed_jids = (allowed_jids + [client.jid.userhostJID()]) if '@PROFILE_BAREJID@' in allowed_magics else allowed_jids - ad_hoc_command = AdHocCommand(self, callback, label, node, features, timeout, _allowed_jids, - allowed_groups, allowed_magics, forbidden_jids, forbidden_groups, client) + # TODO: manage newly created/removed profiles + _allowed_jids = ( + (allowed_jids + [client.jid.userhostJID()]) + if "@PROFILE_BAREJID@" in allowed_magics + else allowed_jids + ) + ad_hoc_command = AdHocCommand( + self, + callback, + label, + node, + features, + timeout, + _allowed_jids, + allowed_groups, + allowed_magics, + forbidden_jids, + forbidden_groups, + client, + ) ad_hoc_command.setHandlerParent(client) profile_commands = self.answering.setdefault(client.profile, {}) profile_commands[node] = ad_hoc_command def onCmdRequest(self, request, profile): request.handled = True - requestor = jid.JID(request['from']) - command_elt = request.elements(NS_COMMANDS, 'command').next() - action = command_elt.getAttribute('action', self.ACTION.EXECUTE) - node = command_elt.getAttribute('node') + requestor = jid.JID(request["from"]) + command_elt = request.elements(NS_COMMANDS, "command").next() + action = command_elt.getAttribute("action", self.ACTION.EXECUTE) + node = command_elt.getAttribute("node") if not node: raise exceptions.DataError - sessionid = command_elt.getAttribute('sessionid') + sessionid = command_elt.getAttribute("sessionid") try: command = self.answering[profile][node] except KeyError: @@ -582,18 +724,26 @@ self.plugin_parent = plugin_parent def connectionInitialized(self): - self.xmlstream.addObserver(CMD_REQUEST, self.plugin_parent.onCmdRequest, profile=self.parent.profile) + self.xmlstream.addObserver( + CMD_REQUEST, self.plugin_parent.onCmdRequest, profile=self.parent.profile + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): identities = [] - if nodeIdentifier == NS_COMMANDS and self.plugin_parent.answering.get(self.parent.profile): # we only add the identity if we have registred commands + if nodeIdentifier == NS_COMMANDS and self.plugin_parent.answering.get( + self.parent.profile + ): # we only add the identity if we have registred commands identities.append(ID_CMD_LIST) return [disco.DiscoFeature(NS_COMMANDS)] + identities - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): ret = [] if nodeIdentifier == NS_COMMANDS: - for command in self.plugin_parent.answering.get(self.parent.profile,{}).values(): + for command in self.plugin_parent.answering.get( + self.parent.profile, {} + ).values(): if command.isAuthorised(requestor): - ret.append(disco.DiscoItem(self.parent.jid, command.node, command.getName())) #TODO: manage name language + ret.append( + disco.DiscoItem(self.parent.jid, command.node, command.getName()) + ) # TODO: manage name language return ret
--- a/sat/plugins/plugin_xep_0054.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0054.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.internet import threads, defer from twisted.words.protocols.jabber import jid, error @@ -36,10 +37,13 @@ from sat.core import exceptions from sat.memory import persistent import mimetypes + try: from PIL import Image except: - raise exceptions.MissingModule(u"Missing module pillow, please download/install it from https://python-pillow.github.io") + raise exceptions.MissingModule( + u"Missing module pillow, please download/install it from https://python-pillow.github.io" + ) from cStringIO import StringIO try: @@ -48,17 +52,17 @@ from wokkel.subprotocols import XMPPHandler AVATAR_PATH = "avatars" -AVATAR_DIM = (64, 64) # FIXME: dim are not adapted to modern resolutions ! +AVATAR_DIM = (64, 64) # FIXME: dim are not adapted to modern resolutions ! IQ_GET = '/iq[@type="get"]' -NS_VCARD = 'vcard-temp' +NS_VCARD = "vcard-temp" VCARD_REQUEST = IQ_GET + '/vCard[@xmlns="' + NS_VCARD + '"]' # TODO: manage requests -PRESENCE = '/presence' -NS_VCARD_UPDATE = 'vcard-temp:x:update' +PRESENCE = "/presence" +NS_VCARD_UPDATE = "vcard-temp:x:update" VCARD_UPDATE = PRESENCE + '/x[@xmlns="' + NS_VCARD_UPDATE + '"]' -CACHED_DATA = {'avatar', 'nick'} +CACHED_DATA = {"avatar", "nick"} MAX_AGE = 60 * 60 * 24 * 365 PLUGIN_INFO = { @@ -70,20 +74,34 @@ C.PI_RECOMMENDATIONS: ["XEP-0045"], C.PI_MAIN: "XEP_0054", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of vcard-temp""") + C.PI_DESCRIPTION: _("""Implementation of vcard-temp"""), } class XEP_0054(object): - #TODO: - check that nickname is ok + # TODO: - check that nickname is ok # - refactor the code/better use of Wokkel # - get missing values def __init__(self, host): log.info(_(u"Plugin XEP_0054 initialization")) self.host = host - host.bridge.addMethod(u"avatarGet", u".plugin", in_sign=u'sbbs', out_sign=u's', method=self._getAvatar, async=True) - host.bridge.addMethod(u"avatarSet", u".plugin", in_sign=u'ss', out_sign=u'', method=self._setAvatar, async=True) + host.bridge.addMethod( + u"avatarGet", + u".plugin", + in_sign=u"sbbs", + out_sign=u"s", + method=self._getAvatar, + async=True, + ) + host.bridge.addMethod( + u"avatarSet", + u".plugin", + in_sign=u"ss", + out_sign=u"", + method=self._setAvatar, + async=True, + ) host.trigger.add(u"presence_available", self.presenceAvailableTrigger) host.memory.setSignalOnUpdate(u"avatar") host.memory.setSignalOnUpdate(u"nick") @@ -98,7 +116,7 @@ @return (bool): True if the bare jid of the entity is a room jid """ try: - muc_plg = self.host.plugins['XEP-0045'] + muc_plg = self.host.plugins["XEP-0045"] except KeyError: return False @@ -123,12 +141,12 @@ def presenceAvailableTrigger(self, presence_elt, client): if client.jid.userhost() in client._cache_0054: try: - avatar_hash = client._cache_0054[client.jid.userhost()]['avatar'] + avatar_hash = client._cache_0054[client.jid.userhost()]["avatar"] except KeyError: log.info(u"No avatar in cache for {}".format(client.jid.userhost())) return True - x_elt = domish.Element((NS_VCARD_UPDATE, 'x')) - x_elt.addElement('photo', content=avatar_hash) + x_elt = domish.Element((NS_VCARD_UPDATE, "x")) + x_elt.addElement("photo", content=avatar_hash) presence_elt.addChild(x_elt) return True @@ -139,7 +157,7 @@ self._fillCachedValues(client.profile) def _fillCachedValues(self, profile): - #FIXME: this may need to be reworked + # FIXME: this may need to be reworked # the current naive approach keeps a map between all jids # in persistent cache, then put avatar hashs in memory. # Hashes should be shared between profiles (or not ? what @@ -153,9 +171,15 @@ try: value = data[name] if value is None: - log.error(u"{name} value for {jid_} is None, ignoring".format(name=name, jid_=jid_)) + log.error( + u"{name} value for {jid_} is None, ignoring".format( + name=name, jid_=jid_ + ) + ) continue - self.host.memory.updateEntityData(jid_, name, data[name], silent=True, profile_key=profile) + self.host.memory.updateEntityData( + jid_, name, data[name], silent=True, profile_key=profile + ) except KeyError: pass @@ -184,7 +208,9 @@ else: client._cache_0054.force(jid_s) else: - self.host.memory.updateEntityData(jid_, name, value, profile_key=client.profile) + self.host.memory.updateEntityData( + jid_, name, value, profile_key=client.profile + ) if name in CACHED_DATA: client._cache_0054.setdefault(jid_s, {})[name] = value client._cache_0054.force(jid_s) @@ -206,7 +232,7 @@ """Parse a <PHOTO> photo_elt and save the picture""" # XXX: this method is launched in a separate thread try: - mime_type = unicode(photo_elt.elements(NS_VCARD, 'TYPE').next()) + mime_type = unicode(photo_elt.elements(NS_VCARD, "TYPE").next()) except StopIteration: log.warning(u"no MIME type found, assuming image/png") mime_type = u"image/png" @@ -220,35 +246,41 @@ mime_type = "image/png" else: # TODO: handle other image formats (svg?) - log.warning(u"following avatar image format is not handled: {type} [{jid}]".format( - type=mime_type, jid=entity_jid.full())) + log.warning( + u"following avatar image format is not handled: {type} [{jid}]".format( + type=mime_type, jid=entity_jid.full() + ) + ) raise Failure(exceptions.DataError()) ext = mimetypes.guess_extension(mime_type, strict=False) assert ext is not None - if ext == u'.jpe': - ext = u'.jpg' - log.debug(u'photo of type {type} with extension {ext} found [{jid}]'.format( - type=mime_type, ext=ext, jid=entity_jid.full())) + if ext == u".jpe": + ext = u".jpg" + log.debug( + u"photo of type {type} with extension {ext} found [{jid}]".format( + type=mime_type, ext=ext, jid=entity_jid.full() + ) + ) try: - buf = str(photo_elt.elements(NS_VCARD, 'BINVAL').next()) + buf = str(photo_elt.elements(NS_VCARD, "BINVAL").next()) except StopIteration: log.warning(u"BINVAL element not found") raise Failure(exceptions.NotFound()) if not buf: log.warning(u"empty avatar for {jid}".format(jid=entity_jid.full())) raise Failure(exceptions.NotFound()) - log.debug(_(u'Decoding binary')) + log.debug(_(u"Decoding binary")) decoded = b64decode(buf) del buf image_hash = sha1(decoded).hexdigest() with client.cache.cacheData( - PLUGIN_INFO['import_name'], + PLUGIN_INFO["import_name"], image_hash, mime_type, # we keep in cache for 1 year - MAX_AGE - ) as f: + MAX_AGE, + ) as f: f.write(decoded) return image_hash @@ -259,39 +291,44 @@ vcard_dict = {} for elem in vcard.elements(): - if elem.name == 'FN': - vcard_dict['fullname'] = unicode(elem) - elif elem.name == 'NICKNAME': - vcard_dict['nick'] = unicode(elem) - self.updateCache(client, entity_jid, 'nick', vcard_dict['nick']) - elif elem.name == 'URL': - vcard_dict['website'] = unicode(elem) - elif elem.name == 'EMAIL': - vcard_dict['email'] = unicode(elem) - elif elem.name == 'BDAY': - vcard_dict['birthday'] = unicode(elem) - elif elem.name == 'PHOTO': + if elem.name == "FN": + vcard_dict["fullname"] = unicode(elem) + elif elem.name == "NICKNAME": + vcard_dict["nick"] = unicode(elem) + self.updateCache(client, entity_jid, "nick", vcard_dict["nick"]) + elif elem.name == "URL": + vcard_dict["website"] = unicode(elem) + elif elem.name == "EMAIL": + vcard_dict["email"] = unicode(elem) + elif elem.name == "BDAY": + vcard_dict["birthday"] = unicode(elem) + elif elem.name == "PHOTO": # TODO: handle EXTVAL try: avatar_hash = yield threads.deferToThread( - self.savePhoto, client, elem, entity_jid) + self.savePhoto, client, elem, entity_jid + ) except (exceptions.DataError, exceptions.NotFound) as e: - avatar_hash = '' - vcard_dict['avatar'] = avatar_hash + avatar_hash = "" + vcard_dict["avatar"] = avatar_hash except Exception as e: log.error(u"avatar saving error: {}".format(e)) avatar_hash = None else: - vcard_dict['avatar'] = avatar_hash - self.updateCache(client, entity_jid, 'avatar', avatar_hash) + vcard_dict["avatar"] = avatar_hash + self.updateCache(client, entity_jid, "avatar", avatar_hash) else: - log.debug(u'FIXME: [{}] VCard tag is not managed yet'.format(elem.name)) + log.debug(u"FIXME: [{}] VCard tag is not managed yet".format(elem.name)) # if a data in cache doesn't exist anymore, we need to delete it # so we check CACHED_DATA no gotten (i.e. not in vcard_dict keys) # and we reset them for datum in CACHED_DATA.difference(vcard_dict.keys()): - log.debug(u"reseting vcard datum [{datum}] for {entity}".format(datum=datum, entity=entity_jid.full())) + log.debug( + u"reseting vcard datum [{datum}] for {entity}".format( + datum=datum, entity=entity_jid.full() + ) + ) self.updateCache(client, entity_jid, datum, None) defer.returnValue(vcard_dict) @@ -309,11 +346,15 @@ def _vCardEb(self, failure_, to_jid, client): """Called when something is wrong with registration""" - log.warning(u"Can't get vCard for {jid}: {failure}".format(jid=to_jid.full, failure=failure_)) + log.warning( + u"Can't get vCard for {jid}: {failure}".format( + jid=to_jid.full, failure=failure_ + ) + ) self.updateCache(client, to_jid, "avatar", None) def _getVcardElt(self, iq_elt): - return iq_elt.elements(NS_VCARD, "vCard").next() + return iq_elt.elements(NS_VCARD, "vCard").next() def getCardRaw(self, client, entity_jid): """get raw vCard XML @@ -322,10 +363,10 @@ """ entity_jid = self.getBareOrFull(client, entity_jid) log.debug(u"Asking for {}'s VCard".format(entity_jid.full())) - reg_request = client.IQ('get') + reg_request = client.IQ("get") reg_request["from"] = client.jid.full() reg_request["to"] = entity_jid.full() - reg_request.addElement('vCard', NS_VCARD) + reg_request.addElement("vCard", NS_VCARD) d = reg_request.send(entity_jid.full()) d.addCallback(self._getVcardElt) return d @@ -337,19 +378,24 @@ @result: id to retrieve the profile """ d = self.getCardRaw(client, entity_jid) - d.addCallbacks(self._vCardCb, self._vCardEb, callbackArgs=[entity_jid, client], errbackArgs=[entity_jid, client]) + d.addCallbacks( + self._vCardCb, + self._vCardEb, + callbackArgs=[entity_jid, client], + errbackArgs=[entity_jid, client], + ) return d def _getCardCb(self, dummy, client, entity): try: - return client._cache_0054[entity.full()]['avatar'] + return client._cache_0054[entity.full()]["avatar"] except KeyError: raise Failure(exceptions.NotFound()) def _getAvatar(self, entity, cache_only, hash_only, profile): client = self.host.getClient(profile) d = self.getAvatar(client, jid.JID(entity), cache_only, hash_only) - d.addErrback(lambda dummy: '') + d.addErrback(lambda dummy: "") return d @@ -370,7 +416,7 @@ try: # we first check if we have avatar in cache - avatar_hash = client._cache_0054[entity.full()]['avatar'] + avatar_hash = client._cache_0054[entity.full()]["avatar"] if avatar_hash: # avatar is known and exists full_path = client.cache.getFilePath(avatar_hash) @@ -379,7 +425,7 @@ raise KeyError else: # avatar has already been checked but it is not set - full_path = u'' + full_path = u"" except KeyError: # avatar is not in cache if cache_only: @@ -406,11 +452,11 @@ @param entity(jid.JID): entity to get nick from @return(unicode, None): nick or None if not found """ - nick = self.getCache(client, entity, u'nick') + nick = self.getCache(client, entity, u"nick") if nick is not None: defer.returnValue(nick) yield self.getCard(client, entity) - defer.returnValue(self.getCache(client, entity, u'nick')) + defer.returnValue(self.getCache(client, entity, u"nick")) @defer.inlineCallbacks def setNick(self, client, nick): @@ -422,22 +468,22 @@ try: vcard_elt = yield self.getCardRaw(client, jid_) except error.StanzaError as e: - if e.condition == 'item-not-found': - vcard_elt = domish.Element((NS_VCARD, 'vCard')) + if e.condition == "item-not-found": + vcard_elt = domish.Element((NS_VCARD, "vCard")) else: raise e try: - nickname_elt = next(vcard_elt.elements(NS_VCARD, u'NICKNAME')) + nickname_elt = next(vcard_elt.elements(NS_VCARD, u"NICKNAME")) except StopIteration: pass else: vcard_elt.children.remove(nickname_elt) - nickname_elt = vcard_elt.addElement((NS_VCARD, u'NICKNAME'), content=nick) + nickname_elt = vcard_elt.addElement((NS_VCARD, u"NICKNAME"), content=nick) iq_elt = client.IQ() vcard_elt = iq_elt.addChild(vcard_elt) yield iq_elt.send() - self.updateCache(client, jid_, u'nick', unicode(nick)) + self.updateCache(client, jid_, u"nick", unicode(nick)) def _buildSetAvatar(self, client, vcard_elt, file_path): # XXX: this method is executed in a separate thread @@ -460,18 +506,15 @@ right -= offset img = img.crop((left, upper, right, lower)) img_buf = StringIO() - img.save(img_buf, 'PNG') + img.save(img_buf, "PNG") - photo_elt = vcard_elt.addElement('PHOTO') - photo_elt.addElement('TYPE', content='image/png') - photo_elt.addElement('BINVAL', content=b64encode(img_buf.getvalue())) + photo_elt = vcard_elt.addElement("PHOTO") + photo_elt.addElement("TYPE", content="image/png") + photo_elt.addElement("BINVAL", content=b64encode(img_buf.getvalue())) image_hash = sha1(img_buf.getvalue()).hexdigest() with client.cache.cacheData( - PLUGIN_INFO['import_name'], - image_hash, - "image/png", - MAX_AGE - ) as f: + PLUGIN_INFO["import_name"], image_hash, "image/png", MAX_AGE + ) as f: f.write(img_buf.getvalue()) return image_hash @@ -489,14 +532,14 @@ # we first check if a vcard already exists, to keep data vcard_elt = yield self.getCardRaw(client, client.jid.userhostJID()) except error.StanzaError as e: - if e.condition == 'item-not-found': - vcard_elt = domish.Element((NS_VCARD, 'vCard')) + if e.condition == "item-not-found": + vcard_elt = domish.Element((NS_VCARD, "vCard")) else: raise e else: # the vcard exists, we need to remove PHOTO element as we'll make a new one try: - photo_elt = next(vcard_elt.elements(NS_VCARD, u'PHOTO')) + photo_elt = next(vcard_elt.elements(NS_VCARD, u"PHOTO")) except StopIteration: pass else: @@ -504,12 +547,14 @@ iq_elt = client.IQ() iq_elt.addChild(vcard_elt) - image_hash = yield threads.deferToThread(self._buildSetAvatar, client, vcard_elt, file_path) + image_hash = yield threads.deferToThread( + self._buildSetAvatar, client, vcard_elt, file_path + ) # image is now at the right size/format - self.updateCache(client, client.jid.userhostJID(), 'avatar', image_hash) + self.updateCache(client, client.jid.userhostJID(), "avatar", image_hash) yield iq_elt.send() - client.presence.available() # FIXME: should send the current presence, not always "available" ! + client.presence.available() # FIXME: should send the current presence, not always "available" ! class XEP_0054_handler(XMPPHandler): @@ -522,21 +567,24 @@ def connectionInitialized(self): self.xmlstream.addObserver(VCARD_UPDATE, self.update) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_VCARD)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] def _checkAvatarHash(self, dummy, client, entity, given_hash): """check that hash in cash (i.e. computed hash) is the same as given one""" # XXX: if they differ, the avater will be requested on each connection # TODO: try to avoid re-requesting avatar in this case - computed_hash = self.plugin_parent.getCache(client, entity, 'avatar') + computed_hash = self.plugin_parent.getCache(client, entity, "avatar") if computed_hash != given_hash: - log.warning(u"computed hash differs from given hash for {entity}:\n" + log.warning( + u"computed hash differs from given hash for {entity}:\n" "computed: {computed}\ngiven: {given}".format( - entity=entity, computed=computed_hash, given=given_hash)) + entity=entity, computed=computed_hash, given=given_hash + ) + ) def update(self, presence): """Called on <presence/> stanza with vcard data @@ -545,22 +593,22 @@ @param presend(domish.Element): <presence/> stanza """ client = self.parent - entity_jid = self.plugin_parent.getBareOrFull(client, jid.JID(presence['from'])) - #FIXME: wokkel's data_form should be used here + entity_jid = self.plugin_parent.getBareOrFull(client, jid.JID(presence["from"])) + # FIXME: wokkel's data_form should be used here try: - x_elt = presence.elements(NS_VCARD_UPDATE, 'x').next() + x_elt = presence.elements(NS_VCARD_UPDATE, "x").next() except StopIteration: return try: - photo_elt = x_elt.elements(NS_VCARD_UPDATE, 'photo').next() + photo_elt = x_elt.elements(NS_VCARD_UPDATE, "photo").next() except StopIteration: return hash_ = unicode(photo_elt).strip() if hash_ == C.HASH_SHA1_EMPTY: - hash_ = u'' - old_avatar = self.plugin_parent.getCache(client, entity_jid, 'avatar') + hash_ = u"" + old_avatar = self.plugin_parent.getCache(client, entity_jid, "avatar") if old_avatar == hash_: # no change, we can return... @@ -568,7 +616,11 @@ # ...but we double check that avatar is in cache file_path = client.cache.getFilePath(hash_) if file_path is None: - log.error(u"Avatar for [{}] should be in cache but it is not! We get it".format(entity_jid.full())) + log.error( + u"Avatar for [{}] should be in cache but it is not! We get it".format( + entity_jid.full() + ) + ) self.plugin_parent.getCard(client, entity_jid) else: log.debug(u"avatar for {} already in cache".format(entity_jid.full())) @@ -578,14 +630,20 @@ # the avatar has been removed # XXX: we use empty string instead of None to indicate that we took avatar # but it is empty on purpose - self.plugin_parent.updateCache(client, entity_jid, 'avatar', '') + self.plugin_parent.updateCache(client, entity_jid, "avatar", "") return file_path = client.cache.getFilePath(hash_) if file_path is not None: - log.debug(u"New avatar found for [{}], it's already in cache, we use it".format(entity_jid.full())) - self.plugin_parent.updateCache(client, entity_jid, 'avatar', hash_) + log.debug( + u"New avatar found for [{}], it's already in cache, we use it".format( + entity_jid.full() + ) + ) + self.plugin_parent.updateCache(client, entity_jid, "avatar", hash_) else: - log.debug(u'New avatar found for [{}], requesting vcard'.format(entity_jid.full())) + log.debug( + u"New avatar found for [{}], requesting vcard".format(entity_jid.full()) + ) d = self.plugin_parent.getCard(client, entity_jid) d.addCallback(self._checkAvatarHash, client, entity_jid, hash_)
--- a/sat/plugins/plugin_xep_0055.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0055.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,6 +19,7 @@ from sat.core.i18n import _, D_ from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.protocols.jabber.xmlstream import IQ @@ -30,6 +31,7 @@ from sat.tools import xml_tools from wokkel import disco, iwokkel + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: @@ -37,7 +39,7 @@ from zope.interface import implements -NS_SEARCH = 'jabber:iq:search' +NS_SEARCH = "jabber:iq:search" PLUGIN_INFO = { C.PI_NAME: "Jabber Search", @@ -48,7 +50,7 @@ C.PI_RECOMMENDATIONS: ["XEP-0059"], C.PI_MAIN: "XEP_0055", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Implementation of Jabber Search""") + C.PI_DESCRIPTION: _("""Implementation of Jabber Search"""), } # config file parameters @@ -58,26 +60,48 @@ DEFAULT_SERVICE_LIST = ["salut.libervia.org"] FIELD_SINGLE = "field_single" # single text field for the simple search -FIELD_CURRENT_SERVICE = "current_service_jid" # read-only text field for the advanced search +FIELD_CURRENT_SERVICE = ( + "current_service_jid" +) # read-only text field for the advanced search + class XEP_0055(object): - def __init__(self, host): log.info(_("Jabber search plugin initialization")) self.host = host # default search services (config file + hard-coded lists) - self.services = [jid.JID(entry) for entry in host.memory.getConfig(CONFIG_SECTION, CONFIG_SERVICE_LIST, DEFAULT_SERVICE_LIST)] + self.services = [ + jid.JID(entry) + for entry in host.memory.getConfig( + CONFIG_SECTION, CONFIG_SERVICE_LIST, DEFAULT_SERVICE_LIST + ) + ] - host.bridge.addMethod("searchGetFieldsUI", ".plugin", in_sign='ss', out_sign='s', - method=self._getFieldsUI, - async=True) - host.bridge.addMethod("searchRequest", ".plugin", in_sign='sa{ss}s', out_sign='s', - method=self._searchRequest, - async=True) + host.bridge.addMethod( + "searchGetFieldsUI", + ".plugin", + in_sign="ss", + out_sign="s", + method=self._getFieldsUI, + async=True, + ) + host.bridge.addMethod( + "searchRequest", + ".plugin", + in_sign="sa{ss}s", + out_sign="s", + method=self._searchRequest, + async=True, + ) self.__search_menu_id = host.registerCallback(self._getMainUI, with_data=True) - host.importMenu((D_("Contacts"), D_("Search directory")), self._getMainUI, security_limit=1, help_string=D_("Search user directory")) + host.importMenu( + (D_("Contacts"), D_("Search directory")), + self._getMainUI, + security_limit=1, + help_string=D_("Search user directory"), + ) def _getHostServices(self, profile): """Return the jabber search services associated to the user host. @@ -89,10 +113,8 @@ d = self.host.findFeaturesSet(client, [NS_SEARCH]) return d.addCallback(lambda set_: list(set_)) - ## Main search UI (menu item callback) ## - def _getMainUI(self, raw_data, profile): """Get the XMLUI for selecting a service and searching the directory. @@ -115,11 +137,18 @@ # extend services offered by user's server with the default services services.extend([service for service in self.services if service not in services]) data = xml_tools.XMLUIResult2DataFormResult(raw_data) - main_ui = xml_tools.XMLUI(C.XMLUI_WINDOW, container="tabs", title=_("Search users"), submit_id=self.__search_menu_id) + main_ui = xml_tools.XMLUI( + C.XMLUI_WINDOW, + container="tabs", + title=_("Search users"), + submit_id=self.__search_menu_id, + ) d = self._addSimpleSearchUI(services, main_ui, data, profile) - d.addCallback(lambda dummy: self._addAdvancedSearchUI(services, main_ui, data, profile)) - return d.addCallback(lambda dummy: {'xmlui': main_ui.toXml()}) + d.addCallback( + lambda dummy: self._addAdvancedSearchUI(services, main_ui, data, profile) + ) + return d.addCallback(lambda dummy: {"xmlui": main_ui.toXml()}) def _addSimpleSearchUI(self, services, main_ui, data, profile): """Add to the main UI a tab for the simple search. @@ -133,32 +162,49 @@ @return: a dummy Deferred """ - service_jid = services[0] # TODO: search on all the given services, not only the first one + service_jid = services[ + 0 + ] # TODO: search on all the given services, not only the first one - form = data_form.Form('form', formNamespace=NS_SEARCH) - form.addField(data_form.Field('text-single', FIELD_SINGLE, label=_('Search for'), value=data.get(FIELD_SINGLE, ''))) + form = data_form.Form("form", formNamespace=NS_SEARCH) + form.addField( + data_form.Field( + "text-single", + FIELD_SINGLE, + label=_("Search for"), + value=data.get(FIELD_SINGLE, ""), + ) + ) - sub_cont = main_ui.main_container.addTab("simple_search", label=_("Simple search"), container=xml_tools.VerticalContainer) + sub_cont = main_ui.main_container.addTab( + "simple_search", + label=_("Simple search"), + container=xml_tools.VerticalContainer, + ) main_ui.changeContainer(sub_cont.append(xml_tools.PairsContainer(main_ui))) xml_tools.dataForm2Widgets(main_ui, form) # FIXME: add colspan attribute to divider? (we are in a PairsContainer) - main_ui.addDivider('blank') - main_ui.addDivider('blank') # here we added a blank line before the button - main_ui.addDivider('blank') + main_ui.addDivider("blank") + main_ui.addDivider("blank") # here we added a blank line before the button + main_ui.addDivider("blank") main_ui.addButton(self.__search_menu_id, _("Search"), (FIELD_SINGLE,)) - main_ui.addDivider('blank') - main_ui.addDivider('blank') # a blank line again after the button + main_ui.addDivider("blank") + main_ui.addDivider("blank") # a blank line again after the button - simple_data = {key: value for key, value in data.iteritems() if key in (FIELD_SINGLE,)} + simple_data = { + key: value for key, value in data.iteritems() if key in (FIELD_SINGLE,) + } if simple_data: log.debug("Simple search with %s on %s" % (simple_data, service_jid)) sub_cont.parent.setSelected(True) main_ui.changeContainer(sub_cont.append(xml_tools.VerticalContainer(main_ui))) - main_ui.addDivider('dash') + main_ui.addDivider("dash") d = self.searchRequest(service_jid, simple_data, profile) - d.addCallbacks(lambda elt: self._displaySearchResult(main_ui, elt), - lambda failure: main_ui.addText(failure.getErrorMessage())) + d.addCallbacks( + lambda elt: self._displaySearchResult(main_ui, elt), + lambda failure: main_ui.addText(failure.getErrorMessage()), + ) return d return defer.succeed(None) @@ -175,16 +221,20 @@ @return: a dummy Deferred """ - sub_cont = main_ui.main_container.addTab("advanced_search", label=_("Advanced search"), container=xml_tools.VerticalContainer) - service_selection_fields = ['service_jid', 'service_jid_extra'] + sub_cont = main_ui.main_container.addTab( + "advanced_search", + label=_("Advanced search"), + container=xml_tools.VerticalContainer, + ) + service_selection_fields = ["service_jid", "service_jid_extra"] - if 'service_jid_extra' in data: + if "service_jid_extra" in data: # refresh button has been pushed, select the tab sub_cont.parent.setSelected(True) # get the selected service - service_jid_s = data.get('service_jid_extra', '') + service_jid_s = data.get("service_jid_extra", "") if not service_jid_s: - service_jid_s = data.get('service_jid', unicode(services[0])) + service_jid_s = data.get("service_jid", unicode(services[0])) log.debug("Refreshing search fields for %s" % service_jid_s) else: service_jid_s = data.get(FIELD_CURRENT_SERVICE, unicode(services[0])) @@ -194,27 +244,32 @@ main_ui.changeContainer(sub_cont.append(xml_tools.PairsContainer(main_ui))) main_ui.addLabel(_("Search on")) - main_ui.addList('service_jid', options=services_s, selected=service_jid_s) + main_ui.addList("service_jid", options=services_s, selected=service_jid_s) main_ui.addLabel(_("Other service")) - main_ui.addString(name='service_jid_extra') + main_ui.addString(name="service_jid_extra") # FIXME: add colspan attribute to divider? (we are in a PairsContainer) - main_ui.addDivider('blank') - main_ui.addDivider('blank') # here we added a blank line before the button - main_ui.addDivider('blank') - main_ui.addButton(self.__search_menu_id, _("Refresh fields"), service_selection_fields) - main_ui.addDivider('blank') - main_ui.addDivider('blank') # a blank line again after the button + main_ui.addDivider("blank") + main_ui.addDivider("blank") # here we added a blank line before the button + main_ui.addDivider("blank") + main_ui.addButton( + self.__search_menu_id, _("Refresh fields"), service_selection_fields + ) + main_ui.addDivider("blank") + main_ui.addDivider("blank") # a blank line again after the button main_ui.addLabel(_("Displaying the search form for")) main_ui.addString(name=FIELD_CURRENT_SERVICE, value=service_jid_s, read_only=True) - main_ui.addDivider('dash') - main_ui.addDivider('dash') + main_ui.addDivider("dash") + main_ui.addDivider("dash") main_ui.changeContainer(sub_cont.append(xml_tools.VerticalContainer(main_ui))) service_jid = jid.JID(service_jid_s) d = self.getFieldsUI(service_jid, profile) - d.addCallbacks(self._addAdvancedForm, lambda failure: main_ui.addText(failure.getErrorMessage()), - [service_jid, main_ui, sub_cont, data, profile]) + d.addCallbacks( + self._addAdvancedForm, + lambda failure: main_ui.addText(failure.getErrorMessage()), + [service_jid, main_ui, sub_cont, data, profile], + ) return d def _addAdvancedForm(self, form_elt, service_jid, main_ui, sub_cont, data, profile): @@ -243,26 +298,29 @@ widget.setAttribute("value", adv_data[name]) # FIXME: add colspan attribute to divider? (we are in a PairsContainer) - main_ui.addDivider('blank') - main_ui.addDivider('blank') # here we added a blank line before the button - main_ui.addDivider('blank') - main_ui.addButton(self.__search_menu_id, _("Search"), adv_fields + [FIELD_CURRENT_SERVICE]) - main_ui.addDivider('blank') - main_ui.addDivider('blank') # a blank line again after the button + main_ui.addDivider("blank") + main_ui.addDivider("blank") # here we added a blank line before the button + main_ui.addDivider("blank") + main_ui.addButton( + self.__search_menu_id, _("Search"), adv_fields + [FIELD_CURRENT_SERVICE] + ) + main_ui.addDivider("blank") + main_ui.addDivider("blank") # a blank line again after the button if adv_data: # display the search results log.debug("Advanced search with %s on %s" % (adv_data, service_jid)) sub_cont.parent.setSelected(True) main_ui.changeContainer(sub_cont.append(xml_tools.VerticalContainer(main_ui))) - main_ui.addDivider('dash') + main_ui.addDivider("dash") d = self.searchRequest(service_jid, adv_data, profile) - d.addCallbacks(lambda elt: self._displaySearchResult(main_ui, elt), - lambda failure: main_ui.addText(failure.getErrorMessage())) + d.addCallbacks( + lambda elt: self._displaySearchResult(main_ui, elt), + lambda failure: main_ui.addText(failure.getErrorMessage()), + ) return d return defer.succeed(None) - def _displaySearchResult(self, main_ui, elt): """Display the search results. @@ -277,7 +335,9 @@ header = headers.keys()[i % len(headers)] widget_type, widget_args, widget_kwargs = xmlui_data[i] value = widget_args[0] - values.setdefault(header, []).append(jid.JID(value) if header == "jid" else value) + values.setdefault(header, []).append( + jid.JID(value) if header == "jid" else value + ) main_ui.addJidsList(jids=values["jid"], name=D_(u"Search results")) # TODO: also display the values other than JID else: @@ -285,10 +345,8 @@ else: main_ui.addText(D_("The search gave no result")) - ## Retrieve the search fields ## - def _getFieldsUI(self, to_jid_s, profile_key): """Ask a service to send us the list of the form fields it manages. @@ -308,10 +366,10 @@ @return: a deferred domish.Element """ client = self.host.getClient(profile_key) - fields_request = IQ(client.xmlstream, 'get') + fields_request = IQ(client.xmlstream, "get") fields_request["from"] = client.jid.full() fields_request["to"] = to_jid.full() - fields_request.addElement('query', NS_SEARCH) + fields_request.addElement("query", NS_SEARCH) d = fields_request.send(to_jid.full()) d.addCallbacks(self._getFieldsUICb, self._getFieldsUIEb) return d @@ -323,15 +381,17 @@ @return: domish.Element """ try: - query_elts = answer.elements('jabber:iq:search', 'query').next() + query_elts = answer.elements("jabber:iq:search", "query").next() except StopIteration: log.info(_("No query element found")) raise DataError # FIXME: StanzaError is probably more appropriate, check the RFC try: - form_elt = query_elts.elements(data_form.NS_X_DATA, 'x').next() + form_elt = query_elts.elements(data_form.NS_X_DATA, "x").next() except StopIteration: log.info(_("No data form found")) - raise NotImplementedError("Only search through data form is implemented so far") + raise NotImplementedError( + "Only search through data form is implemented so far" + ) return form_elt def _getFieldsUIEb(self, failure): @@ -343,10 +403,8 @@ log.info(_("Fields request failure: %s") % unicode(failure.getErrorMessage())) raise failure - ## Do the search ## - def _searchRequest(self, to_jid_s, search_data, profile_key): """Actually do a search, according to filled data. @@ -370,15 +428,17 @@ if FIELD_SINGLE in search_data: value = search_data[FIELD_SINGLE] d = self.getFieldsUI(to_jid, profile_key) - d.addCallback(lambda elt: self.searchRequestMulti(to_jid, value, elt, profile_key)) + d.addCallback( + lambda elt: self.searchRequestMulti(to_jid, value, elt, profile_key) + ) return d client = self.host.getClient(profile_key) - search_request = IQ(client.xmlstream, 'set') + search_request = IQ(client.xmlstream, "set") search_request["from"] = client.jid.full() search_request["to"] = to_jid.full() - query_elt = search_request.addElement('query', NS_SEARCH) - x_form = data_form.Form('submit', formNamespace=NS_SEARCH) + query_elt = search_request.addElement("query", NS_SEARCH) + x_form = data_form.Form("submit", formNamespace=NS_SEARCH) x_form.makeFields(search_data) query_elt.addChild(x_form.toElement()) # TODO: XEP-0059 could be used here (with the needed new method attributes) @@ -406,13 +466,17 @@ for success, form_elt in result: if not success: continue - if result_elt is None: # the result element is built over the first answer + if ( + result_elt is None + ): # the result element is built over the first answer result_elt = form_elt continue - for item_elt in form_elt.elements('jabber:x:data', 'item'): + for item_elt in form_elt.elements("jabber:x:data", "item"): result_elt.addChild(item_elt) if result_elt is None: - raise defer.failure.Failure(DataError(_("The search could not be performed"))) + raise defer.failure.Failure( + DataError(_("The search could not be performed")) + ) return result_elt return defer.DeferredList(d_list).addCallback(cb) @@ -424,15 +488,17 @@ @return: domish.Element """ try: - query_elts = answer.elements('jabber:iq:search', 'query').next() + query_elts = answer.elements("jabber:iq:search", "query").next() except StopIteration: log.info(_("No query element found")) raise DataError # FIXME: StanzaError is probably more appropriate, check the RFC try: - form_elt = query_elts.elements(data_form.NS_X_DATA, 'x').next() + form_elt = query_elts.elements(data_form.NS_X_DATA, "x").next() except StopIteration: log.info(_("No data form found")) - raise NotImplementedError("Only search through data form is implemented so far") + raise NotImplementedError( + "Only search through data form is implemented so far" + ) return form_elt def _searchErr(self, failure): @@ -453,9 +519,8 @@ self.host = plugin_parent.host self.profile = profile - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_SEARCH)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] -
--- a/sat/plugins/plugin_xep_0059.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0059.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from wokkel import disco @@ -38,7 +39,7 @@ C.PI_PROTOCOLS: ["XEP-0059"], C.PI_MAIN: "XEP_0059", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Result Set Management""") + C.PI_DESCRIPTION: _("""Implementation of Result Set Management"""), } @@ -55,8 +56,8 @@ class XEP_0059_handler(xmlstream.XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(rsm.NS_RSM)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0060.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0060.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from sat.tools import sat_defer @@ -33,6 +34,7 @@ import urllib import datetime from dateutil import tz + # XXX: sat_tmp.wokkel.pubsub is actually use instead of wokkel version # mam and rsm come from sat_tmp.wokkel too from wokkel import pubsub @@ -49,70 +51,226 @@ C.PI_RECOMMENDATIONS: ["XEP-0313"], C.PI_MAIN: "XEP_0060", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of PubSub Protocol""") + C.PI_DESCRIPTION: _("""Implementation of PubSub Protocol"""), } UNSPECIFIED = "unspecified error" MAM_FILTER = "mam_filter_" -Extra = namedtuple('Extra', ('rsm_request', 'extra')) +Extra = namedtuple("Extra", ("rsm_request", "extra")) # rsm_request is the rsm.RSMRequest build with rsm_ prefixed keys, or None # extra is a potentially empty dict class XEP_0060(object): - OPT_ACCESS_MODEL = 'pubsub#access_model' - OPT_PERSIST_ITEMS = 'pubsub#persist_items' - OPT_MAX_ITEMS = 'pubsub#max_items' - OPT_DELIVER_PAYLOADS = 'pubsub#deliver_payloads' - OPT_SEND_ITEM_SUBSCRIBE = 'pubsub#send_item_subscribe' - OPT_NODE_TYPE = 'pubsub#node_type' - OPT_SUBSCRIPTION_TYPE = 'pubsub#subscription_type' - OPT_SUBSCRIPTION_DEPTH = 'pubsub#subscription_depth' - OPT_ROSTER_GROUPS_ALLOWED = 'pubsub#roster_groups_allowed' - OPT_PUBLISH_MODEL = 'pubsub#publish_model' - ACCESS_OPEN = 'open' - ACCESS_PRESENCE = 'presence' - ACCESS_ROSTER = 'roster' - ACCESS_PUBLISHER_ROSTER = 'publisher-roster' - ACCESS_AUTHORIZE = 'authorize' - ACCESS_WHITELIST = 'whitelist' + OPT_ACCESS_MODEL = "pubsub#access_model" + OPT_PERSIST_ITEMS = "pubsub#persist_items" + OPT_MAX_ITEMS = "pubsub#max_items" + OPT_DELIVER_PAYLOADS = "pubsub#deliver_payloads" + OPT_SEND_ITEM_SUBSCRIBE = "pubsub#send_item_subscribe" + OPT_NODE_TYPE = "pubsub#node_type" + OPT_SUBSCRIPTION_TYPE = "pubsub#subscription_type" + OPT_SUBSCRIPTION_DEPTH = "pubsub#subscription_depth" + OPT_ROSTER_GROUPS_ALLOWED = "pubsub#roster_groups_allowed" + OPT_PUBLISH_MODEL = "pubsub#publish_model" + ACCESS_OPEN = "open" + ACCESS_PRESENCE = "presence" + ACCESS_ROSTER = "roster" + ACCESS_PUBLISHER_ROSTER = "publisher-roster" + ACCESS_AUTHORIZE = "authorize" + ACCESS_WHITELIST = "whitelist" def __init__(self, host): log.info(_(u"PubSub plugin initialization")) self.host = host - self._mam = host.plugins.get('XEP-0313') - self._node_cb = {} # dictionnary of callbacks for node (key: node, value: list of callbacks) + self._mam = host.plugins.get("XEP-0313") + self._node_cb = {} # dictionnary of callbacks for node (key: node, value: list of callbacks) self.rt_sessions = sat_defer.RTDeferredSessions() - host.bridge.addMethod("psNodeCreate", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self._createNode, async=True) - host.bridge.addMethod("psNodeConfigurationGet", ".plugin", in_sign='sss', out_sign='a{ss}', method=self._getNodeConfiguration, async=True) - host.bridge.addMethod("psNodeConfigurationSet", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._setNodeConfiguration, async=True) - host.bridge.addMethod("psNodeAffiliationsGet", ".plugin", in_sign='sss', out_sign='a{ss}', method=self._getNodeAffiliations, async=True) - host.bridge.addMethod("psNodeAffiliationsSet", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._setNodeAffiliations, async=True) - host.bridge.addMethod("psNodeSubscriptionsGet", ".plugin", in_sign='sss', out_sign='a{ss}', method=self._getNodeSubscriptions, async=True) - host.bridge.addMethod("psNodeSubscriptionsSet", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._setNodeSubscriptions, async=True) - host.bridge.addMethod("psNodeDelete", ".plugin", in_sign='sss', out_sign='', method=self._deleteNode, async=True) - host.bridge.addMethod("psNodeWatchAdd", ".plugin", in_sign='sss', out_sign='', method=self._addWatch, async=False) - host.bridge.addMethod("psNodeWatchRemove", ".plugin", in_sign='sss', out_sign='', method=self._removeWatch, async=False) - host.bridge.addMethod("psAffiliationsGet", ".plugin", in_sign='sss', out_sign='a{ss}', method=self._getAffiliations, async=True) - host.bridge.addMethod("psItemsGet", ".plugin", in_sign='ssiassa{ss}s', out_sign='(asa{ss})', method=self._getItems, async=True) - host.bridge.addMethod("psItemSend", ".plugin", in_sign='ssssa{ss}s', out_sign='s', method=self._sendItem, async=True) - host.bridge.addMethod("psRetractItem", ".plugin", in_sign='sssbs', out_sign='', method=self._retractItem, async=True) - host.bridge.addMethod("psRetractItems", ".plugin", in_sign='ssasbs', out_sign='', method=self._retractItems, async=True) - host.bridge.addMethod("psSubscribe", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self._subscribe, async=True) - host.bridge.addMethod("psUnsubscribe", ".plugin", in_sign='sss', out_sign='', method=self._unsubscribe, async=True) - host.bridge.addMethod("psSubscriptionsGet", ".plugin", in_sign='sss', out_sign='aa{ss}', method=self._subscriptions, async=True) - host.bridge.addMethod("psSubscribeToMany", ".plugin", in_sign='a(ss)sa{ss}s', out_sign='s', method=self._subscribeToMany) - host.bridge.addMethod("psGetSubscribeRTResult", ".plugin", in_sign='ss', out_sign='(ua(sss))', method=self._manySubscribeRTResult, async=True) - host.bridge.addMethod("psGetFromMany", ".plugin", in_sign='a(ss)ia{ss}s', out_sign='s', method=self._getFromMany) - host.bridge.addMethod("psGetFromManyRTResult", ".plugin", in_sign='ss', out_sign='(ua(sssasa{ss}))', method=self._getFromManyRTResult, async=True) + host.bridge.addMethod( + "psNodeCreate", + ".plugin", + in_sign="ssa{ss}s", + out_sign="s", + method=self._createNode, + async=True, + ) + host.bridge.addMethod( + "psNodeConfigurationGet", + ".plugin", + in_sign="sss", + out_sign="a{ss}", + method=self._getNodeConfiguration, + async=True, + ) + host.bridge.addMethod( + "psNodeConfigurationSet", + ".plugin", + in_sign="ssa{ss}s", + out_sign="", + method=self._setNodeConfiguration, + async=True, + ) + host.bridge.addMethod( + "psNodeAffiliationsGet", + ".plugin", + in_sign="sss", + out_sign="a{ss}", + method=self._getNodeAffiliations, + async=True, + ) + host.bridge.addMethod( + "psNodeAffiliationsSet", + ".plugin", + in_sign="ssa{ss}s", + out_sign="", + method=self._setNodeAffiliations, + async=True, + ) + host.bridge.addMethod( + "psNodeSubscriptionsGet", + ".plugin", + in_sign="sss", + out_sign="a{ss}", + method=self._getNodeSubscriptions, + async=True, + ) + host.bridge.addMethod( + "psNodeSubscriptionsSet", + ".plugin", + in_sign="ssa{ss}s", + out_sign="", + method=self._setNodeSubscriptions, + async=True, + ) + host.bridge.addMethod( + "psNodeDelete", + ".plugin", + in_sign="sss", + out_sign="", + method=self._deleteNode, + async=True, + ) + host.bridge.addMethod( + "psNodeWatchAdd", + ".plugin", + in_sign="sss", + out_sign="", + method=self._addWatch, + async=False, + ) + host.bridge.addMethod( + "psNodeWatchRemove", + ".plugin", + in_sign="sss", + out_sign="", + method=self._removeWatch, + async=False, + ) + host.bridge.addMethod( + "psAffiliationsGet", + ".plugin", + in_sign="sss", + out_sign="a{ss}", + method=self._getAffiliations, + async=True, + ) + host.bridge.addMethod( + "psItemsGet", + ".plugin", + in_sign="ssiassa{ss}s", + out_sign="(asa{ss})", + method=self._getItems, + async=True, + ) + host.bridge.addMethod( + "psItemSend", + ".plugin", + in_sign="ssssa{ss}s", + out_sign="s", + method=self._sendItem, + async=True, + ) + host.bridge.addMethod( + "psRetractItem", + ".plugin", + in_sign="sssbs", + out_sign="", + method=self._retractItem, + async=True, + ) + host.bridge.addMethod( + "psRetractItems", + ".plugin", + in_sign="ssasbs", + out_sign="", + method=self._retractItems, + async=True, + ) + host.bridge.addMethod( + "psSubscribe", + ".plugin", + in_sign="ssa{ss}s", + out_sign="s", + method=self._subscribe, + async=True, + ) + host.bridge.addMethod( + "psUnsubscribe", + ".plugin", + in_sign="sss", + out_sign="", + method=self._unsubscribe, + async=True, + ) + host.bridge.addMethod( + "psSubscriptionsGet", + ".plugin", + in_sign="sss", + out_sign="aa{ss}", + method=self._subscriptions, + async=True, + ) + host.bridge.addMethod( + "psSubscribeToMany", + ".plugin", + in_sign="a(ss)sa{ss}s", + out_sign="s", + method=self._subscribeToMany, + ) + host.bridge.addMethod( + "psGetSubscribeRTResult", + ".plugin", + in_sign="ss", + out_sign="(ua(sss))", + method=self._manySubscribeRTResult, + async=True, + ) + host.bridge.addMethod( + "psGetFromMany", + ".plugin", + in_sign="a(ss)ia{ss}s", + out_sign="s", + method=self._getFromMany, + ) + host.bridge.addMethod( + "psGetFromManyRTResult", + ".plugin", + in_sign="ss", + out_sign="(ua(sssasa{ss}))", + method=self._getFromManyRTResult, + async=True, + ) - # high level observer method - host.bridge.addSignal("psEvent", ".plugin", signature='ssssa{ss}s') # args: category, service(jid), node, type (C.PS_ITEMS, C.PS_DELETE), data, profile + # high level observer method + host.bridge.addSignal( + "psEvent", ".plugin", signature="ssssa{ss}s" + ) # args: category, service(jid), node, type (C.PS_ITEMS, C.PS_DELETE), data, profile # low level observer method, used if service/node is in watching list (see psNodeWatch* methods) - host.bridge.addSignal("psEventRaw", ".plugin", signature='sssass') # args: service(jid), node, type (C.PS_ITEMS, C.PS_DELETE), list of item_xml, profile + host.bridge.addSignal( + "psEventRaw", ".plugin", signature="sssass" + ) # args: service(jid), node, type (C.PS_ITEMS, C.PS_DELETE), list of item_xml, profile def getHandler(self, client): client.pubsub_client = SatPubSubClient(self.host, self) @@ -122,10 +280,18 @@ def profileConnected(self, client): client.pubsub_watching = set() try: - client.pubsub_service = jid.JID(self.host.memory.getConfig('', 'pubsub_service')) + client.pubsub_service = jid.JID( + self.host.memory.getConfig("", "pubsub_service") + ) except RuntimeError: - log.info(_(u"Can't retrieve pubsub_service from conf, we'll use first one that we find")) - client.pubsub_service = yield self.host.findServiceEntity(client, "pubsub", "service") + log.info( + _( + u"Can't retrieve pubsub_service from conf, we'll use first one that we find" + ) + ) + client.pubsub_service = yield self.host.findServiceEntity( + client, "pubsub", "service" + ) def getFeatures(self, profile): try: @@ -133,7 +299,11 @@ except exceptions.ProfileNotSetError: return {} try: - return {'service': client.pubsub_service.full() if client.pubsub_service is not None else ''} + return { + "service": client.pubsub_service.full() + if client.pubsub_service is not None + else "" + } except AttributeError: if self.host.isConnected(profile): log.debug("Profile is not connected, service is not checked yet") @@ -154,10 +324,10 @@ else: # rsm rsm_args = {} - for arg in ('max', 'after', 'before', 'index'): + for arg in ("max", "after", "before", "index"): try: - argname = "max_" if arg == 'max' else arg - rsm_args[argname] = extra.pop('rsm_{}'.format(arg)) + argname = "max_" if arg == "max" else arg + rsm_args[argname] = extra.pop("rsm_{}".format(arg)) except KeyError: continue @@ -168,16 +338,18 @@ # mam mam_args = {} - for arg in ('start', 'end'): + for arg in ("start", "end"): try: - mam_args[arg] = datetime.datetime.fromtimestamp(int(extra.pop('{}{}'.format(MAM_FILTER, arg))), tz.tzutc()) + mam_args[arg] = datetime.datetime.fromtimestamp( + int(extra.pop("{}{}".format(MAM_FILTER, arg))), tz.tzutc() + ) except (TypeError, ValueError): log.warning(u"Bad value for {} filter".format(arg)) except KeyError: continue try: - mam_args['with_jid'] = jid.JID(extra.pop('{}jid'.format(MAM_FILTER))) + mam_args["with_jid"] = jid.JID(extra.pop("{}jid".format(MAM_FILTER))) except (jid.InvalidFormat): log.warning(u"Bad value for jid filter") except KeyError: @@ -185,13 +357,13 @@ for name, value in extra.iteritems(): if name.startswith(MAM_FILTER): - var = name[len(MAM_FILTER):] - extra_fields = mam_args.setdefault('extra_fields', []) + var = name[len(MAM_FILTER) :] + extra_fields = mam_args.setdefault("extra_fields", []) extra_fields.append(data_form.Field(var=var, value=value)) if mam_args: - assert 'mam' not in extra - extra['mam'] = mam.MAMRequest(mam.buildForm(**mam_args)) + assert "mam" not in extra + extra["mam"] = mam.MAMRequest(mam.buildForm(**mam_args)) return Extra(rsm_request, extra) def addManagedNode(self, node, **kwargs): @@ -210,7 +382,7 @@ for event, cb in kwargs.iteritems(): event_name = event[:-3] assert event_name in C.PS_EVENTS - callbacks.setdefault(event_name,[]).append(cb) + callbacks.setdefault(event_name, []).append(cb) def removeManagedNode(self, node, *args): """Add a handler for a node @@ -231,14 +403,21 @@ except ValueError: pass else: - log.debug(u"removed callback {cb} for event {event} on node {node}".format( - cb=callback, event=event, node=node)) + log.debug( + u"removed callback {cb} for event {event} on node {node}".format( + cb=callback, event=event, node=node + ) + ) if not cb_list: del registred_cb[event] if not registred_cb: del self._node_cb[node] return - log.error(u"Trying to remove inexistant callback {cb} for node {node}".format(cb=callback, node=node)) + log.error( + u"Trying to remove inexistant callback {cb} for node {node}".format( + cb=callback, node=node + ) + ) # def listNodes(self, service, nodeIdentifier='', profile=C.PROF_KEY_NONE): # """Retrieve the name of the nodes that are accessible on the target service. @@ -270,11 +449,21 @@ # d.addCallback(lambda subs: [sub.getAttribute('node') for sub in subs if sub.getAttribute('subscription') == filter_]) # return d - def _sendItem(self, service, nodeIdentifier, payload, item_id=None, extra=None, profile_key=C.PROF_KEY_NONE): + def _sendItem( + self, + service, + nodeIdentifier, + payload, + item_id=None, + extra=None, + profile_key=C.PROF_KEY_NONE, + ): client = self.host.getClient(profile_key) service = None if not service else jid.JID(service) - d = self.sendItem(client, service, nodeIdentifier, payload, item_id or None, extra) - d.addCallback(lambda ret: ret or u'') + d = self.sendItem( + client, service, nodeIdentifier, payload, item_id or None, extra + ) + d.addCallback(lambda ret: ret or u"") return d def _getPublishedItemId(self, iq_elt, original_id): @@ -283,12 +472,14 @@ if not found original_id is returned, or empty string if it is None or empty string """ try: - item_id = iq_elt.pubsub.publish.item['id'] + item_id = iq_elt.pubsub.publish.item["id"] except (AttributeError, KeyError): item_id = None return item_id or original_id - def sendItem(self, client, service, nodeIdentifier, payload, item_id=None, extra=None): + def sendItem( + self, client, service, nodeIdentifier, payload, item_id=None, extra=None + ): """high level method to send one item @param service(jid.JID, None): service to send the item to @@ -305,21 +496,40 @@ return d def publish(self, client, service, nodeIdentifier, items=None): - return client.pubsub_client.publish(service, nodeIdentifier, items, client.pubsub_client.parent.jid) + return client.pubsub_client.publish( + service, nodeIdentifier, items, client.pubsub_client.parent.jid + ) def _unwrapMAMMessage(self, message_elt): try: - item_elt = (message_elt.elements(mam.NS_MAM, 'result').next() - .elements(C.NS_FORWARD, 'forwarded').next() - .elements(C.NS_CLIENT, 'message').next() - .elements('http://jabber.org/protocol/pubsub#event', 'event').next() - .elements('http://jabber.org/protocol/pubsub#event', 'items').next() - .elements('http://jabber.org/protocol/pubsub#event', 'item').next()) + item_elt = ( + message_elt.elements(mam.NS_MAM, "result") + .next() + .elements(C.NS_FORWARD, "forwarded") + .next() + .elements(C.NS_CLIENT, "message") + .next() + .elements("http://jabber.org/protocol/pubsub#event", "event") + .next() + .elements("http://jabber.org/protocol/pubsub#event", "items") + .next() + .elements("http://jabber.org/protocol/pubsub#event", "item") + .next() + ) except StopIteration: raise exceptions.DataError(u"Can't find Item in MAM message element") return item_elt - def _getItems(self, service='', node='', max_items=10, item_ids=None, sub_id=None, extra_dict=None, profile_key=C.PROF_KEY_NONE): + def _getItems( + self, + service="", + node="", + max_items=10, + item_ids=None, + sub_id=None, + extra_dict=None, + profile_key=C.PROF_KEY_NONE, + ): """Get items from pubsub node @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit @@ -328,11 +538,30 @@ service = jid.JID(service) if service else None max_items = None if max_items == C.NO_LIMIT else max_items extra = self.parseExtra(extra_dict) - d = self.getItems(client, service, node or None, max_items or None, item_ids, sub_id or None, extra.rsm_request, extra.extra) + d = self.getItems( + client, + service, + node or None, + max_items or None, + item_ids, + sub_id or None, + extra.rsm_request, + extra.extra, + ) d.addCallback(self.serItemsData) return d - def getItems(self, client, service, node, max_items=None, item_ids=None, sub_id=None, rsm_request=None, extra=None): + def getItems( + self, + client, + service, + node, + max_items=None, + item_ids=None, + sub_id=None, + rsm_request=None, + extra=None, + ): """Retrieve pubsub items from a node. @param service (JID, None): pubsub service. @@ -354,9 +583,11 @@ if extra is None: extra = {} try: - mam_query = extra['mam'] + mam_query = extra["mam"] except KeyError: - d = client.pubsub_client.items(service, node, max_items, item_ids, sub_id, None, rsm_request) + d = client.pubsub_client.items( + service, node, max_items, item_ids, sub_id, None, rsm_request + ) else: # if mam is requested, we have to do a totally different query if self._mam is None: @@ -368,25 +599,35 @@ if mam_query.node is None: mam_query.node = node elif mam_query.node != node: - raise exceptions.DataError(u"MAM query node is incoherent with getItems's node") + raise exceptions.DataError( + u"MAM query node is incoherent with getItems's node" + ) if mam_query.rsm is None: mam_query.rsm = rsm_request else: if mam_query.rsm != rsm_request: - raise exceptions.DataError(u"Conflict between RSM request and MAM's RSM request") + raise exceptions.DataError( + u"Conflict between RSM request and MAM's RSM request" + ) d = self._mam.getArchives(client, mam_query, service, self._unwrapMAMMessage) try: - subscribe = C.bool(extra['subscribe']) + subscribe = C.bool(extra["subscribe"]) except KeyError: subscribe = False def subscribeEb(failure, service, node): failure.trap(error.StanzaError) - log.warning(u"Could not subscribe to node {} on service {}: {}".format(node, unicode(service), unicode(failure.value))) + log.warning( + u"Could not subscribe to node {} on service {}: {}".format( + node, unicode(service), unicode(failure.value) + ) + ) def doSubscribe(items): - self.subscribe(service, node, profile_key=client.profile).addErrback(subscribeEb, service, node) + self.subscribe(service, node, profile_key=client.profile).addErrback( + subscribeEb, service, node + ) return items if subscribe: @@ -395,12 +636,18 @@ def addMetadata(result): items, rsm_response = result service_jid = service if service else client.jid.userhostJID() - metadata = {'service': service_jid, - 'node': node, - 'uri': self.getNodeURI(service_jid, node), - } + metadata = { + "service": service_jid, + "node": node, + "uri": self.getNodeURI(service_jid, node), + } if rsm_request is not None and rsm_response is not None: - metadata.update({'rsm_{}'.format(key): value for key, value in rsm_response.toDict().iteritems()}) + metadata.update( + { + "rsm_{}".format(key): value + for key, value in rsm_response.toDict().iteritems() + } + ) return (items, metadata) d.addCallback(addMetadata) @@ -431,17 +678,38 @@ # d_dict[publisher] = self.getItems(service, node, max_items, None, sub_id, rsm, client.profile) # defer.returnValue(d_dict) - def getOptions(self, service, nodeIdentifier, subscriber, subscriptionIdentifier=None, profile_key=C.PROF_KEY_NONE): + def getOptions( + self, + service, + nodeIdentifier, + subscriber, + subscriptionIdentifier=None, + profile_key=C.PROF_KEY_NONE, + ): client = self.host.getClient(profile_key) - return client.pubsub_client.getOptions(service, nodeIdentifier, subscriber, subscriptionIdentifier) + return client.pubsub_client.getOptions( + service, nodeIdentifier, subscriber, subscriptionIdentifier + ) - def setOptions(self, service, nodeIdentifier, subscriber, options, subscriptionIdentifier=None, profile_key=C.PROF_KEY_NONE): + def setOptions( + self, + service, + nodeIdentifier, + subscriber, + options, + subscriptionIdentifier=None, + profile_key=C.PROF_KEY_NONE, + ): client = self.host.getClient(profile_key) - return client.pubsub_client.setOptions(service, nodeIdentifier, subscriber, options, subscriptionIdentifier) + return client.pubsub_client.setOptions( + service, nodeIdentifier, subscriber, options, subscriptionIdentifier + ) def _createNode(self, service_s, nodeIdentifier, options, profile_key): client = self.host.getClient(profile_key) - return self.createNode(client, jid.JID(service_s) if service_s else None, nodeIdentifier, options) + return self.createNode( + client, jid.JID(service_s) if service_s else None, nodeIdentifier, options + ) def createNode(self, client, service, nodeIdentifier=None, options=None): """Create a new node @@ -461,28 +729,31 @@ try: yield self.createNode(client, service, nodeIdentifier, options) except error.StanzaError as e: - if e.condition == 'conflict': + if e.condition == "conflict": pass else: raise e def _getNodeConfiguration(self, service_s, nodeIdentifier, profile_key): client = self.host.getClient(profile_key) - d = self.getConfiguration(client, jid.JID(service_s) if service_s else None, nodeIdentifier) + d = self.getConfiguration( + client, jid.JID(service_s) if service_s else None, nodeIdentifier + ) + def serialize(form): # FIXME: better more generic dataform serialisation should be available in SàT return {f.var: unicode(f.value) for f in form.fields.values()} + d.addCallback(serialize) return d def getConfiguration(self, client, service, nodeIdentifier): - request = pubsub.PubSubRequest('configureGet') + request = pubsub.PubSubRequest("configureGet") request.recipient = service request.nodeIdentifier = nodeIdentifier def cb(iq): - form = data_form.findForm(iq.pubsub.configure, - pubsub.NS_PUBSUB_NODE_CONFIG) + form = data_form.findForm(iq.pubsub.configure, pubsub.NS_PUBSUB_NODE_CONFIG) form.typeCheck() return form @@ -492,16 +763,19 @@ def _setNodeConfiguration(self, service_s, nodeIdentifier, options, profile_key): client = self.host.getClient(profile_key) - d = self.setConfiguration(client, jid.JID(service_s) if service_s else None, nodeIdentifier, options) + d = self.setConfiguration( + client, jid.JID(service_s) if service_s else None, nodeIdentifier, options + ) return d def setConfiguration(self, client, service, nodeIdentifier, options): - request = pubsub.PubSubRequest('configureSet') + request = pubsub.PubSubRequest("configureSet") request.recipient = service request.nodeIdentifier = nodeIdentifier - form = data_form.Form(formType='submit', - formNamespace=pubsub.NS_PUBSUB_NODE_CONFIG) + form = data_form.Form( + formType="submit", formNamespace=pubsub.NS_PUBSUB_NODE_CONFIG + ) form.makeFields(options) request.options = form @@ -510,7 +784,9 @@ def _getAffiliations(self, service_s, nodeIdentifier, profile_key): client = self.host.getClient(profile_key) - d = self.getAffiliations(client, jid.JID(service_s) if service_s else None, nodeIdentifier or None) + d = self.getAffiliations( + client, jid.JID(service_s) if service_s else None, nodeIdentifier or None + ) return d def getAffiliations(self, client, service, nodeIdentifier=None): @@ -519,19 +795,32 @@ @param nodeIdentifier(unicode, None): node to get affiliation from None to get all nodes affiliations for this service """ - request = pubsub.PubSubRequest('affiliations') + request = pubsub.PubSubRequest("affiliations") request.recipient = service request.nodeIdentifier = nodeIdentifier def cb(iq_elt): try: - affiliations_elt = next(iq_elt.pubsub.elements((pubsub.NS_PUBSUB, 'affiliations'))) + affiliations_elt = next( + iq_elt.pubsub.elements((pubsub.NS_PUBSUB, "affiliations")) + ) except StopIteration: - raise ValueError(_(u"Invalid result: missing <affiliations> element: {}").format(iq_elt.toXml)) + raise ValueError( + _(u"Invalid result: missing <affiliations> element: {}").format( + iq_elt.toXml + ) + ) try: - return {e['node']: e['affiliation'] for e in affiliations_elt.elements((pubsub.NS_PUBSUB, 'affiliation'))} + return { + e["node"]: e["affiliation"] + for e in affiliations_elt.elements((pubsub.NS_PUBSUB, "affiliation")) + } except KeyError: - raise ValueError(_(u"Invalid result: bad <affiliation> element: {}").format(iq_elt.toXml)) + raise ValueError( + _(u"Invalid result: bad <affiliation> element: {}").format( + iq_elt.toXml + ) + ) d = request.send(client.xmlstream) d.addCallback(cb) @@ -539,34 +828,62 @@ def _getNodeAffiliations(self, service_s, nodeIdentifier, profile_key): client = self.host.getClient(profile_key) - d = self.getNodeAffiliations(client, jid.JID(service_s) if service_s else None, nodeIdentifier) - d.addCallback(lambda affiliations: {j.full(): a for j, a in affiliations.iteritems()}) + d = self.getNodeAffiliations( + client, jid.JID(service_s) if service_s else None, nodeIdentifier + ) + d.addCallback( + lambda affiliations: {j.full(): a for j, a in affiliations.iteritems()} + ) return d def getNodeAffiliations(self, client, service, nodeIdentifier): """Retrieve affiliations of a node owned by profile""" - request = pubsub.PubSubRequest('affiliationsGet') + request = pubsub.PubSubRequest("affiliationsGet") request.recipient = service request.nodeIdentifier = nodeIdentifier def cb(iq_elt): try: - affiliations_elt = next(iq_elt.pubsub.elements((pubsub.NS_PUBSUB_OWNER, 'affiliations'))) + affiliations_elt = next( + iq_elt.pubsub.elements((pubsub.NS_PUBSUB_OWNER, "affiliations")) + ) except StopIteration: - raise ValueError(_(u"Invalid result: missing <affiliations> element: {}").format(iq_elt.toXml)) + raise ValueError( + _(u"Invalid result: missing <affiliations> element: {}").format( + iq_elt.toXml + ) + ) try: - return {jid.JID(e['jid']): e['affiliation'] for e in affiliations_elt.elements((pubsub.NS_PUBSUB_OWNER, 'affiliation'))} + return { + jid.JID(e["jid"]): e["affiliation"] + for e in affiliations_elt.elements( + (pubsub.NS_PUBSUB_OWNER, "affiliation") + ) + } except KeyError: - raise ValueError(_(u"Invalid result: bad <affiliation> element: {}").format(iq_elt.toXml)) + raise ValueError( + _(u"Invalid result: bad <affiliation> element: {}").format( + iq_elt.toXml + ) + ) d = request.send(client.xmlstream) d.addCallback(cb) return d - def _setNodeAffiliations(self, service_s, nodeIdentifier, affiliations, profile_key=C.PROF_KEY_NONE): + def _setNodeAffiliations( + self, service_s, nodeIdentifier, affiliations, profile_key=C.PROF_KEY_NONE + ): client = self.host.getClient(profile_key) - affiliations = {jid.JID(jid_): affiliation for jid_, affiliation in affiliations.iteritems()} - d = self.setNodeAffiliations(client, jid.JID(service_s) if service_s else None, nodeIdentifier, affiliations) + affiliations = { + jid.JID(jid_): affiliation for jid_, affiliation in affiliations.iteritems() + } + d = self.setNodeAffiliations( + client, + jid.JID(service_s) if service_s else None, + nodeIdentifier, + affiliations, + ) return d def setNodeAffiliations(self, client, service, nodeIdentifier, affiliations): @@ -575,7 +892,7 @@ @param affiliations(dict[jid.JID, unicode]): affiliations to set check https://xmpp.org/extensions/xep-0060.html#affiliations for a list of possible affiliations """ - request = pubsub.PubSubRequest('affiliationsSet') + request = pubsub.PubSubRequest("affiliationsSet") request.recipient = service request.nodeIdentifier = nodeIdentifier request.affiliations = affiliations @@ -584,7 +901,9 @@ def _deleteNode(self, service_s, nodeIdentifier, profile_key): client = self.host.getClient(profile_key) - return self.deleteNode(client, jid.JID(service_s) if service_s else None, nodeIdentifier) + return self.deleteNode( + client, jid.JID(service_s) if service_s else None, nodeIdentifier + ) def deleteNode(self, client, service, nodeIdentifier): return client.pubsub_client.deleteNode(service, nodeIdentifier) @@ -607,48 +926,86 @@ service = jid.JID(service_s) if service_s else client.jid.userhostJID() client.pubsub_watching.remove((service, node)) - def _retractItem(self, service_s, nodeIdentifier, itemIdentifier, notify, profile_key): - return self._retractItems(service_s, nodeIdentifier, (itemIdentifier,), notify, profile_key) + def _retractItem( + self, service_s, nodeIdentifier, itemIdentifier, notify, profile_key + ): + return self._retractItems( + service_s, nodeIdentifier, (itemIdentifier,), notify, profile_key + ) - def _retractItems(self, service_s, nodeIdentifier, itemIdentifiers, notify, profile_key): - return self.retractItems(jid.JID(service_s) if service_s else None, nodeIdentifier, itemIdentifiers, notify, profile_key) + def _retractItems( + self, service_s, nodeIdentifier, itemIdentifiers, notify, profile_key + ): + return self.retractItems( + jid.JID(service_s) if service_s else None, + nodeIdentifier, + itemIdentifiers, + notify, + profile_key, + ) - def retractItems(self, service, nodeIdentifier, itemIdentifiers, notify=True, profile_key=C.PROF_KEY_NONE): + def retractItems( + self, + service, + nodeIdentifier, + itemIdentifiers, + notify=True, + profile_key=C.PROF_KEY_NONE, + ): client = self.host.getClient(profile_key) - return client.pubsub_client.retractItems(service, nodeIdentifier, itemIdentifiers, notify=True) + return client.pubsub_client.retractItems( + service, nodeIdentifier, itemIdentifiers, notify=True + ) def _subscribe(self, service, nodeIdentifier, options, profile_key=C.PROF_KEY_NONE): client = self.host.getClient(profile_key) service = None if not service else jid.JID(service) d = self.subscribe(client, service, nodeIdentifier, options=options or None) - d.addCallback(lambda subscription: subscription.subscriptionIdentifier or u'') + d.addCallback(lambda subscription: subscription.subscriptionIdentifier or u"") return d def subscribe(self, client, service, nodeIdentifier, sub_jid=None, options=None): # TODO: reimplement a subscribtion cache, checking that we have not subscription before trying to subscribe - return client.pubsub_client.subscribe(service, nodeIdentifier, sub_jid or client.jid.userhostJID(), options=options) + return client.pubsub_client.subscribe( + service, nodeIdentifier, sub_jid or client.jid.userhostJID(), options=options + ) def _unsubscribe(self, service, nodeIdentifier, profile_key=C.PROF_KEY_NONE): client = self.host.getClient(profile_key) service = None if not service else jid.JID(service) return self.unsubscribe(client, service, nodeIdentifier) - def unsubscribe(self, client, service, nodeIdentifier, sub_jid=None, subscriptionIdentifier=None, sender=None): - return client.pubsub_client.unsubscribe(service, nodeIdentifier, sub_jid or client.jid.userhostJID(), subscriptionIdentifier, sender) + def unsubscribe( + self, + client, + service, + nodeIdentifier, + sub_jid=None, + subscriptionIdentifier=None, + sender=None, + ): + return client.pubsub_client.unsubscribe( + service, + nodeIdentifier, + sub_jid or client.jid.userhostJID(), + subscriptionIdentifier, + sender, + ) - def _subscriptions(self, service, nodeIdentifier='', profile_key=C.PROF_KEY_NONE): + def _subscriptions(self, service, nodeIdentifier="", profile_key=C.PROF_KEY_NONE): client = self.host.getClient(profile_key) service = None if not service else jid.JID(service) def gotSubscriptions(subscriptions): # we replace pubsub.Subscription instance by dict that we can serialize for idx, sub in enumerate(subscriptions): - sub_dict = {'node': sub.nodeIdentifier, - 'subscriber': sub.subscriber.full(), - 'state': sub.state - } + sub_dict = { + "node": sub.nodeIdentifier, + "subscriber": sub.subscriber.full(), + "state": sub.state, + } if sub.subscriptionIdentifier is not None: - sub_dict['id'] = sub.subscriptionIdentifier + sub_dict["id"] = sub.subscriptionIdentifier subscriptions[idx] = sub_dict return subscriptions @@ -679,19 +1036,20 @@ # XXX: urllib.urlencode use "&" to separate value, while XMPP URL (cf. RFC 5122) # use ";" as a separator. So if more than one value is used in query_data, # urlencode MUST NOT BE USED. - query_data = [('node', node.encode('utf-8'))] + query_data = [("node", node.encode("utf-8"))] if item is not None: - query_data.append(('item', item.encode('utf-8'))) + query_data.append(("item", item.encode("utf-8"))) return "xmpp:{service}?;{query}".format( - service=service.userhost(), - query=urllib.urlencode(query_data) - ).decode('utf-8') + service=service.userhost(), query=urllib.urlencode(query_data) + ).decode("utf-8") ## methods to manage several stanzas/jids at once ## # generic # - def getRTResults(self, session_id, on_success=None, on_error=None, profile=C.PROF_KEY_NONE): + def getRTResults( + self, session_id, on_success=None, on_error=None, profile=C.PROF_KEY_NONE + ): return self.rt_sessions.getResults(session_id, on_success, on_error, profile) def serItemsData(self, items_data, item_cb=lambda item: item.toXml()): @@ -705,7 +1063,10 @@ @return (tuple): a serialised form ready to go throught bridge """ items, metadata = items_data - return [item_cb(item) for item in items], {key: unicode(value) for key, value in metadata.iteritems()} + return ( + [item_cb(item) for item in items], + {key: unicode(value) for key, value in metadata.iteritems()}, + ) def serItemsDataD(self, items_data, item_cb): """Helper method to serialise result from [getItems], deferred version @@ -719,11 +1080,20 @@ @return (tuple): a deferred which fire a serialised form ready to go throught bridge """ items, metadata = items_data + def eb(failure): - log.warning("Error while serialising/parsing item: {}".format(unicode(failure.value))) + log.warning( + "Error while serialising/parsing item: {}".format(unicode(failure.value)) + ) + d = defer.gatherResults([item_cb(item).addErrback(eb) for item in items]) + def finishSerialisation(serialised_items): - return [item for item in serialised_items if item is not None], {key: unicode(value) for key, value in metadata.iteritems()} + return ( + [item for item in serialised_items if item is not None], + {key: unicode(value) for key, value in metadata.iteritems()}, + ) + d.addCallback(finishSerialisation) return d @@ -739,14 +1109,23 @@ """ if failure_result is None: failure_result = () - return [('', result) if success else (unicode(result.result) or UNSPECIFIED, failure_result) for success, result in results] + return [ + ("", result) + if success + else (unicode(result.result) or UNSPECIFIED, failure_result) + for success, result in results + ] # subscribe # def _getNodeSubscriptions(self, service_s, nodeIdentifier, profile_key): client = self.host.getClient(profile_key) - d = self.getNodeSubscriptions(client, jid.JID(service_s) if service_s else None, nodeIdentifier) - d.addCallback(lambda subscriptions: {j.full(): a for j, a in subscriptions.iteritems()}) + d = self.getNodeSubscriptions( + client, jid.JID(service_s) if service_s else None, nodeIdentifier + ) + d.addCallback( + lambda subscriptions: {j.full(): a for j, a in subscriptions.iteritems()} + ) return d def getNodeSubscriptions(self, client, service, nodeIdentifier): @@ -756,30 +1135,55 @@ """ if not nodeIdentifier: raise exceptions.DataError("node identifier can't be empty") - request = pubsub.PubSubRequest('subscriptionsGet') + request = pubsub.PubSubRequest("subscriptionsGet") request.recipient = service request.nodeIdentifier = nodeIdentifier def cb(iq_elt): try: - subscriptions_elt = next(iq_elt.pubsub.elements((pubsub.NS_PUBSUB, 'subscriptions'))) + subscriptions_elt = next( + iq_elt.pubsub.elements((pubsub.NS_PUBSUB, "subscriptions")) + ) except StopIteration: - raise ValueError(_(u"Invalid result: missing <subscriptions> element: {}").format(iq_elt.toXml)) + raise ValueError( + _(u"Invalid result: missing <subscriptions> element: {}").format( + iq_elt.toXml + ) + ) except AttributeError as e: raise ValueError(_(u"Invalid result: {}").format(e)) try: - return {jid.JID(s['jid']): s['subscription'] for s in subscriptions_elt.elements((pubsub.NS_PUBSUB, 'subscription'))} + return { + jid.JID(s["jid"]): s["subscription"] + for s in subscriptions_elt.elements( + (pubsub.NS_PUBSUB, "subscription") + ) + } except KeyError: - raise ValueError(_(u"Invalid result: bad <subscription> element: {}").format(iq_elt.toXml)) + raise ValueError( + _(u"Invalid result: bad <subscription> element: {}").format( + iq_elt.toXml + ) + ) d = request.send(client.xmlstream) d.addCallback(cb) return d - def _setNodeSubscriptions(self, service_s, nodeIdentifier, subscriptions, profile_key=C.PROF_KEY_NONE): + def _setNodeSubscriptions( + self, service_s, nodeIdentifier, subscriptions, profile_key=C.PROF_KEY_NONE + ): client = self.host.getClient(profile_key) - subscriptions = {jid.JID(jid_): subscription for jid_, subscription in subscriptions.iteritems()} - d = self.setNodeSubscriptions(client, jid.JID(service_s) if service_s else None, nodeIdentifier, subscriptions) + subscriptions = { + jid.JID(jid_): subscription + for jid_, subscription in subscriptions.iteritems() + } + d = self.setNodeSubscriptions( + client, + jid.JID(service_s) if service_s else None, + nodeIdentifier, + subscriptions, + ) return d def setNodeSubscriptions(self, client, service, nodeIdentifier, subscriptions): @@ -788,10 +1192,13 @@ @param subscriptions(dict[jid.JID, unicode]): subscriptions to set check https://xmpp.org/extensions/xep-0060.html#substates for a list of possible subscriptions """ - request = pubsub.PubSubRequest('subscriptionsSet') + request = pubsub.PubSubRequest("subscriptionsSet") request.recipient = service request.nodeIdentifier = nodeIdentifier - request.subscriptions = {pubsub.Subscription(nodeIdentifier, jid_, state) for jid_, state in subscriptions.iteritems()} + request.subscriptions = { + pubsub.Subscription(nodeIdentifier, jid_, state) + for jid_, state in subscriptions.iteritems() + } d = request.send(client.xmlstream) return d @@ -808,16 +1215,37 @@ @param profile_key: %(doc_profile_key)s """ profile = self.host.getClient(profile_key).profile - d = self.rt_sessions.getResults(session_id, on_success=lambda result:'', on_error=lambda failure:unicode(failure.value), profile=profile) + d = self.rt_sessions.getResults( + session_id, + on_success=lambda result: "", + on_error=lambda failure: unicode(failure.value), + profile=profile, + ) # we need to convert jid.JID to unicode with full() to serialise it for the bridge - d.addCallback(lambda ret: (ret[0], [(service.full(), node, '' if success else failure or UNSPECIFIED) - for (service, node), (success, failure) in ret[1].iteritems()])) + d.addCallback( + lambda ret: ( + ret[0], + [ + (service.full(), node, "" if success else failure or UNSPECIFIED) + for (service, node), (success, failure) in ret[1].iteritems() + ], + ) + ) return d - def _subscribeToMany(self, node_data, subscriber=None, options=None, profile_key=C.PROF_KEY_NONE): - return self.subscribeToMany([(jid.JID(service), unicode(node)) for service, node in node_data], jid.JID(subscriber), options, profile_key) + def _subscribeToMany( + self, node_data, subscriber=None, options=None, profile_key=C.PROF_KEY_NONE + ): + return self.subscribeToMany( + [(jid.JID(service), unicode(node)) for service, node in node_data], + jid.JID(subscriber), + options, + profile_key, + ) - def subscribeToMany(self, node_data, subscriber, options=None, profile_key=C.PROF_KEY_NONE): + def subscribeToMany( + self, node_data, subscriber, options=None, profile_key=C.PROF_KEY_NONE + ): """Subscribe to several nodes at once. @param node_data (iterable[tuple]): iterable of tuple (service, node) where: @@ -831,7 +1259,9 @@ client = self.host.getClient(profile_key) deferreds = {} for service, node in node_data: - deferreds[(service, node)] = client.pubsub_client.subscribe(service, node, subscriber, options=options) + deferreds[(service, node)] = client.pubsub_client.subscribe( + service, node, subscriber, options=options + ) return self.rt_sessions.newSession(deferreds, client.profile) # found_nodes = yield self.listNodes(service, profile=client.profile) # subscribed_nodes = yield self.listSubscribedNodes(service, profile=client.profile) @@ -860,24 +1290,49 @@ - metadata(dict): serialised metadata """ profile = self.host.getClient(profile_key).profile - d = self.rt_sessions.getResults(session_id, - on_success=lambda result: ('', self.serItemsData(result)), - on_error=lambda failure: (unicode(failure.value) or UNSPECIFIED, ([],{})), - profile=profile) - d.addCallback(lambda ret: (ret[0], - [(service.full(), node, failure, items, metadata) - for (service, node), (success, (failure, (items, metadata))) in ret[1].iteritems()])) + d = self.rt_sessions.getResults( + session_id, + on_success=lambda result: ("", self.serItemsData(result)), + on_error=lambda failure: (unicode(failure.value) or UNSPECIFIED, ([], {})), + profile=profile, + ) + d.addCallback( + lambda ret: ( + ret[0], + [ + (service.full(), node, failure, items, metadata) + for (service, node), (success, (failure, (items, metadata))) in ret[ + 1 + ].iteritems() + ], + ) + ) return d - def _getFromMany(self, node_data, max_item=10, extra_dict=None, profile_key=C.PROF_KEY_NONE): + def _getFromMany( + self, node_data, max_item=10, extra_dict=None, profile_key=C.PROF_KEY_NONE + ): """ @param max_item(int): maximum number of item to get, C.NO_LIMIT for no limit """ max_item = None if max_item == C.NO_LIMIT else max_item extra = self.parseExtra(extra_dict) - return self.getFromMany([(jid.JID(service), unicode(node)) for service, node in node_data], max_item, extra.rsm_request, extra.extra, profile_key) + return self.getFromMany( + [(jid.JID(service), unicode(node)) for service, node in node_data], + max_item, + extra.rsm_request, + extra.extra, + profile_key, + ) - def getFromMany(self, node_data, max_item=None, rsm_request=None, extra=None, profile_key=C.PROF_KEY_NONE): + def getFromMany( + self, + node_data, + max_item=None, + rsm_request=None, + extra=None, + profile_key=C.PROF_KEY_NONE, + ): """Get items from many nodes at once @param node_data (iterable[tuple]): iterable of tuple (service, node) where: @@ -891,7 +1346,9 @@ client = self.host.getClient(profile_key) deferreds = {} for service, node in node_data: - deferreds[(service, node)] = self.getItems(client, service, node, max_item, rsm_request=rsm_request, extra=extra) + deferreds[(service, node)] = self.getItems( + client, service, node, max_item, rsm_request=rsm_request, extra=extra + ) return self.rt_sessions.newSession(deferreds, client.profile) @@ -930,7 +1387,13 @@ client = self.parent if (event.sender, event.nodeIdentifier) in client.pubsub_watching: raw_items = [i.toXml() for i in event.items] - self.host.bridge.psEventRaw(event.sender.full(), event.nodeIdentifier, C.PS_ITEMS, raw_items, client.profile) + self.host.bridge.psEventRaw( + event.sender.full(), + event.nodeIdentifier, + C.PS_ITEMS, + raw_items, + client.profile, + ) def deleteReceived(self, event): log.debug((u"Publish node deleted")) @@ -938,7 +1401,9 @@ callback(self.parent, event) client = self.parent if (event.sender, event.nodeIdentifier) in client.pubsub_watching: - self.host.bridge.psEventRaw(event.sender.full(), event.nodeIdentifier, C.PS_DELETE, [], client.profile) + self.host.bridge.psEventRaw( + event.sender.full(), event.nodeIdentifier, C.PS_DELETE, [], client.profile + ) def subscriptions(self, service, nodeIdentifier, sender=None): """Return the list of subscriptions to the given service and node. @@ -949,7 +1414,7 @@ @type nodeIdentifier: C{unicode} @return (list[pubsub.Subscription]): list of subscriptions """ - request = pubsub.PubSubRequest('subscriptions') + request = pubsub.PubSubRequest("subscriptions") request.recipient = service request.nodeIdentifier = nodeIdentifier request.sender = sender @@ -957,20 +1422,24 @@ def cb(iq): subs = [] - for subscription_elt in iq.pubsub.subscriptions.elements(pubsub.NS_PUBSUB, 'subscription'): - subscription = pubsub.Subscription(subscription_elt['node'], - jid.JID(subscription_elt['jid']), - subscription_elt['subscription'], - subscriptionIdentifier=subscription_elt.getAttribute('subid')) + for subscription_elt in iq.pubsub.subscriptions.elements( + pubsub.NS_PUBSUB, "subscription" + ): + subscription = pubsub.Subscription( + subscription_elt["node"], + jid.JID(subscription_elt["jid"]), + subscription_elt["subscription"], + subscriptionIdentifier=subscription_elt.getAttribute("subid"), + ) subs.append(subscription) return subs return d.addCallback(cb) - def getDiscoInfo(self, requestor, service, nodeIdentifier=''): + def getDiscoInfo(self, requestor, service, nodeIdentifier=""): disco_info = [] self.host.trigger.point("PubSub Disco Info", disco_info, self.parent.profile) return disco_info - def getDiscoItems(self, requestor, service, nodeIdentifier=''): + def getDiscoItems(self, requestor, service, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0065.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0065.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SAT plugin for managing xep-0065 @@ -56,6 +56,7 @@ from sat.core.i18n import _ from sat.core.log import getLogger + log = getLogger(__name__) from sat.core.constants import Const as C from sat.core import exceptions @@ -92,25 +93,27 @@ C.PI_RECOMMENDATIONS: ["NAT-PORT"], C.PI_MAIN: "XEP_0065", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of SOCKS5 Bytestreams""") + C.PI_DESCRIPTION: _("""Implementation of SOCKS5 Bytestreams"""), } IQ_SET = '/iq[@type="set"]' -NS_BS = 'http://jabber.org/protocol/bytestreams' +NS_BS = "http://jabber.org/protocol/bytestreams" BS_REQUEST = IQ_SET + '/query[@xmlns="' + NS_BS + '"]' -TIMER_KEY = 'timer' -DEFER_KEY = 'finished' # key of the deferred used to track session end -SERVER_STARTING_PORT = 0 # starting number for server port search (0 to ask automatic attribution) +TIMER_KEY = "timer" +DEFER_KEY = "finished" # key of the deferred used to track session end +SERVER_STARTING_PORT = ( + 0 +) # starting number for server port search (0 to ask automatic attribution) # priorities are candidates local priorities, must be a int between 0 and 65535 PRIORITY_BEST_DIRECT = 10000 PRIORITY_DIRECT = 5000 PRIORITY_ASSISTED = 1000 -PRIORITY_PROXY = 0.2 # proxy is the last option for s5b -CANDIDATE_DELAY = 0.2 # see XEP-0260 §4 -CANDIDATE_DELAY_PROXY = 0.2 # additional time for proxy types (see XEP-0260 §4 note 3) +PRIORITY_PROXY = 0.2 # proxy is the last option for s5b +CANDIDATE_DELAY = 0.2 # see XEP-0260 §4 +CANDIDATE_DELAY_PROXY = 0.2 # additional time for proxy types (see XEP-0260 §4 note 3) -TIMEOUT = 300 # maxium time between session creation and stream start +TIMEOUT = 300 # maxium time between session creation and stream start # XXX: by default eveything is automatic # TODO: use these params to force use of specific proxy/port/IP @@ -132,14 +135,15 @@ # </params> # """ -(STATE_INITIAL, -STATE_AUTH, -STATE_REQUEST, -STATE_READY, -STATE_AUTH_USERPASS, -STATE_CLIENT_INITIAL, -STATE_CLIENT_AUTH, -STATE_CLIENT_REQUEST, +( + STATE_INITIAL, + STATE_AUTH, + STATE_REQUEST, + STATE_READY, + STATE_AUTH_USERPASS, + STATE_CLIENT_INITIAL, + STATE_CLIENT_AUTH, + STATE_CLIENT_REQUEST, ) = xrange(8) SOCKS5_VER = 0x05 @@ -167,12 +171,21 @@ REPLY_ADDR_NOT_SUPPORTED = 0x08 -ProxyInfos = namedtuple("ProxyInfos", ['host', 'jid', 'port']) +ProxyInfos = namedtuple("ProxyInfos", ["host", "jid", "port"]) class Candidate(object): - - def __init__(self, host, port, type_, priority, jid_, id_=None, priority_local=False, factory=None): + def __init__( + self, + host, + port, + type_, + priority, + jid_, + id_=None, + priority_local=False, + factory=None, + ): """ @param host(unicode): host IP or domain @param port(int): port @@ -184,8 +197,7 @@ else priority is used as global one (and local priority is set to 0) """ assert isinstance(jid_, jid.JID) - self.host, self.port, self.type, self.jid = ( - host, int(port), type_, jid_) + self.host, self.port, self.type, self.jid = (host, int(port), type_, jid_) self.id = id_ if id_ is not None else unicode(uuid.uuid4()) if priority_local: self._local_priority = int(priority) @@ -204,7 +216,7 @@ try: self.factory.discard() except AttributeError: - pass # no discard for Socks5ServerFactory + pass # no discard for Socks5ServerFactory @property def local_priority(self): @@ -218,22 +230,25 @@ # similar to __unicode__ but we don't show jid and we encode id return "Candidate ({0.priority}): host={0.host} port={0.port} type={0.type}{id}".format( self, - id=u" id={}".format(self.id if self.id is not None else u'').encode('utf-8', 'ignore'), - ) + id=u" id={}".format(self.id if self.id is not None else u"").encode( + "utf-8", "ignore" + ), + ) def __unicode__(self): return u"Candidate ({0.priority}): host={0.host} port={0.port} jid={0.jid} type={0.type}{id}".format( - self, - id=u" id={}".format(self.id if self.id is not None else u''), - ) + self, id=u" id={}".format(self.id if self.id is not None else u"") + ) def __eq__(self, other): # self.id is is not used in __eq__ as the same candidate can have # different ids if proposed by initiator or responder try: - return (self.host == other.host and - self.port == other.port and - self.jid == other.jid) + return ( + self.host == other.host + and self.port == other.port + and self.jid == other.jid + ) except (AttributeError, TypeError): return False @@ -256,7 +271,7 @@ multiplier = 10 else: raise exceptions.InternalError(u"Unknown {} type !".format(self.type)) - return 2**16 * multiplier + self._local_priority + return 2 ** 16 * multiplier + self._local_priority def activate(self, sid, peer_jid, client): """Activate the proxy candidate @@ -269,15 +284,15 @@ """ assert self.type == XEP_0065.TYPE_PROXY iq_elt = client.IQ() - iq_elt['to'] = self.jid.full() - query_elt = iq_elt.addElement((NS_BS, 'query')) - query_elt['sid'] = sid - query_elt.addElement('activate', content=peer_jid.full()) + iq_elt["to"] = self.jid.full() + query_elt = iq_elt.addElement((NS_BS, "query")) + query_elt["sid"] = sid + query_elt.addElement("activate", content=peer_jid.full()) return iq_elt.send() def startTransfer(self, session_hash=None): if self.type == XEP_0065.TYPE_PROXY: - chunk_size = 4096 # Prosody's proxy reject bigger chunks by default + chunk_size = 4096 # Prosody's proxy reject bigger chunks by default else: chunk_size = None self.factory.startTransfer(session_hash, chunk_size=chunk_size) @@ -291,18 +306,20 @@ @param sid(unicode): session id @return (str): hash """ - return hashlib.sha1((sid + requester_jid.full() + target_jid.full()).encode('utf-8')).hexdigest() + return hashlib.sha1( + (sid + requester_jid.full() + target_jid.full()).encode("utf-8") + ).hexdigest() class SOCKSv5(protocol.Protocol): - CHUNK_SIZE = 2**16 + CHUNK_SIZE = 2 ** 16 def __init__(self, session_hash=None): """ @param session_hash(str): hash of the session must only be used in client mode """ - self.connection = defer.Deferred() # called when connection/auth is done + self.connection = defer.Deferred() # called when connection/auth is done if session_hash is not None: self.server_mode = False self._session_hash = session_hash @@ -318,13 +335,13 @@ self.addressType = 0 self.requestType = 0 self._stream_object = None - self.active = False # set to True when protocol is actually used for transfer - # used by factories to know when the finished Deferred can be triggered + self.active = False # set to True when protocol is actually used for transfer + # used by factories to know when the finished Deferred can be triggered @property def stream_object(self): if self._stream_object is None: - self._stream_object = self.getSession()['stream_object'] + self._stream_object = self.getSession()["stream_object"] if self.server_mode: self._stream_object.registerProducer(self.transport, True) return self._stream_object @@ -342,22 +359,22 @@ def _startNegotiation(self): log.debug("starting negotiation (client mode)") self.state = STATE_CLIENT_AUTH - self.transport.write(struct.pack('!3B', SOCKS5_VER, 1, AUTHMECH_ANON)) + self.transport.write(struct.pack("!3B", SOCKS5_VER, 1, AUTHMECH_ANON)) def _parseNegotiation(self): try: # Parse out data - ver, nmethod = struct.unpack('!BB', self.buf[:2]) - methods = struct.unpack('%dB' % nmethod, self.buf[2:nmethod + 2]) + ver, nmethod = struct.unpack("!BB", self.buf[:2]) + methods = struct.unpack("%dB" % nmethod, self.buf[2 : nmethod + 2]) # Ensure version is correct if ver != 5: - self.transport.write(struct.pack('!BB', SOCKS5_VER, AUTHMECH_INVALID)) + self.transport.write(struct.pack("!BB", SOCKS5_VER, AUTHMECH_INVALID)) self.transport.loseConnection() return # Trim off front of the buffer - self.buf = self.buf[nmethod + 2:] + self.buf = self.buf[nmethod + 2 :] # Check for supported auth mechs for m in self.supportedAuthMechs: @@ -368,12 +385,12 @@ elif m == AUTHMECH_USERPASS: self.state = STATE_AUTH_USERPASS # Complete negotiation w/ this method - self.transport.write(struct.pack('!BB', SOCKS5_VER, m)) + self.transport.write(struct.pack("!BB", SOCKS5_VER, m)) return # No supported mechs found, notify client and close the connection log.warning(u"Unsupported authentication mechanism") - self.transport.write(struct.pack('!BB', SOCKS5_VER, AUTHMECH_INVALID)) + self.transport.write(struct.pack("!BB", SOCKS5_VER, AUTHMECH_INVALID)) self.transport.loseConnection() except struct.error: pass @@ -381,34 +398,34 @@ def _parseUserPass(self): try: # Parse out data - ver, ulen = struct.unpack('BB', self.buf[:2]) - uname, = struct.unpack('%ds' % ulen, self.buf[2:ulen + 2]) - plen, = struct.unpack('B', self.buf[ulen + 2]) - password, = struct.unpack('%ds' % plen, self.buf[ulen + 3:ulen + 3 + plen]) + ver, ulen = struct.unpack("BB", self.buf[:2]) + uname, = struct.unpack("%ds" % ulen, self.buf[2 : ulen + 2]) + plen, = struct.unpack("B", self.buf[ulen + 2]) + password, = struct.unpack("%ds" % plen, self.buf[ulen + 3 : ulen + 3 + plen]) # Trim off fron of the buffer - self.buf = self.buf[3 + ulen + plen:] + self.buf = self.buf[3 + ulen + plen :] # Fire event to authenticate user if self.authenticateUserPass(uname, password): # Signal success self.state = STATE_REQUEST - self.transport.write(struct.pack('!BB', SOCKS5_VER, 0x00)) + self.transport.write(struct.pack("!BB", SOCKS5_VER, 0x00)) else: # Signal failure - self.transport.write(struct.pack('!BB', SOCKS5_VER, 0x01)) + self.transport.write(struct.pack("!BB", SOCKS5_VER, 0x01)) self.transport.loseConnection() except struct.error: pass def sendErrorReply(self, errorcode): # Any other address types are not supported - result = struct.pack('!BBBBIH', SOCKS5_VER, errorcode, 0, 1, 0, 0) + result = struct.pack("!BBBBIH", SOCKS5_VER, errorcode, 0, 1, 0, 0) self.transport.write(result) self.transport.loseConnection() def _parseRequest(self): try: # Parse out data and trim buffer accordingly - ver, cmd, rsvd, self.addressType = struct.unpack('!BBBB', self.buf[:4]) + ver, cmd, rsvd, self.addressType = struct.unpack("!BBBB", self.buf[:4]) # Ensure we actually support the requested address type if self.addressType not in self.supportedAddrs: @@ -417,12 +434,12 @@ # Deal with addresses if self.addressType == ADDR_IPV4: - addr, port = struct.unpack('!IH', self.buf[4:10]) + addr, port = struct.unpack("!IH", self.buf[4:10]) self.buf = self.buf[10:] elif self.addressType == ADDR_DOMAINNAME: nlen = ord(self.buf[4]) - addr, port = struct.unpack('!%dsH' % nlen, self.buf[5:]) - self.buf = self.buf[7 + len(addr):] + addr, port = struct.unpack("!%dsH" % nlen, self.buf[5:]) + self.buf = self.buf[7 + len(addr) :] else: # Any other address types are not supported self.sendErrorReply(REPLY_ADDR_NOT_SUPPORTED) @@ -449,13 +466,22 @@ def _makeRequest(self): hash_ = self._session_hash - request = struct.pack('!5B%dsH' % len(hash_), SOCKS5_VER, CMD_CONNECT, 0, ADDR_DOMAINNAME, len(hash_), hash_, 0) + request = struct.pack( + "!5B%dsH" % len(hash_), + SOCKS5_VER, + CMD_CONNECT, + 0, + ADDR_DOMAINNAME, + len(hash_), + hash_, + 0, + ) self.transport.write(request) self.state = STATE_CLIENT_REQUEST def _parseRequestReply(self): try: - ver, rep, rsvd, self.addressType = struct.unpack('!BBBB', self.buf[:4]) + ver, rep, rsvd, self.addressType = struct.unpack("!BBBB", self.buf[:4]) # Ensure we actually support the requested address type if self.addressType not in self.supportedAddrs: self.sendErrorReply(REPLY_ADDR_NOT_SUPPORTED) @@ -463,12 +489,12 @@ # Deal with addresses if self.addressType == ADDR_IPV4: - addr, port = struct.unpack('!IH', self.buf[4:10]) + addr, port = struct.unpack("!IH", self.buf[4:10]) self.buf = self.buf[10:] elif self.addressType == ADDR_DOMAINNAME: nlen = ord(self.buf[4]) - addr, port = struct.unpack('!%dsH' % nlen, self.buf[5:]) - self.buf = self.buf[7 + len(addr):] + addr, port = struct.unpack("!%dsH" % nlen, self.buf[5:]) + self.buf = self.buf[7 + len(addr) :] else: # Any other address types are not supported self.sendErrorReply(REPLY_ADDR_NOT_SUPPORTED) @@ -487,7 +513,11 @@ return None def connectionMade(self): - log.debug(u"Socks5 connectionMade (mode = {})".format("server" if self.state == STATE_INITIAL else "client")) + log.debug( + u"Socks5 connectionMade (mode = {})".format( + "server" if self.state == STATE_INITIAL else "client" + ) + ) if self.state == STATE_CLIENT_INITIAL: self._startNegotiation() @@ -495,8 +525,11 @@ # Check that this session is expected if not self.factory.addToSession(addr, self): self.sendErrorReply(REPLY_CONN_REFUSED) - log.warning(u"Unexpected connection request received from {host}" - .format(host=self.transport.getPeer().host)) + log.warning( + u"Unexpected connection request received from {host}".format( + host=self.transport.getPeer().host + ) + ) return self._session_hash = addr self.connectCompleted(addr, 0) @@ -519,10 +552,20 @@ def connectCompleted(self, remotehost, remoteport): if self.addressType == ADDR_IPV4: - result = struct.pack('!BBBBIH', SOCKS5_VER, REPLY_SUCCESS, 0, 1, remotehost, remoteport) + result = struct.pack( + "!BBBBIH", SOCKS5_VER, REPLY_SUCCESS, 0, 1, remotehost, remoteport + ) elif self.addressType == ADDR_DOMAINNAME: - result = struct.pack('!BBBBB%dsH' % len(remotehost), SOCKS5_VER, REPLY_SUCCESS, 0, - ADDR_DOMAINNAME, len(remotehost), remotehost, remoteport) + result = struct.pack( + "!BBBBB%dsH" % len(remotehost), + SOCKS5_VER, + REPLY_SUCCESS, + 0, + ADDR_DOMAINNAME, + len(remotehost), + remotehost, + remoteport, + ) self.transport.write(result) self.state = STATE_READY @@ -553,7 +596,7 @@ if self.state == STATE_CLIENT_REQUEST: self._parseRequestReply() if self.state == STATE_CLIENT_AUTH: - ver, method = struct.unpack('!BB', buf) + ver, method = struct.unpack("!BB", buf) self.buf = self.buf[2:] if ver != SOCKS5_VER or method != AUTHMECH_ANON: self.transport.loseConnection() @@ -564,7 +607,7 @@ log.debug(u"Socks5 connection lost: {}".format(reason.value)) if self.state != STATE_READY: self.connection.errback(reason) - if self.server_mode : + if self.server_mode: self.factory.removeFromSession(self._session_hash, self, reason) @@ -583,7 +626,7 @@ def startTransfer(self, session_hash, chunk_size=None): session = self.getSession(session_hash) try: - protocol = session['protocols'][0] + protocol = session["protocols"][0] except (KeyError, IndexError): log.error(u"Can't start file transfer, can't find protocol") else: @@ -603,7 +646,7 @@ except KeyError: return False else: - session_data.setdefault('protocols', []).append(protocol) + session_data.setdefault("protocols", []).append(protocol) return True def removeFromSession(self, session_hash, protocol, reason): @@ -616,7 +659,7 @@ @param reason(failure.Failure): reason of the removal """ try: - protocols = self.getSession(session_hash)['protocols'] + protocols = self.getSession(session_hash)["protocols"] protocols.remove(protocol) except (KeyError, ValueError): log.error(u"Protocol not found in session while it should be there") @@ -686,10 +729,10 @@ class XEP_0065(object): NAMESPACE = NS_BS - TYPE_DIRECT = 'direct' - TYPE_ASSISTED = 'assisted' - TYPE_TUNEL = 'tunel' - TYPE_PROXY = 'proxy' + TYPE_DIRECT = "direct" + TYPE_ASSISTED = "assisted" + TYPE_TUNEL = "tunel" + TYPE_PROXY = "proxy" Candidate = Candidate def __init__(self, host): @@ -698,16 +741,16 @@ # session data self.hash_clients_map = {} # key: hash of the transfer session, value: session data - self._cache_proxies = {} # key: server jid, value: proxy data + self._cache_proxies = {} # key: server jid, value: proxy data # misc data self._server_factory = None self._external_port = None # plugins shortcuts - self._ip = self.host.plugins['IP'] + self._ip = self.host.plugins["IP"] try: - self._np = self.host.plugins['NAT-PORT'] + self._np = self.host.plugins["NAT-PORT"] except KeyError: log.debug(u"NAT Port plugin not available") self._np = None @@ -739,16 +782,22 @@ try: listening_port = reactor.listenTCP(port, self._server_factory) except internet_error.CannotListenError as e: - log.debug(u"Cannot listen on port {port}: {err_msg}{err_num}".format( - port=port, - err_msg=e.socketError.strerror, - err_num=u' (error code: {})'.format(e.socketError.errno), - )) + log.debug( + u"Cannot listen on port {port}: {err_msg}{err_num}".format( + port=port, + err_msg=e.socketError.strerror, + err_num=u" (error code: {})".format(e.socketError.errno), + ) + ) else: self._server_factory_port = listening_port.getHost().port break - log.info(_("Socks5 Stream server launched on port {}").format(self._server_factory_port)) + log.info( + _("Socks5 Stream server launched on port {}").format( + self._server_factory_port + ) + ) return self._server_factory @defer.inlineCallbacks @@ -759,36 +808,43 @@ @return ((D)(ProxyInfos, None)): Found proxy infos, or None if not acceptable proxy is found """ + def notFound(server): log.info(u"No proxy found on this server") self._cache_proxies[server] = None defer.returnValue(None) + server = client.jid.host try: defer.returnValue(self._cache_proxies[server]) except KeyError: pass try: - proxy = (yield self.host.findServiceEntities(client, 'proxy', 'bytestreams')).pop() + proxy = ( + yield self.host.findServiceEntities(client, "proxy", "bytestreams") + ).pop() except (defer.CancelledError, StopIteration, KeyError): notFound(server) - iq_elt = client.IQ('get') - iq_elt['to'] = proxy.full() - iq_elt.addElement((NS_BS, 'query')) + iq_elt = client.IQ("get") + iq_elt["to"] = proxy.full() + iq_elt.addElement((NS_BS, "query")) try: result_elt = yield iq_elt.send() except jabber_error.StanzaError as failure: - log.warning(u"Error while requesting proxy info on {jid}: {error}" - .format(proxy.full(), failure)) + log.warning( + u"Error while requesting proxy info on {jid}: {error}".format( + proxy.full(), failure + ) + ) notFound(server) try: - query_elt = result_elt.elements(NS_BS, 'query').next() - streamhost_elt = query_elt.elements(NS_BS, 'streamhost').next() - host = streamhost_elt['host'] - jid_ = streamhost_elt['jid'] - port = streamhost_elt['port'] + query_elt = result_elt.elements(NS_BS, "query").next() + streamhost_elt = query_elt.elements(NS_BS, "streamhost").next() + host = streamhost_elt["host"] + jid_ = streamhost_elt["jid"] + port = streamhost_elt["port"] if not all((host, jid, port)): raise KeyError jid_ = jid.JID(jid_) @@ -818,7 +874,9 @@ if self._np is None: log.warning(u"NAT port plugin not available, we can't map port") else: - ext_port = yield self._np.mapPort(local_port, desc=u"SaT socks5 stream") + ext_port = yield self._np.mapPort( + local_port, desc=u"SaT socks5 stream" + ) if ext_port is None: log.warning(u"Can't map NAT port") else: @@ -843,17 +901,56 @@ # the preferred direct connection ip = local_ips.pop(0) - candidates.append(Candidate(ip, local_port, XEP_0065.TYPE_DIRECT, PRIORITY_BEST_DIRECT, client.jid, priority_local=True, factory=server_factory)) + candidates.append( + Candidate( + ip, + local_port, + XEP_0065.TYPE_DIRECT, + PRIORITY_BEST_DIRECT, + client.jid, + priority_local=True, + factory=server_factory, + ) + ) for ip in local_ips: - candidates.append(Candidate(ip, local_port, XEP_0065.TYPE_DIRECT, PRIORITY_DIRECT, client.jid, priority_local=True, factory=server_factory)) + candidates.append( + Candidate( + ip, + local_port, + XEP_0065.TYPE_DIRECT, + PRIORITY_DIRECT, + client.jid, + priority_local=True, + factory=server_factory, + ) + ) # then the assisted one if ext_port is not None: - candidates.append(Candidate(external_ip, ext_port, XEP_0065.TYPE_ASSISTED, PRIORITY_ASSISTED, client.jid, priority_local=True, factory=server_factory)) + candidates.append( + Candidate( + external_ip, + ext_port, + XEP_0065.TYPE_ASSISTED, + PRIORITY_ASSISTED, + client.jid, + priority_local=True, + factory=server_factory, + ) + ) # finally the proxy if proxy: - candidates.append(Candidate(proxy.host, proxy.port, XEP_0065.TYPE_PROXY, PRIORITY_PROXY, proxy.jid, priority_local=True)) + candidates.append( + Candidate( + proxy.host, + proxy.port, + XEP_0065.TYPE_PROXY, + PRIORITY_PROXY, + proxy.jid, + priority_local=True, + ) + ) # should be already sorted, but just in case the priorities get weird candidates.sort(key=lambda c: c.priority, reverse=True) @@ -870,7 +967,9 @@ candidate.factory.connector = connector return candidate.factory.connection - def connectCandidate(self, client, candidate, session_hash, peer_session_hash=None, delay=None): + def connectCandidate( + self, client, candidate, session_hash, peer_session_hash=None, delay=None + ): """Connect to a candidate Connection will be done with a Socks5ClientFactory @@ -900,16 +999,30 @@ d.addCallback(self._addConnector, candidate) return d - def tryCandidates(self, client, candidates, session_hash, peer_session_hash, connection_cb=None, connection_eb=None): + def tryCandidates( + self, + client, + candidates, + session_hash, + peer_session_hash, + connection_cb=None, + connection_eb=None, + ): defers_list = [] for candidate in candidates: delay = CANDIDATE_DELAY * len(defers_list) if candidate.type == XEP_0065.TYPE_PROXY: delay += CANDIDATE_DELAY_PROXY - d = self.connectCandidate(client, candidate, session_hash, peer_session_hash, delay) + d = self.connectCandidate( + client, candidate, session_hash, peer_session_hash, delay + ) if connection_cb is not None: - d.addCallback(lambda dummy, candidate=candidate, client=client: connection_cb(client, candidate)) + d.addCallback( + lambda dummy, candidate=candidate, client=client: connection_cb( + client, candidate + ) + ) if connection_eb is not None: d.addErrback(connection_eb, client, candidate) defers_list.append(d) @@ -942,9 +1055,11 @@ if failure.check(defer.CancelledError): log.debug(u"Connection of {} has been cancelled".format(candidate)) else: - log.info(u"Connection of {candidate} Failed: {error}".format( - candidate = candidate, - error = failure.value)) + log.info( + u"Connection of {candidate} Failed: {error}".format( + candidate=candidate, error=failure.value + ) + ) candidates[candidates.index(candidate)] = None def allTested(self): @@ -952,7 +1067,14 @@ good_candidates = [c for c in candidates if c] return good_candidates[0] if good_candidates else None - defer_candidates = self.tryCandidates(client, candidates, session_hash, peer_session_hash, connectionCb, connectionEb) + defer_candidates = self.tryCandidates( + client, + candidates, + session_hash, + peer_session_hash, + connectionCb, + connectionEb, + ) d_list = defer.DeferredList(defer_candidates) d_list.addCallback(allTested) return d_list @@ -977,11 +1099,13 @@ @param failure_(None, failure.Failure): None if eveything was fine, a failure else @return (None, failure.Failure): failure_ is returned """ - log.debug(u'Cleaning session with hash {hash}{id}: {reason}'.format( - hash=session_hash, - reason='' if failure_ is None else failure_.value, - id='' if sid is None else u' (id: {})'.format(sid), - )) + log.debug( + u"Cleaning session with hash {hash}{id}: {reason}".format( + hash=session_hash, + reason="" if failure_ is None else failure_.value, + id="" if sid is None else u" (id: {})".format(sid), + ) + ) try: assert self.hash_clients_map[session_hash] == client @@ -1004,7 +1128,7 @@ del client._s5b_sessions[session_hash] try: - session_data['timer'].cancel() + session_data["timer"].cancel() except (internet_error.AlreadyCalled, internet_error.AlreadyCancelled): pass @@ -1025,19 +1149,19 @@ session_data[client] = client def gotCandidates(candidates): - session_data['candidates'] = candidates + session_data["candidates"] = candidates iq_elt = client.IQ() iq_elt["from"] = client.jid.full() iq_elt["to"] = to_jid.full() - query_elt = iq_elt.addElement((NS_BS, 'query')) - query_elt['mode'] = 'tcp' - query_elt['sid'] = sid + query_elt = iq_elt.addElement((NS_BS, "query")) + query_elt["mode"] = "tcp" + query_elt["sid"] = sid for candidate in candidates: - streamhost = query_elt.addElement('streamhost') - streamhost['host'] = candidate.host - streamhost['port'] = str(candidate.port) - streamhost['jid'] = candidate.jid.full() + streamhost = query_elt.addElement("streamhost") + streamhost["host"] = candidate.host + streamhost["port"] = str(candidate.port) + streamhost["jid"] = candidate.jid.full() log.debug(u"Candidate proposed: {}".format(candidate)) d = iq_elt.send() @@ -1055,31 +1179,39 @@ @param iq_elt(domish.Element): <iq> result """ try: - query_elt = iq_elt.elements(NS_BS, 'query').next() - streamhost_used_elt = query_elt.elements(NS_BS, 'streamhost-used').next() + query_elt = iq_elt.elements(NS_BS, "query").next() + streamhost_used_elt = query_elt.elements(NS_BS, "streamhost-used").next() except StopIteration: log.warning(u"No streamhost found in stream query") # FIXME: must clean session return - streamhost_jid = jid.JID(streamhost_used_elt['jid']) + streamhost_jid = jid.JID(streamhost_used_elt["jid"]) try: - candidate = (c for c in session_data['candidates'] if c.jid == streamhost_jid).next() + candidate = ( + c for c in session_data["candidates"] if c.jid == streamhost_jid + ).next() except StopIteration: - log.warning(u"Candidate [{jid}] is unknown !".format(jid=streamhost_jid.full())) + log.warning( + u"Candidate [{jid}] is unknown !".format(jid=streamhost_jid.full()) + ) return else: log.info(u"Candidate choosed by target: {}".format(candidate)) if candidate.type == XEP_0065.TYPE_PROXY: log.info(u"A Socks5 proxy is used") - d = self.connectCandidate(client, candidate, session_data['hash']) - d.addCallback(lambda dummy: candidate.activate(session_data['id'], session_data['peer_jid'], client)) + d = self.connectCandidate(client, candidate, session_data["hash"]) + d.addCallback( + lambda dummy: candidate.activate( + session_data["id"], session_data["peer_jid"], client + ) + ) d.addErrback(self._activationEb) else: d = defer.succeed(None) - d.addCallback(lambda dummy: candidate.startTransfer(session_data['hash'])) + d.addCallback(lambda dummy: candidate.startTransfer(session_data["hash"])) def _activationEb(self, failure): log.warning(u"Proxy activation error: {}".format(failure.value)) @@ -1105,7 +1237,7 @@ @return (dict): session data """ if sid in client.xep_0065_sid_session: - raise exceptions.ConflictError(u'A session with this id already exists !') + raise exceptions.ConflictError(u"A session with this id already exists !") if requester: session_hash = getSessionHash(client.jid, to_jid, sid) session_data = self._registerHash(client, session_hash, stream_object) @@ -1115,15 +1247,19 @@ session_d.addBoth(self.killSession, session_hash, sid, client) session_data = client._s5b_sessions[session_hash] = { DEFER_KEY: session_d, - TIMER_KEY: reactor.callLater(TIMEOUT, self._timeOut, session_hash, client), - } + TIMER_KEY: reactor.callLater( + TIMEOUT, self._timeOut, session_hash, client + ), + } client.xep_0065_sid_session[sid] = session_data session_data.update( - {'id': sid, - 'peer_jid': to_jid, - 'stream_object': stream_object, - 'hash': session_hash, - }) + { + "id": sid, + "peer_jid": to_jid, + "stream_object": stream_object, + "hash": session_hash, + } + ) return session_data @@ -1141,7 +1277,7 @@ """ if client is None: try: - client = self.hash_clients_map[session_hash] + client = self.hash_clients_map[session_hash] except KeyError as e: log.warning(u"The requested session doesn't exists !") raise e @@ -1167,10 +1303,10 @@ session_data = client._s5b_sessions[session_hash] = { DEFER_KEY: session_d, TIMER_KEY: reactor.callLater(TIMEOUT, self._timeOut, session_hash, client), - } + } if stream_object is not None: - session_data['stream_object'] = stream_object + session_data["stream_object"] = stream_object assert session_hash not in self.hash_clients_map self.hash_clients_map[session_hash] = client @@ -1180,22 +1316,22 @@ def associateStreamObject(self, client, session_hash, stream_object): """Associate a stream object with a session""" session_data = self.getSession(client, session_hash) - assert 'stream_object' not in session_data - session_data['stream_object'] = stream_object + assert "stream_object" not in session_data + session_data["stream_object"] = stream_object def streamQuery(self, iq_elt, client): log.debug(u"BS stream query") iq_elt.handled = True - query_elt = iq_elt.elements(NS_BS, 'query').next() + query_elt = iq_elt.elements(NS_BS, "query").next() try: - sid = query_elt['sid'] + sid = query_elt["sid"] except KeyError: log.warning(u"Invalid bystreams request received") return client.sendError(iq_elt, "bad-request") - streamhost_elts = list(query_elt.elements(NS_BS, 'streamhost')) + streamhost_elts = list(query_elt.elements(NS_BS, "streamhost")) if not streamhost_elts: return client.sendError(iq_elt, "bad-request") @@ -1203,7 +1339,7 @@ session_data = client.xep_0065_sid_session[sid] except KeyError: log.warning(u"Ignoring unexpected BS transfer: {}".format(sid)) - return client.sendError(iq_elt, 'not-acceptable') + return client.sendError(iq_elt, "not-acceptable") peer_jid = session_data["peer_jid"] = jid.JID(iq_elt["from"]) @@ -1211,7 +1347,7 @@ nb_sh = len(streamhost_elts) for idx, sh_elt in enumerate(streamhost_elts): try: - host, port, jid_ = sh_elt['host'], sh_elt['port'], jid.JID(sh_elt['jid']) + host, port, jid_ = sh_elt["host"], sh_elt["port"], jid.JID(sh_elt["jid"]) except KeyError: log.warning(u"malformed streamhost element") return client.sendError(iq_elt, "bad-request") @@ -1225,19 +1361,19 @@ for candidate in candidates: log.info(u"Candidate proposed: {}".format(candidate)) - d = self.getBestCandidate(client, candidates, session_data['hash']) + d = self.getBestCandidate(client, candidates, session_data["hash"]) d.addCallback(self._ackStream, iq_elt, session_data, client) def _ackStream(self, candidate, iq_elt, session_data, client): if candidate is None: log.info("No streamhost candidate worked, we have to end negotiation") - return client.sendError(iq_elt, 'item-not-found') + return client.sendError(iq_elt, "item-not-found") log.info(u"We choose: {}".format(candidate)) - result_elt = xmlstream.toResponse(iq_elt, 'result') - query_elt = result_elt.addElement((NS_BS, 'query')) - query_elt['sid'] = session_data['id'] - streamhost_used_elt = query_elt.addElement('streamhost-used') - streamhost_used_elt['jid'] = candidate.jid.full() + result_elt = xmlstream.toResponse(iq_elt, "result") + query_elt = result_elt.addElement((NS_BS, "query")) + query_elt["sid"] = session_data["id"] + streamhost_used_elt = query_elt.addElement("streamhost-used") + streamhost_used_elt["jid"] = candidate.jid.full() client.send(result_elt) @@ -1249,10 +1385,12 @@ self.host = plugin_parent.host def connectionInitialized(self): - self.xmlstream.addObserver(BS_REQUEST, self.plugin_parent.streamQuery, client=self.parent) + self.xmlstream.addObserver( + BS_REQUEST, self.plugin_parent.streamQuery, client=self.parent + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_BS)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0070.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0070.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,25 +21,27 @@ from sat.core.log import getLogger from twisted.words.protocols.jabber import xmlstream from twisted.words.protocols import jabber + log = getLogger(__name__) from sat.tools import xml_tools from wokkel import disco, iwokkel from zope.interface import implements + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: from wokkel.subprotocols import XMPPHandler -NS_HTTP_AUTH = 'http://jabber.org/protocol/http-auth' +NS_HTTP_AUTH = "http://jabber.org/protocol/http-auth" -IQ = 'iq' -IQ_GET = '/'+IQ+'[@type="get"]' +IQ = "iq" +IQ_GET = "/" + IQ + '[@type="get"]' IQ_HTTP_AUTH_REQUEST = IQ_GET + '/confirm[@xmlns="' + NS_HTTP_AUTH + '"]' -MSG = 'message' -MSG_GET = '/'+MSG+'[@type="normal"]' +MSG = "message" +MSG_GET = "/" + MSG + '[@type="normal"]' MSG_HTTP_AUTH_REQUEST = MSG_GET + '/confirm[@xmlns="' + NS_HTTP_AUTH + '"]' @@ -51,7 +53,7 @@ C.PI_DEPENDENCIES: [], C.PI_MAIN: "XEP_0070", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of HTTP Requests via XMPP""") + C.PI_DESCRIPTION: _("""Implementation of HTTP Requests via XMPP"""), } @@ -88,16 +90,20 @@ def _treatHttpAuthRequest(self, elt, stanzaType, client): elt.handled = True - auth_elt = elt.elements(NS_HTTP_AUTH, 'confirm').next() - auth_id = auth_elt['id'] - auth_method = auth_elt['method'] - auth_url = auth_elt['url'] + auth_elt = elt.elements(NS_HTTP_AUTH, "confirm").next() + auth_id = auth_elt["id"] + auth_method = auth_elt["method"] + auth_url = auth_elt["url"] self._dictRequest[client] = (auth_id, auth_method, auth_url, stanzaType, elt) - confirm_ui = xml_tools.XMLUI("form", title=D_(u"Auth confirmation"), submit_id='') - confirm_ui.addText(D_(u"{} needs to validate your identity, do you agree ?".format(auth_url))) + confirm_ui = xml_tools.XMLUI("form", title=D_(u"Auth confirmation"), submit_id="") + confirm_ui.addText( + D_(u"{} needs to validate your identity, do you agree ?".format(auth_url)) + ) confirm_ui.addText(D_(u"Validation code : {}".format(auth_id))) - confirm_ui.addText(D_(u"Please check that this code is the same as on {}".format(auth_url))) + confirm_ui.addText( + D_(u"Please check that this code is the same as on {}".format(auth_url)) + ) confirm_ui.addText(u"") confirm_ui.addText(D_(u"Submit to authorize, cancel otherwise.")) d = xml_tools.deferredUI(self.host, confirm_ui, chained=False) @@ -107,7 +113,7 @@ def _authRequestCallback(self, result, profile): client = self.host.getClient(profile) try: - cancelled = result['cancelled'] + cancelled = result["cancelled"] except KeyError: cancelled = False @@ -119,23 +125,25 @@ authorized = False else: try: - auth_id, auth_method, auth_url, stanzaType, elt = self._dictRequest[client] + auth_id, auth_method, auth_url, stanzaType, elt = self._dictRequest[ + client + ] del self._dictRequest[client] authorized = True except KeyError: authorized = False if authorized: - if (stanzaType == IQ): + if stanzaType == IQ: # iq log.debug(_(u"XEP-0070 reply iq")) - iq_result_elt = xmlstream.toResponse(elt, 'result') + iq_result_elt = xmlstream.toResponse(elt, "result") client.send(iq_result_elt) - elif (stanzaType == MSG): + elif stanzaType == MSG: # message log.debug(_(u"XEP-0070 reply message")) - msg_result_elt = xmlstream.toResponse(elt, 'result') - msg_result_elt.addChild(elt.elements(NS_HTTP_AUTH, 'confirm').next()) + msg_result_elt = xmlstream.toResponse(elt, "result") + msg_result_elt.addChild(elt.elements(NS_HTTP_AUTH, "confirm").next()) client.send(msg_result_elt) else: log.debug(_(u"XEP-0070 reply error")) @@ -152,11 +160,19 @@ self.profile = profile def connectionInitialized(self): - self.xmlstream.addObserver(IQ_HTTP_AUTH_REQUEST, self.plugin_parent.onHttpAuthRequestIQ, client=self.parent) - self.xmlstream.addObserver(MSG_HTTP_AUTH_REQUEST, self.plugin_parent.onHttpAuthRequestMsg, client=self.parent) + self.xmlstream.addObserver( + IQ_HTTP_AUTH_REQUEST, + self.plugin_parent.onHttpAuthRequestIQ, + client=self.parent, + ) + self.xmlstream.addObserver( + MSG_HTTP_AUTH_REQUEST, + self.plugin_parent.onHttpAuthRequestMsg, + client=self.parent, + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_HTTP_AUTH)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0071.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0071.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,24 +21,28 @@ from sat.core.constants import Const as C from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) from sat.tools.common import data_format from twisted.internet import defer from wokkel import disco, iwokkel from zope.interface import implements + # from lxml import etree try: from lxml import html except ImportError: - raise exceptions.MissingModule(u"Missing module lxml, please download/install it from http://lxml.de/") + raise exceptions.MissingModule( + u"Missing module lxml, please download/install it from http://lxml.de/" + ) try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: from wokkel.subprotocols import XMPPHandler -NS_XHTML_IM = 'http://jabber.org/protocol/xhtml-im' -NS_XHTML = 'http://www.w3.org/1999/xhtml' +NS_XHTML_IM = "http://jabber.org/protocol/xhtml-im" +NS_XHTML = "http://www.w3.org/1999/xhtml" PLUGIN_INFO = { C.PI_NAME: "XHTML-IM Plugin", @@ -48,28 +52,39 @@ C.PI_DEPENDENCIES: ["TEXT-SYNTAXES"], C.PI_MAIN: "XEP_0071", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of XHTML-IM""") + C.PI_DESCRIPTION: _("""Implementation of XHTML-IM"""), } allowed = { - "a": set(["href", "style", "type"]), - "blockquote": set(["style"]), - "body": set(["style"]), - "br": set([]), - "cite": set(["style"]), - "em": set([]), - "img": set(["alt", "height", "src", "style", "width"]), - "li": set(["style"]), - "ol": set(["style"]), - "p": set(["style"]), - "span": set(["style"]), - "strong": set([]), - "ul": set(["style"]), - } + "a": set(["href", "style", "type"]), + "blockquote": set(["style"]), + "body": set(["style"]), + "br": set([]), + "cite": set(["style"]), + "em": set([]), + "img": set(["alt", "height", "src", "style", "width"]), + "li": set(["style"]), + "ol": set(["style"]), + "p": set(["style"]), + "span": set(["style"]), + "strong": set([]), + "ul": set(["style"]), +} -styles_allowed = ["background-color", "color", "font-family", "font-size", "font-style", "font-weight", "margin-left", "margin-right", "text-align", "text-decoration"] +styles_allowed = [ + "background-color", + "color", + "font-family", + "font-size", + "font-style", + "font-weight", + "margin-left", + "margin-right", + "text-align", + "text-decoration", +] -blacklist = ['script'] # tag that we have to kill (we don't keep content) +blacklist = ["script"] # tag that we have to kill (we don't keep content) class XEP_0071(object): @@ -79,7 +94,12 @@ log.info(_("XHTML-IM plugin initialization")) self.host = host self._s = self.host.plugins["TEXT-SYNTAXES"] - self._s.addSyntax(self.SYNTAX_XHTML_IM, lambda xhtml: xhtml, self.XHTML2XHTML_IM, [self._s.OPT_HIDDEN]) + self._s.addSyntax( + self.SYNTAX_XHTML_IM, + lambda xhtml: xhtml, + self.XHTML2XHTML_IM, + [self._s.OPT_HIDDEN], + ) host.trigger.add("MessageReceived", self.messageReceivedTrigger) host.trigger.add("sendMessage", self.sendMessageTrigger) @@ -97,17 +117,23 @@ # TODO: check if text only body is empty, then try to convert XHTML-IM to pure text and show a warning message def converted(xhtml, lang): if lang: - data['extra']['xhtml_{}'.format(lang)] = xhtml + data["extra"]["xhtml_{}".format(lang)] = xhtml else: - data['extra']['xhtml'] = xhtml + data["extra"]["xhtml"] = xhtml defers = [] for body_elt in body_elts: - lang = body_elt.getAttribute((C.NS_XML, 'lang'), '') - treat_d = defer.succeed(None) # deferred used for treatments - if self.host.trigger.point("xhtml_post_treat", client, message_elt, body_elt, lang, treat_d): + lang = body_elt.getAttribute((C.NS_XML, "lang"), "") + treat_d = defer.succeed(None) # deferred used for treatments + if self.host.trigger.point( + "xhtml_post_treat", client, message_elt, body_elt, lang, treat_d + ): continue - treat_d.addCallback(lambda dummy: self._s.convert(body_elt.toXml(), self.SYNTAX_XHTML_IM, safe=True)) + treat_d.addCallback( + lambda dummy: self._s.convert( + body_elt.toXml(), self.SYNTAX_XHTML_IM, safe=True + ) + ) treat_d.addCallback(converted, lang) defers.append(treat_d) @@ -116,15 +142,15 @@ return d_list def _fill_body_text(self, text, data, lang): - data['message'][lang or ''] = text - message_elt = data['xml'] + data["message"][lang or ""] = text + message_elt = data["xml"] body_elt = message_elt.addElement("body", content=text) if lang: - body_elt[(C.NS_XML, 'lang')] = lang + body_elt[(C.NS_XML, "lang")] = lang def _check_body_text(self, data, lang, markup, syntax, defers): """check if simple text message exists, and fill if needed""" - if not (lang or '') in data['message']: + if not (lang or "") in data["message"]: d = self._s.convert(markup, syntax, self._s.SYNTAX_TEXT) d.addCallback(self._fill_body_text, data, lang) defers.append(d) @@ -136,30 +162,30 @@ """ # at this point, either ['extra']['rich'] or ['extra']['xhtml'] exists # but both can't exist at the same time - message_elt = data['xml'] - html_elt = message_elt.addElement((NS_XHTML_IM, 'html')) + message_elt = data["xml"] + html_elt = message_elt.addElement((NS_XHTML_IM, "html")) def syntax_converted(xhtml_im, lang): - body_elt = html_elt.addElement((NS_XHTML, 'body')) + body_elt = html_elt.addElement((NS_XHTML, "body")) if lang: - body_elt[(C.NS_XML, 'lang')] = lang - data['extra']['xhtml_{}'.format(lang)] = xhtml_im + body_elt[(C.NS_XML, "lang")] = lang + data["extra"]["xhtml_{}".format(lang)] = xhtml_im else: - data['extra']['xhtml'] = xhtml_im + data["extra"]["xhtml"] = xhtml_im body_elt.addRawXml(xhtml_im) syntax = self._s.getCurrentSyntax(client.profile) defers = [] - if u'xhtml' in data['extra']: + if u"xhtml" in data["extra"]: # we have directly XHTML - for lang, xhtml in data_format.getSubDict('xhtml', data['extra']): + for lang, xhtml in data_format.getSubDict("xhtml", data["extra"]): self._check_body_text(data, lang, xhtml, self._s.SYNTAX_XHTML, defers) d = self._s.convert(xhtml, self._s.SYNTAX_XHTML, self.SYNTAX_XHTML_IM) d.addCallback(syntax_converted, lang) defers.append(d) - elif u'rich' in data['extra']: + elif u"rich" in data["extra"]: # we have rich syntax to convert - for lang, rich_data in data_format.getSubDict('rich', data['extra']): + for lang, rich_data in data_format.getSubDict("rich", data["extra"]): self._check_body_text(data, lang, rich_data, syntax, defers) d = self._s.convert(rich_data, syntax, self.SYNTAX_XHTML_IM) d.addCallback(syntax_converted, lang) @@ -174,12 +200,12 @@ """ Check presence of XHTML-IM in message """ try: - html_elt = message.elements(NS_XHTML_IM, 'html').next() + html_elt = message.elements(NS_XHTML_IM, "html").next() except StopIteration: # No XHTML-IM pass else: - body_elts = html_elt.elements(NS_XHTML, 'body') + body_elts = html_elt.elements(NS_XHTML, "body") post_treat.addCallback(self._messagePostTreat, message, body_elts, client) return True @@ -187,18 +213,20 @@ """ Check presence of rich text in extra """ rich = {} xhtml = {} - for key, value in data['extra'].iteritems(): - if key.startswith('rich'): + for key, value in data["extra"].iteritems(): + if key.startswith("rich"): rich[key[5:]] = value - elif key.startswith('xhtml'): + elif key.startswith("xhtml"): xhtml[key[6:]] = value if rich and xhtml: - raise exceptions.DataError(_(u"Can't have XHTML and rich content at the same time")) + raise exceptions.DataError( + _(u"Can't have XHTML and rich content at the same time") + ) if rich or xhtml: if rich: - data['rich'] = rich + data["rich"] = rich else: - data['xhtml'] = xhtml + data["xhtml"] = xhtml post_xml_treatments.addCallback(self._sendMessageAddRich, client) return True @@ -208,7 +236,7 @@ """ purged = [] - styles = [style.strip().split(':') for style in styles_raw.split(';')] + styles = [style.strip().split(":") for style in styles_raw.split(";")] for style_tuple in styles: if len(style_tuple) != 2: @@ -219,7 +247,7 @@ continue purged.append((name, value.strip())) - return u'; '.join([u"%s: %s" % data for data in purged]) + return u"; ".join([u"%s: %s" % data for data in purged]) def XHTML2XHTML_IM(self, xhtml): """ Convert XHTML document to XHTML_IM subset @@ -227,12 +255,12 @@ """ # TODO: more clever tag replacement (replace forbidden tags with equivalents when possible) - parser = html.HTMLParser(remove_comments=True, encoding='utf-8') + parser = html.HTMLParser(remove_comments=True, encoding="utf-8") root = html.fromstring(xhtml, parser=parser) - body_elt = root.find('body') + body_elt = root.find("body") if body_elt is None: # we use the whole XML as body if no body element is found - body_elt = html.Element('body') + body_elt = html.Element("body") body_elt.append(root) else: body_elt.attrib.clear() @@ -247,24 +275,25 @@ attrib = elem.attrib att_to_remove = set(attrib).difference(allowed[elem.tag]) for att in att_to_remove: - del(attrib[att]) + del (attrib[att]) if "style" in attrib: attrib["style"] = self._purgeStyle(attrib["style"]) for elem in to_strip: if elem.tag in blacklist: - #we need to remove the element and all descendants + # we need to remove the element and all descendants log.debug(u"removing black listed tag: %s" % (elem.tag)) elem.drop_tree() else: elem.drop_tag() - if len(body_elt) !=1: + if len(body_elt) != 1: root_elt = body_elt body_elt.tag = "p" else: root_elt = body_elt[0] - return html.tostring(root_elt, encoding='unicode', method='xml') + return html.tostring(root_elt, encoding="unicode", method="xml") + class XEP_0071_handler(XMPPHandler): implements(iwokkel.IDisco) @@ -273,8 +302,8 @@ self.plugin_parent = plugin_parent self.host = plugin_parent.host - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_XHTML_IM)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0077.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0077.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core.constants import Const as C from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.protocols.jabber import jid from twisted.words.protocols.jabber import xmlstream @@ -29,7 +30,7 @@ from wokkel import data_form -NS_REG = 'jabber:iq:register' +NS_REG = "jabber:iq:register" PLUGIN_INFO = { C.PI_NAME: "XEP 0077 Plugin", @@ -38,11 +39,12 @@ C.PI_PROTOCOLS: ["XEP-0077"], C.PI_DEPENDENCIES: [], C.PI_MAIN: "XEP_0077", - C.PI_DESCRIPTION: _("""Implementation of in-band registration""") + C.PI_DESCRIPTION: _("""Implementation of in-band registration"""), } # FIXME: this implementation is incomplete + class RegisteringAuthenticator(xmlstream.ConnectAuthenticator): # FIXME: request IQ is not send to check available fields, while XEP recommand to use it # FIXME: doesn't handle data form or oob @@ -53,8 +55,7 @@ self.password = password self.email = email self.registered = defer.Deferred() - log.debug(_(u"Registration asked for {jid}").format( - jid = jid_)) + log.debug(_(u"Registration asked for {jid}").format(jid=jid_)) def connectionMade(self): log.debug(_(u"Connection made with {server}".format(server=self.jid.host))) @@ -77,57 +78,78 @@ class XEP_0077(object): - def __init__(self, host): log.info(_("Plugin XEP_0077 initialization")) self.host = host - host.bridge.addMethod("inBandRegister", ".plugin", in_sign='ss', out_sign='', - method=self._inBandRegister, - async=True) - host.bridge.addMethod("inBandAccountNew", ".plugin", in_sign='ssssi', out_sign='', - method=self._registerNewAccount, - async=True) - host.bridge.addMethod("inBandUnregister", ".plugin", in_sign='ss', out_sign='', - method=self._unregister, - async=True) - host.bridge.addMethod("inBandPasswordChange", ".plugin", in_sign='ss', out_sign='', - method=self._changePassword, - async=True) + host.bridge.addMethod( + "inBandRegister", + ".plugin", + in_sign="ss", + out_sign="", + method=self._inBandRegister, + async=True, + ) + host.bridge.addMethod( + "inBandAccountNew", + ".plugin", + in_sign="ssssi", + out_sign="", + method=self._registerNewAccount, + async=True, + ) + host.bridge.addMethod( + "inBandUnregister", + ".plugin", + in_sign="ss", + out_sign="", + method=self._unregister, + async=True, + ) + host.bridge.addMethod( + "inBandPasswordChange", + ".plugin", + in_sign="ss", + out_sign="", + method=self._changePassword, + async=True, + ) @staticmethod def buildRegisterIQ(xmlstream_, jid_, password, email=None): - iq_elt = xmlstream.IQ(xmlstream_, 'set') + iq_elt = xmlstream.IQ(xmlstream_, "set") iq_elt["to"] = jid_.host - query_elt = iq_elt.addElement(('jabber:iq:register', 'query')) - username_elt = query_elt.addElement('username') + query_elt = iq_elt.addElement(("jabber:iq:register", "query")) + username_elt = query_elt.addElement("username") username_elt.addContent(jid_.user) - password_elt = query_elt.addElement('password') + password_elt = query_elt.addElement("password") password_elt.addContent(password) if email is not None: - email_elt = query_elt.addElement('email') + email_elt = query_elt.addElement("email") email_elt.addContent(email) return iq_elt def _regCb(self, answer, client, post_treat_cb): """Called after the first get IQ""" try: - query_elt = answer.elements(NS_REG, 'query').next() + query_elt = answer.elements(NS_REG, "query").next() except StopIteration: raise exceptions.DataError("Can't find expected query element") try: - x_elem = query_elt.elements(data_form.NS_X_DATA, 'x').next() + x_elem = query_elt.elements(data_form.NS_X_DATA, "x").next() except StopIteration: # XXX: it seems we have an old service which doesn't manage data forms log.warning(_("Can't find data form")) - raise exceptions.DataError(_("This gateway can't be managed by SàT, sorry :(")) + raise exceptions.DataError( + _("This gateway can't be managed by SàT, sorry :(") + ) def submitForm(data, profile): form_elt = xml_tools.XMLUIResultToElt(data) iq_elt = client.IQ() - iq_elt['id'] = answer['id'] - iq_elt['to'] = answer['from'] + iq_elt["id"] = answer["id"] + iq_elt["to"] = answer["from"] query_elt = iq_elt.addElement("query", NS_REG) query_elt.addChild(form_elt) d = iq_elt.send() @@ -136,7 +158,9 @@ return d form = data_form.Form.fromElement(x_elem) - submit_reg_id = self.host.registerCallback(submitForm, with_data=True, one_shot=True) + submit_reg_id = self.host.registerCallback( + submitForm, with_data=True, one_shot=True + ) return xml_tools.dataForm2XMLUI(form, submit_reg_id) def _regEb(self, failure, client): @@ -147,13 +171,15 @@ def _regSuccess(self, answer, client, post_treat_cb): log.debug(_(u"registration answer: %s") % answer.toXml()) if post_treat_cb is not None: - post_treat_cb(jid.JID(answer['from']), client.profile) + post_treat_cb(jid.JID(answer["from"]), client.profile) return {} def _regFailure(self, failure, client): log.info(_(u"Registration failure: %s") % unicode(failure.value)) - if failure.value.condition == 'conflict': - raise exceptions.ConflictError( _("Username already exists, please choose an other one")) + if failure.value.condition == "conflict": + raise exceptions.ConflictError( + _("Username already exists, please choose an other one") + ) raise failure def _inBandRegister(self, to_jid_s, profile_key=C.PROF_KEY_NONE): @@ -167,24 +193,31 @@ # FIXME: this post_treat_cb arguments seems wrong, check it client = self.host.getClient(profile_key) log.debug(_(u"Asking registration for {}").format(to_jid.full())) - reg_request = client.IQ(u'get') + reg_request = client.IQ(u"get") reg_request["from"] = client.jid.full() reg_request["to"] = to_jid.full() - reg_request.addElement('query', NS_REG) - d = reg_request.send(to_jid.full()).addCallbacks(self._regCb, self._regEb, callbackArgs=[client, post_treat_cb], errbackArgs=[client]) + reg_request.addElement("query", NS_REG) + d = reg_request.send(to_jid.full()).addCallbacks( + self._regCb, + self._regEb, + callbackArgs=[client, post_treat_cb], + errbackArgs=[client], + ) return d def _registerNewAccount(self, jid_, password, email, host, port): kwargs = {} if email: - kwargs['email'] = email + kwargs["email"] = email if host: - kwargs['host'] = host + kwargs["host"] = host if port: - kwargs['port'] = port + kwargs["port"] = port return self.registerNewAccount(jid.JID(jid_), password, **kwargs) - def registerNewAccount(self, jid_, password, email=None, host=u"127.0.0.1", port=C.XMPP_C2S_PORT): + def registerNewAccount( + self, jid_, password, email=None, host=u"127.0.0.1", port=C.XMPP_C2S_PORT + ): """register a new account on a XMPP server @param jid_(jid.JID): request jid to register @@ -207,7 +240,11 @@ def changePassword(self, client, new_password): iq_elt = self.buildRegisterIQ(client.xmlstream, client.jid, new_password) d = iq_elt.send(client.jid.host) - d.addCallback(lambda dummy: self.host.memory.setParam("Password", new_password, "Connection", profile_key=client.profile)) + d.addCallback( + lambda dummy: self.host.memory.setParam( + "Password", new_password, "Connection", profile_key=client.profile + ) + ) return d def _unregister(self, to_jid_s, profile_key): @@ -222,7 +259,7 @@ @param to_jid(jid.JID): jid of the service or server """ iq_elt = client.IQ() - iq_elt['to'] = to_jid.full() - query_elt = iq_elt.addElement((NS_REG, u'query')) - query_elt.addElement(u'remove') + iq_elt["to"] = to_jid.full() + query_elt = iq_elt.addElement((NS_REG, u"query")) + query_elt.addElement(u"remove") return iq_elt.send()
--- a/sat/plugins/plugin_xep_0085.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0085.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,10 +21,12 @@ from sat.core.constants import Const as C from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) from wokkel import disco, iwokkel from zope.interface import implements from twisted.words.protocols.jabber.jid import JID + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: @@ -50,7 +52,7 @@ C.PI_DEPENDENCIES: [], C.PI_MAIN: "XEP_0085", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Chat State Notifications Protocol""") + C.PI_DESCRIPTION: _("""Implementation of Chat State Notifications Protocol"""), } @@ -63,7 +65,7 @@ "inactive": {"next_state": "gone", "delay": 480}, "gone": {"next_state": "", "delay": 0}, "composing": {"next_state": "paused", "delay": 30}, - "paused": {"next_state": "inactive", "delay": 450} + "paused": {"next_state": "inactive", "delay": 450}, } @@ -71,6 +73,7 @@ """ This error is raised when an unknown chat state is used. """ + pass @@ -78,6 +81,7 @@ """ Implementation for XEP 0085 """ + params = """ <params> <individual> @@ -87,10 +91,10 @@ </individual> </params> """ % { - 'category_name': PARAM_KEY, - 'category_label': _(PARAM_KEY), - 'param_name': PARAM_NAME, - 'param_label': _('Enable chat state notifications') + "category_name": PARAM_KEY, + "category_label": _(PARAM_KEY), + "param_name": PARAM_NAME, + "param_label": _("Enable chat state notifications"), } def __init__(self, host): @@ -107,11 +111,16 @@ host.trigger.add("paramUpdateTrigger", self.paramUpdateTrigger) # args: to_s (jid as string), profile - host.bridge.addMethod("chatStateComposing", ".plugin", in_sign='ss', - out_sign='', method=self.chatStateComposing) + host.bridge.addMethod( + "chatStateComposing", + ".plugin", + in_sign="ss", + out_sign="", + method=self.chatStateComposing, + ) # args: from (jid as string), state in CHAT_STATES, profile - host.bridge.addSignal("chatStateReceived", ".plugin", signature='sss') + host.bridge.addSignal("chatStateReceived", ".plugin", signature="sss") def getHandler(self, client): return XEP_0085_handler(self, client.profile) @@ -125,7 +134,7 @@ # FIXME: the "unavailable" presence stanza is received by to_jid # before the chat state, so it will be ignored... find a way to # actually defer the disconnection - self.map[profile][to_jid]._onEvent('gone') + self.map[profile][to_jid]._onEvent("gone") del self.map[profile] def updateCache(self, entity_jid, value, profile): @@ -139,7 +148,9 @@ if value == DELETE_VALUE: self.host.memory.delEntityDatum(entity_jid, ENTITY_KEY, profile) else: - self.host.memory.updateEntityData(entity_jid, ENTITY_KEY, value, profile_key=profile) + self.host.memory.updateEntityData( + entity_jid, ENTITY_KEY, value, profile_key=profile + ) if not value or value == DELETE_VALUE: # reinit chat state UI for this or these contact(s) self.host.bridge.chatStateReceived(entity_jid.full(), "", profile) @@ -153,7 +164,9 @@ @param type_: parameter type """ if (category, name) == (PARAM_KEY, PARAM_NAME): - self.updateCache(C.ENTITY_ALL, True if C.bool(value) else DELETE_VALUE, profile=profile) + self.updateCache( + C.ENTITY_ALL, True if C.bool(value) else DELETE_VALUE, profile=profile + ) return False return True @@ -178,7 +191,7 @@ # contact enabled Chat State Notifications self.updateCache(from_jid, True, profile=profile) except StopIteration: - if message.getAttribute('type') == 'chat': + if message.getAttribute("type") == "chat": # contact didn't enable Chat State Notifications self.updateCache(from_jid, False, profile=profile) return True @@ -188,32 +201,40 @@ # send our next "composing" states to any MUC and to the contacts who enabled the feature self._chatStateInit(from_jid, message.getAttribute("type"), profile) - state_list = [child.name for child in message.elements() if - message.getAttribute("type") in MESSAGE_TYPES - and child.name in CHAT_STATES - and child.defaultUri == NS_CHAT_STATES] + state_list = [ + child.name + for child in message.elements() + if message.getAttribute("type") in MESSAGE_TYPES + and child.name in CHAT_STATES + and child.defaultUri == NS_CHAT_STATES + ] for state in state_list: # there must be only one state according to the XEP - if state != 'gone' or message.getAttribute('type') != 'groupchat': - self.host.bridge.chatStateReceived(message.getAttribute("from"), state, profile) + if state != "gone" or message.getAttribute("type") != "groupchat": + self.host.bridge.chatStateReceived( + message.getAttribute("from"), state, profile + ) break return True - def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments): + def sendMessageTrigger( + self, client, mess_data, pre_xml_treatments, post_xml_treatments + ): """ Eventually add the chat state to the message and initiate the state machine when sending an "active" state. """ profile = client.profile + def treatment(mess_data): - message = mess_data['xml'] + message = mess_data["xml"] to_jid = JID(message.getAttribute("to")) if not self._checkActivation(to_jid, forceEntityData=True, profile=profile): return mess_data try: # message with a body always mean active state domish.generateElementsNamed(message.elements(), name="body").next() - message.addElement('active', NS_CHAT_STATES) + message.addElement("active", NS_CHAT_STATES) # launch the chat state machine (init the timer) if self._isMUC(to_jid, profile): to_jid = to_jid.userhostJID() @@ -236,8 +257,8 @@ @return: bool """ try: - type_ = self.host.memory.getEntityDatum(to_jid.userhostJID(), 'type', profile) - if type_ == 'chatroom': # FIXME: should not use disco instead ? + type_ = self.host.memory.getEntityDatum(to_jid.userhostJID(), "type", profile) + if type_ == "chatroom": # FIXME: should not use disco instead ? return True except (exceptions.UnknownEntityError, KeyError): pass @@ -281,8 +302,7 @@ return profile_map = self.map.setdefault(profile, {}) if to_jid not in profile_map: - machine = ChatStateMachine(self.host, to_jid, - mess_type, profile) + machine = ChatStateMachine(self.host, to_jid, mess_type, profile) self.map[profile][to_jid] = machine def _chatStateActive(self, to_jid, mess_type, profile_key): @@ -316,7 +336,9 @@ to_jid = to_jid.userhostJID() elif not to_jid.resource: to_jid.resource = self.host.memory.getMainResource(client, to_jid) - if not self._checkActivation(to_jid, forceEntityData=False, profile=client.profile): + if not self._checkActivation( + to_jid, forceEntityData=False, profile=client.profile + ): return try: self.map[client.profile][to_jid]._onEvent("composing") @@ -359,22 +381,26 @@ assert "next_state" in transition and "delay" in transition if state != self.state and state != "active": - if state != 'gone' or self.mess_type != 'groupchat': + if state != "gone" or self.mess_type != "groupchat": # send a new message without body - log.debug(u"sending state '{state}' to {jid}".format(state=state, jid=self.to_jid.full())) + log.debug( + u"sending state '{state}' to {jid}".format( + state=state, jid=self.to_jid.full() + ) + ) client = self.host.getClient(self.profile) mess_data = { - 'from': client.jid, - 'to': self.to_jid, - 'uid': '', - 'message': {}, - 'type': self.mess_type, - 'subject': {}, - 'extra': {}, - } + "from": client.jid, + "to": self.to_jid, + "uid": "", + "message": {}, + "type": self.mess_type, + "subject": {}, + "extra": {}, + } client.generateMessageXML(mess_data) - mess_data['xml'].addElement(state, NS_CHAT_STATES) - client.send(mess_data['xml']) + mess_data["xml"].addElement(state, NS_CHAT_STATES) + client.send(mess_data["xml"]) self.state = state try: @@ -383,7 +409,9 @@ pass if transition["next_state"] and transition["delay"] > 0: - self.timer = reactor.callLater(transition["delay"], self._onEvent, transition["next_state"]) + self.timer = reactor.callLater( + transition["delay"], self._onEvent, transition["next_state"] + ) class XEP_0085_handler(XMPPHandler): @@ -394,8 +422,8 @@ self.host = plugin_parent.host self.profile = profile - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_CHAT_STATES)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0092.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0092.py Wed Jun 27 20:14:46 2018 +0200 @@ -24,6 +24,7 @@ from wokkel import compat from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) NS_VERSION = "jabber:iq:version" @@ -37,17 +38,23 @@ C.PI_DEPENDENCIES: [], C.PI_RECOMMENDATIONS: [C.TEXT_CMDS], C.PI_MAIN: "XEP_0092", - C.PI_HANDLER: "no", # version is already handler in core.xmpp module - C.PI_DESCRIPTION: _("""Implementation of Software Version""") + C.PI_HANDLER: "no", # version is already handler in core.xmpp module + C.PI_DESCRIPTION: _("""Implementation of Software Version"""), } class XEP_0092(object): - def __init__(self, host): log.info(_("Plugin XEP_0092 initialization")) self.host = host - host.bridge.addMethod("getSoftwareVersion", ".plugin", in_sign='ss', out_sign='(sss)', method=self._getVersion, async=True) + host.bridge.addMethod( + "getSoftwareVersion", + ".plugin", + in_sign="ss", + out_sign="(sss)", + method=self._getVersion, + async=True, + ) try: self.host.plugins[C.TEXT_CMDS].addWhoIsCb(self._whois, 50) except KeyError: @@ -56,7 +63,8 @@ def _getVersion(self, entity_jid_s, profile_key): def prepareForBridge(data): name, version, os = data - return (name or '', version or '', os or '') + return (name or "", version or "", os or "") + d = self.getVersion(jid.JID(entity_jid_s), profile_key) d.addCallback(prepareForBridge) return d @@ -71,25 +79,29 @@ - os: operating system of the queried entity """ client = self.host.getClient(profile_key) + def getVersion(dummy): - iq_elt = compat.IQ(client.xmlstream, 'get') - iq_elt['to'] = jid_.full() + iq_elt = compat.IQ(client.xmlstream, "get") + iq_elt["to"] = jid_.full() iq_elt.addElement("query", NS_VERSION) d = iq_elt.send() d.addCallback(self._gotVersion) return d + d = self.host.checkFeature(client, NS_VERSION, jid_) d.addCallback(getVersion) - reactor.callLater(TIMEOUT, d.cancel) # XXX: timeout needed because some clients don't answer the IQ + reactor.callLater( + TIMEOUT, d.cancel + ) # XXX: timeout needed because some clients don't answer the IQ return d def _gotVersion(self, iq_elt): try: - query_elt = iq_elt.elements(NS_VERSION, 'query').next() + query_elt = iq_elt.elements(NS_VERSION, "query").next() except StopIteration: raise exceptions.DataError ret = [] - for name in ('name', 'version', 'os'): + for name in ("name", "version", "os"): try: data_elt = query_elt.elements(NS_VERSION, name).next() ret.append(unicode(data_elt)) @@ -98,9 +110,9 @@ return tuple(ret) - def _whois(self, client, whois_msg, mess_data, target_jid): """ Add software/OS information to whois """ + def versionCb(version_data): name, version, os = version_data if name: @@ -109,9 +121,10 @@ whois_msg.append(_("Client version: %s") % version) if os: whois_msg.append(_("Operating system: %s") % os) + def versionEb(failure): failure.trap(exceptions.FeatureNotFound, defer.CancelledError) - if failure.check(failure,exceptions.FeatureNotFound): + if failure.check(failure, exceptions.FeatureNotFound): whois_msg.append(_("Software version not available")) else: whois_msg.append(_("Client software version request timeout")) @@ -119,4 +132,3 @@ d = self.getVersion(target_jid, client.profile) d.addCallbacks(versionCb, versionEb) return d -
--- a/sat/plugins/plugin_xep_0095.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0095.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from twisted.words.protocols.jabber import xmlstream @@ -37,19 +38,18 @@ C.PI_PROTOCOLS: ["XEP-0095"], C.PI_MAIN: "XEP_0095", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Stream Initiation""") + C.PI_DESCRIPTION: _("""Implementation of Stream Initiation"""), } IQ_SET = '/iq[@type="set"]' -NS_SI = 'http://jabber.org/protocol/si' +NS_SI = "http://jabber.org/protocol/si" SI_REQUEST = IQ_SET + '/si[@xmlns="' + NS_SI + '"]' SI_PROFILE_HEADER = "http://jabber.org/protocol/si/profile/" -SI_ERROR_CONDITIONS = ('bad-profile', 'no-valid-streams') +SI_ERROR_CONDITIONS = ("bad-profile", "no-valid-streams") class XEP_0095(object): - def __init__(self, host): log.info(_("Plugin XEP_0095 initialization")) self.host = host @@ -70,7 +70,11 @@ try: del self.si_profiles[si_profile] except KeyError: - log.error(u"Trying to unregister SI profile [{}] which was not registered".format(si_profile)) + log.error( + u"Trying to unregister SI profile [{}] which was not registered".format( + si_profile + ) + ) def streamInit(self, iq_elt, client): """This method is called on stream initiation (XEP-0095 #3.2) @@ -79,17 +83,21 @@ """ log.info(_("XEP-0095 Stream initiation")) iq_elt.handled = True - si_elt = iq_elt.elements(NS_SI, 'si').next() - si_id = si_elt['id'] - si_mime_type = iq_elt.getAttribute('mime-type', 'application/octet-stream') - si_profile = si_elt['profile'] - si_profile_key = si_profile[len(SI_PROFILE_HEADER):] if si_profile.startswith(SI_PROFILE_HEADER) else si_profile + si_elt = iq_elt.elements(NS_SI, "si").next() + si_id = si_elt["id"] + si_mime_type = iq_elt.getAttribute("mime-type", "application/octet-stream") + si_profile = si_elt["profile"] + si_profile_key = ( + si_profile[len(SI_PROFILE_HEADER) :] + if si_profile.startswith(SI_PROFILE_HEADER) + else si_profile + ) if si_profile_key in self.si_profiles: - #We know this SI profile, we call the callback + # We know this SI profile, we call the callback self.si_profiles[si_profile_key](client, iq_elt, si_id, si_mime_type, si_elt) else: - #We don't know this profile, we send an error - self.sendError(client, iq_elt, 'bad-profile') + # We don't know this profile, we send an error + self.sendError(client, iq_elt, "bad-profile") def sendError(self, client, request, condition): """Send IQ error as a result @@ -99,7 +107,7 @@ """ if condition in SI_ERROR_CONDITIONS: si_condition = condition - condition = 'bad-request' + condition = "bad-request" else: si_condition = None @@ -119,8 +127,8 @@ log.info(_("sending stream initiation accept answer")) if misc_elts is None: misc_elts = [] - result_elt = xmlstream.toResponse(iq_elt, 'result') - si_elt = result_elt.addElement((NS_SI, 'si')) + result_elt = xmlstream.toResponse(iq_elt, "result") + si_elt = result_elt.addElement((NS_SI, "si")) si_elt.addChild(feature_elt) for elt in misc_elts: si_elt.addChild(elt) @@ -134,8 +142,15 @@ raise exceptions.DataError return (iq_elt, si_elt) - - def proposeStream(self, client, to_jid, si_profile, feature_elt, misc_elts, mime_type='application/octet-stream'): + def proposeStream( + self, + client, + to_jid, + si_profile, + feature_elt, + misc_elts, + mime_type="application/octet-stream", + ): """Propose a stream initiation @param to_jid(jid.JID): recipient @@ -154,8 +169,8 @@ offer["from"] = client.jid.full() offer["to"] = to_jid.full() - si = offer.addElement('si', NS_SI) - si['id'] = sid + si = offer.addElement("si", NS_SI) + si["id"] = sid si["mime-type"] = mime_type si["profile"] = si_profile for elt in misc_elts: @@ -175,10 +190,17 @@ self.host = plugin_parent.host def connectionInitialized(self): - self.xmlstream.addObserver(SI_REQUEST, self.plugin_parent.streamInit, client=self.parent) + self.xmlstream.addObserver( + SI_REQUEST, self.plugin_parent.streamInit, client=self.parent + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): - return [disco.DiscoFeature(NS_SI)] + [disco.DiscoFeature(u"http://jabber.org/protocol/si/profile/{}".format(profile_name)) for profile_name in self.plugin_parent.si_profiles] + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): + return [disco.DiscoFeature(NS_SI)] + [ + disco.DiscoFeature( + u"http://jabber.org/protocol/si/profile/{}".format(profile_name) + ) + for profile_name in self.plugin_parent.si_profiles + ] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0096.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0096.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from sat.tools import xml_tools @@ -43,7 +44,7 @@ C.PI_DEPENDENCIES: ["XEP-0020", "XEP-0095", "XEP-0065", "XEP-0047", "FILE"], C.PI_MAIN: "XEP_0096", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Implementation of SI File Transfer""") + C.PI_DESCRIPTION: _("""Implementation of SI File Transfer"""), } @@ -53,13 +54,19 @@ def __init__(self, host): log.info(_("Plugin XEP_0096 initialization")) self.host = host - self.managed_stream_m = [self.host.plugins["XEP-0065"].NAMESPACE, - self.host.plugins["XEP-0047"].NAMESPACE] # Stream methods managed + self.managed_stream_m = [ + self.host.plugins["XEP-0065"].NAMESPACE, + self.host.plugins["XEP-0047"].NAMESPACE, + ] # Stream methods managed self._f = self.host.plugins["FILE"] - self._f.register(NS_SI_FT, self.sendFile, priority=0, method_name=u"Stream Initiation") + self._f.register( + NS_SI_FT, self.sendFile, priority=0, method_name=u"Stream Initiation" + ) self._si = self.host.plugins["XEP-0095"] self._si.registerSIProfile(SI_PROFILE_NAME, self._transferRequest) - host.bridge.addMethod("siSendFile", ".plugin", in_sign='sssss', out_sign='s', method=self._sendFile) + host.bridge.addMethod( + "siSendFile", ".plugin", in_sign="sssss", out_sign="s", method=self._sendFile + ) def unload(self): self._si.unregisterSIProfile(SI_PROFILE_NAME) @@ -72,7 +79,7 @@ """ if message is not None: log.warning(message) - self._si.sendError(client, iq_elt, 'bad-request') + self._si.sendError(client, iq_elt, "bad-request") def _parseRange(self, parent_elt, file_size): """find and parse <range/> element @@ -84,7 +91,7 @@ - range_length """ try: - range_elt = parent_elt.elements(NS_SI_FT, 'range').next() + range_elt = parent_elt.elements(NS_SI_FT, "range").next() except StopIteration: range_ = False range_offset = None @@ -93,17 +100,17 @@ range_ = True try: - range_offset = int(range_elt['offset']) + range_offset = int(range_elt["offset"]) except KeyError: range_offset = 0 try: - range_length = int(range_elt['length']) + range_length = int(range_elt["length"]) except KeyError: range_length = file_size if range_offset != 0 or range_length != file_size: - raise NotImplementedError # FIXME + raise NotImplementedError # FIXME return range_, range_offset, range_length @@ -116,17 +123,21 @@ @param si_elt(domish.Element): request """ log.info(_("XEP-0096 file transfer requested")) - peer_jid = jid.JID(iq_elt['from']) + peer_jid = jid.JID(iq_elt["from"]) try: file_elt = si_elt.elements(NS_SI_FT, "file").next() except StopIteration: - return self._badRequest(client, iq_elt, "No <file/> element found in SI File Transfer request") + return self._badRequest( + client, iq_elt, "No <file/> element found in SI File Transfer request" + ) try: feature_elt = self.host.plugins["XEP-0020"].getFeatureElt(si_elt) except exceptions.NotFound: - return self._badRequest(client, iq_elt, "No <feature/> element found in SI File Transfer request") + return self._badRequest( + client, iq_elt, "No <feature/> element found in SI File Transfer request" + ) try: filename = file_elt["name"] @@ -137,12 +148,16 @@ file_date = file_elt.getAttribute("date") file_hash = file_elt.getAttribute("hash") - log.info(u"File proposed: name=[{name}] size={size}".format(name=filename, size=file_size)) + log.info( + u"File proposed: name=[{name}] size={size}".format( + name=filename, size=file_size + ) + ) try: - file_desc = unicode(file_elt.elements(NS_SI_FT, 'desc').next()) + file_desc = unicode(file_elt.elements(NS_SI_FT, "desc").next()) except StopIteration: - file_desc = '' + file_desc = "" try: range_, range_offset, range_length = self._parseRange(file_elt, file_size) @@ -150,7 +165,9 @@ return self._badRequest(client, iq_elt, "Malformed SI File Transfer request") try: - stream_method = self.host.plugins["XEP-0020"].negotiate(feature_elt, 'stream-method', self.managed_stream_m, namespace=None) + stream_method = self.host.plugins["XEP-0020"].negotiate( + feature_elt, "stream-method", self.managed_stream_m, namespace=None + ) except KeyError: return self._badRequest(client, iq_elt, "No stream method found") @@ -160,16 +177,30 @@ elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE: plugin = self.host.plugins["XEP-0047"] else: - log.error(u"Unknown stream method, this should not happen at this stage, cancelling transfer") + log.error( + u"Unknown stream method, this should not happen at this stage, cancelling transfer" + ) else: log.warning(u"Can't find a valid stream method") - self._si.sendError(client, iq_elt, 'not-acceptable') + self._si.sendError(client, iq_elt, "not-acceptable") return - #if we are here, the transfer can start, we just need user's agreement - data = {"name": filename, "peer_jid": peer_jid, "size": file_size, "date": file_date, "hash": file_hash, "desc": file_desc, - "range": range_, "range_offset": range_offset, "range_length": range_length, - "si_id": si_id, "progress_id": si_id, "stream_method": stream_method, "stream_plugin": plugin} + # if we are here, the transfer can start, we just need user's agreement + data = { + "name": filename, + "peer_jid": peer_jid, + "size": file_size, + "date": file_date, + "hash": file_hash, + "desc": file_desc, + "range": range_, + "range_offset": range_offset, + "range_length": range_length, + "si_id": si_id, + "progress_id": si_id, + "stream_method": stream_method, + "stream_plugin": plugin, + } d = self._f.getDestDir(client, peer_jid, data, data, stream_object=True) d.addCallback(self.confirmationCb, client, iq_elt, data) @@ -183,7 +214,7 @@ """ if not accepted: log.info(u"File transfer declined") - self._si.sendError(client, iq_elt, 'forbidden') + self._si.sendError(client, iq_elt, "forbidden") return # data, timeout, stream_method, failed_methods = client._xep_0096_waiting_for_approval[sid] # can_range = data['can_range'] == "True" @@ -207,12 +238,16 @@ # file_obj = self._getFileObject(dest_path, can_range) # range_offset = file_obj.tell() - d = data['stream_plugin'].createSession(client, data['stream_object'], data['peer_jid'], data['si_id']) + d = data["stream_plugin"].createSession( + client, data["stream_object"], data["peer_jid"], data["si_id"] + ) d.addCallback(self._transferCb, client, data) d.addErrback(self._transferEb, client, data) - #we can send the iq result - feature_elt = self.host.plugins["XEP-0020"].chooseOption({'stream-method': data['stream_method']}, namespace=None) + # we can send the iq result + feature_elt = self.host.plugins["XEP-0020"].chooseOption( + {"stream-method": data["stream_method"]}, namespace=None + ) misc_elts = [] misc_elts.append(domish.Element((SI_PROFILE, "file"))) # if can_range: @@ -227,9 +262,9 @@ @param data: session data """ - #TODO: check hash - data['stream_object'].close() - log.info(u'Transfer {si_id} successfuly finished'.format(**data)) + # TODO: check hash + data["stream_object"].close() + log.info(u"Transfer {si_id} successfuly finished".format(**data)) def _transferEb(self, failure, client, data): """Called when something went wrong with the transfer @@ -237,12 +272,18 @@ @param id: stream id @param data: session data """ - log.warning(u'Transfer {si_id} failed: {reason}'.format(reason=unicode(failure.value), **data)) - data['stream_object'].close() + log.warning( + u"Transfer {si_id} failed: {reason}".format( + reason=unicode(failure.value), **data + ) + ) + data["stream_object"].close() def _sendFile(self, peer_jid_s, filepath, name, desc, profile=C.PROF_KEY_NONE): client = self.host.getClient(profile) - return self.sendFile(client, jid.JID(peer_jid_s), filepath, name or None, desc or None) + return self.sendFile( + client, jid.JID(peer_jid_s), filepath, name or None, desc or None + ) def sendFile(self, client, peer_jid, filepath, name=None, desc=None, extra=None): """Send a file using XEP-0096 @@ -255,23 +296,27 @@ @param extra: not used here @return: an unique id to identify the transfer """ - feature_elt = self.host.plugins["XEP-0020"].proposeFeatures({'stream-method': self.managed_stream_m}, namespace=None) + feature_elt = self.host.plugins["XEP-0020"].proposeFeatures( + {"stream-method": self.managed_stream_m}, namespace=None + ) file_transfer_elts = [] statinfo = os.stat(filepath) - file_elt = domish.Element((SI_PROFILE, 'file')) - file_elt['name'] = name or os.path.basename(filepath) - assert '/' not in file_elt['name'] + file_elt = domish.Element((SI_PROFILE, "file")) + file_elt["name"] = name or os.path.basename(filepath) + assert "/" not in file_elt["name"] size = statinfo.st_size - file_elt['size'] = str(size) + file_elt["size"] = str(size) if desc: - file_elt.addElement('desc', content=desc) + file_elt.addElement("desc", content=desc) file_transfer_elts.append(file_elt) - file_transfer_elts.append(domish.Element((None, 'range'))) + file_transfer_elts.append(domish.Element((None, "range"))) - sid, offer_d = self._si.proposeStream(client, peer_jid, SI_PROFILE, feature_elt, file_transfer_elts) + sid, offer_d = self._si.proposeStream( + client, peer_jid, SI_PROFILE, feature_elt, file_transfer_elts + ) args = [filepath, sid, size, client] offer_d.addCallbacks(self._fileCb, self._fileEb, args, None, args) return sid @@ -285,7 +330,9 @@ log.warning(u"No <feature/> element found in result while expected") return - choosed_options = self.host.plugins["XEP-0020"].getChoosedOptions(feature_elt, namespace=None) + choosed_options = self.host.plugins["XEP-0020"].getChoosedOptions( + feature_elt, namespace=None + ) try: stream_method = choosed_options["stream-method"] except KeyError: @@ -307,45 +354,46 @@ log.warning(u"Invalid stream method received") return - stream_object = stream.FileStreamObject(self.host, - client, - filepath, - uid=sid, - size=size, - ) - d = plugin.startStream(client, stream_object, jid.JID(iq_elt['from']), sid) + stream_object = stream.FileStreamObject( + self.host, client, filepath, uid=sid, size=size + ) + d = plugin.startStream(client, stream_object, jid.JID(iq_elt["from"]), sid) d.addCallback(self._sendCb, client, sid, stream_object) d.addErrback(self._sendEb, client, sid, stream_object) def _fileEb(self, failure, filepath, sid, size, client): if failure.check(error.StanzaError): stanza_err = failure.value - if stanza_err.code == '403' and stanza_err.condition == 'forbidden': - from_s = stanza_err.stanza['from'] + if stanza_err.code == "403" and stanza_err.condition == "forbidden": + from_s = stanza_err.stanza["from"] log.info(u"File transfer refused by {}".format(from_s)) msg = D_(u"The contact {} has refused your file").format(from_s) title = D_(u"File refused") xml_tools.quickNote(self.host, client, msg, title, C.XMLUI_DATA_LVL_INFO) else: log.warning(_(u"Error during file transfer")) - msg = D_(u"Something went wrong during the file transfer session initialisation: {reason}").format(reason=unicode(stanza_err)) + msg = D_( + u"Something went wrong during the file transfer session initialisation: {reason}" + ).format(reason=unicode(stanza_err)) title = D_(u"File transfer error") xml_tools.quickNote(self.host, client, msg, title, C.XMLUI_DATA_LVL_ERROR) elif failure.check(exceptions.DataError): - log.warning(u'Invalid stanza received') + log.warning(u"Invalid stanza received") else: - log.error(u'Error while proposing stream: {}'.format(failure)) + log.error(u"Error while proposing stream: {}".format(failure)) def _sendCb(self, dummy, client, sid, stream_object): - log.info(_(u'transfer {sid} successfuly finished [{profile}]').format( - sid=sid, - profile=client.profile)) + log.info( + _(u"transfer {sid} successfuly finished [{profile}]").format( + sid=sid, profile=client.profile + ) + ) stream_object.close() def _sendEb(self, failure, client, sid, stream_object): - log.warning(_(u'transfer {sid} failed [{profile}]: {reason}').format( - sid=sid, - profile=client.profile, - reason=unicode(failure.value), - )) + log.warning( + _(u"transfer {sid} failed [{profile}]: {reason}").format( + sid=sid, profile=client.profile, reason=unicode(failure.value) + ) + ) stream_object.close()
--- a/sat/plugins/plugin_xep_0100.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0100.py Wed Jun 27 20:14:46 2018 +0200 @@ -22,6 +22,7 @@ from sat.core import exceptions from sat.tools import xml_tools from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.protocols.jabber import jid from twisted.internet import reactor, defer @@ -33,37 +34,58 @@ C.PI_PROTOCOLS: ["XEP-0100"], C.PI_DEPENDENCIES: ["XEP-0077"], C.PI_MAIN: "XEP_0100", - C.PI_DESCRIPTION: _("""Implementation of Gateways protocol""") + C.PI_DESCRIPTION: _("""Implementation of Gateways protocol"""), } -WARNING_MSG = D_(u"""Be careful ! Gateways allow you to use an external IM (legacy IM), so you can see your contact as XMPP contacts. -But when you do this, all your messages go throught the external legacy IM server, it is a huge privacy issue (i.e.: all your messages throught the gateway can be monitored, recorded, analysed by the external server, most of time a private company).""") +WARNING_MSG = D_( + u"""Be careful ! Gateways allow you to use an external IM (legacy IM), so you can see your contact as XMPP contacts. +But when you do this, all your messages go throught the external legacy IM server, it is a huge privacy issue (i.e.: all your messages throught the gateway can be monitored, recorded, analysed by the external server, most of time a private company).""" +) GATEWAY_TIMEOUT = 10 # time to wait before cancelling a gateway disco info, in seconds -TYPE_DESCRIPTIONS = {'irc': D_("Internet Relay Chat"), - 'xmpp': D_("XMPP"), - 'qq': D_("Tencent QQ"), - 'simple': D_("SIP/SIMPLE"), - 'icq': D_("ICQ"), - 'yahoo': D_("Yahoo! Messenger"), - 'gadu-gadu': D_("Gadu-Gadu"), - 'aim': D_("AOL Instant Messenger"), - 'msn': D_("Windows Live Messenger"), - } +TYPE_DESCRIPTIONS = { + "irc": D_("Internet Relay Chat"), + "xmpp": D_("XMPP"), + "qq": D_("Tencent QQ"), + "simple": D_("SIP/SIMPLE"), + "icq": D_("ICQ"), + "yahoo": D_("Yahoo! Messenger"), + "gadu-gadu": D_("Gadu-Gadu"), + "aim": D_("AOL Instant Messenger"), + "msn": D_("Windows Live Messenger"), +} class XEP_0100(object): - def __init__(self, host): log.info(_("Gateways plugin initialization")) self.host = host self.__gateways = {} # dict used to construct the answer to findGateways. Key = target jid - host.bridge.addMethod("findGateways", ".plugin", in_sign='ss', out_sign='s', method=self._findGateways) - host.bridge.addMethod("gatewayRegister", ".plugin", in_sign='ss', out_sign='s', method=self._gatewayRegister) + host.bridge.addMethod( + "findGateways", + ".plugin", + in_sign="ss", + out_sign="s", + method=self._findGateways, + ) + host.bridge.addMethod( + "gatewayRegister", + ".plugin", + in_sign="ss", + out_sign="s", + method=self._gatewayRegister, + ) self.__menu_id = host.registerCallback(self._gatewaysMenu, with_data=True) - self.__selected_id = host.registerCallback(self._gatewaySelectedCb, with_data=True) - host.importMenu((D_("Service"), D_("Gateways")), self._gatewaysMenu, security_limit=1, help_string=D_("Find gateways")) + self.__selected_id = host.registerCallback( + self._gatewaySelectedCb, with_data=True + ) + host.importMenu( + (D_("Service"), D_("Gateways")), + self._gatewaysMenu, + security_limit=1, + help_string=D_("Find gateways"), + ) def _gatewaysMenu(self, data, profile): """ XMLUI activated by menu: return Gateways UI @@ -72,24 +94,31 @@ """ client = self.host.getClient(profile) try: - jid_ = jid.JID(data.get(xml_tools.formEscape('external_jid'), client.jid.host)) + jid_ = jid.JID( + data.get(xml_tools.formEscape("external_jid"), client.jid.host) + ) except RuntimeError: raise exceptions.DataError(_("Invalid JID")) d = self.findGateways(jid_, profile) d.addCallback(self._gatewaysResult2XMLUI, jid_) - d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()}) + d.addCallback(lambda xmlui: {"xmlui": xmlui.toXml()}) return d def _gatewaysResult2XMLUI(self, result, entity): - xmlui = xml_tools.XMLUI(title=_('Gateways manager (%s)') % entity.full()) + xmlui = xml_tools.XMLUI(title=_("Gateways manager (%s)") % entity.full()) xmlui.addText(_(WARNING_MSG)) - xmlui.addDivider('dash') - adv_list = xmlui.changeContainer('advanced_list', columns=3, selectable='single', callback_id=self.__selected_id) + xmlui.addDivider("dash") + adv_list = xmlui.changeContainer( + "advanced_list", + columns=3, + selectable="single", + callback_id=self.__selected_id, + ) for success, gateway_data in result: if not success: fail_cond, disco_item = gateway_data xmlui.addJid(disco_item.entity) - xmlui.addText(_('Failed (%s)') % fail_cond) + xmlui.addText(_("Failed (%s)") % fail_cond) xmlui.addEmpty() else: jid_, data = gateway_data @@ -100,22 +129,22 @@ xmlui.addText(name) xmlui.addText(self._getIdentityDesc(identity)) adv_list.end() - xmlui.addDivider('blank') - xmlui.changeContainer('advanced_list', columns=3) - xmlui.addLabel(_('Use external XMPP server')) - xmlui.addString('external_jid') - xmlui.addButton(self.__menu_id, _(u'Go !'), fields_back=('external_jid',)) + xmlui.addDivider("blank") + xmlui.changeContainer("advanced_list", columns=3) + xmlui.addLabel(_("Use external XMPP server")) + xmlui.addString("external_jid") + xmlui.addButton(self.__menu_id, _(u"Go !"), fields_back=("external_jid",)) return xmlui def _gatewaySelectedCb(self, data, profile): try: - target_jid = jid.JID(data['index']) + target_jid = jid.JID(data["index"]) except (KeyError, RuntimeError): log.warning(_("No gateway index selected")) return {} d = self.gatewayRegister(target_jid, profile) - d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()}) + d.addCallback(lambda xmlui: {"xmlui": xmlui.toXml()}) return d def _getIdentityDesc(self, identity): @@ -124,8 +153,13 @@ """ category, type_ = identity - if category != 'gateway': - log.error(_(u'INTERNAL ERROR: identity category should always be "gateway" in _getTypeString, got "%s"') % category) + if category != "gateway": + log.error( + _( + u'INTERNAL ERROR: identity category should always be "gateway" in _getTypeString, got "%s"' + ) + % category + ) try: return _(TYPE_DESCRIPTIONS[type_]) except KeyError: @@ -145,8 +179,10 @@ def gatewayRegister(self, target_jid, profile_key=C.PROF_KEY_NONE): """Register gateway using in-band registration, then log-in to gateway""" profile = self.host.memory.getProfileName(profile_key) - assert(profile) - d = self.host.plugins["XEP-0077"].inBandRegister(target_jid, self._registrationSuccessful, profile) + assert profile + d = self.host.plugins["XEP-0077"].inBandRegister( + target_jid, self._registrationSuccessful, profile + ) return d def _infosReceived(self, dl_result, items, target, client): @@ -165,12 +201,27 @@ ret.append((success, (msg, items[idx]))) else: entity = items[idx].entity - gateways = [(identity, result.identities[identity]) for identity in result.identities if identity[0] == 'gateway'] + gateways = [ + (identity, result.identities[identity]) + for identity in result.identities + if identity[0] == "gateway" + ] if gateways: - log.info(_(u"Found gateway [%(jid)s]: %(identity_name)s") % {'jid': entity.full(), 'identity_name': ' - '.join([gateway[1] for gateway in gateways])}) + log.info( + _(u"Found gateway [%(jid)s]: %(identity_name)s") + % { + "jid": entity.full(), + "identity_name": " - ".join( + [gateway[1] for gateway in gateways] + ), + } + ) ret.append((success, (entity, gateways))) else: - log.info(_(u"Skipping [%(jid)s] which is not a gateway") % {'jid': entity.full()}) + log.info( + _(u"Skipping [%(jid)s] which is not a gateway") + % {"jid": entity.full()} + ) return ret def _itemsReceived(self, disco, target, client): @@ -185,7 +236,9 @@ log.debug(_(u"item found: %s") % item.entity) _defers.append(client.disco.requestInfo(item.entity)) dl = defer.DeferredList(_defers) - dl.addCallback(self._infosReceived, items=disco._items, target=target, client=client) + dl.addCallback( + self._infosReceived, items=disco._items, target=target, client=client + ) reactor.callLater(GATEWAY_TIMEOUT, dl.cancel) return dl @@ -203,7 +256,10 @@ """Find gateways in the target JID, using discovery protocol """ client = self.host.getClient(profile) - log.debug(_(u"find gateways (target = %(target)s, profile = %(profile)s)") % {'target': target.full(), 'profile': profile}) + log.debug( + _(u"find gateways (target = %(target)s, profile = %(profile)s)") + % {"target": target.full(), "profile": profile} + ) d = client.disco.requestItems(target) d.addCallback(self._itemsReceived, target=target, client=client) return d
--- a/sat/plugins/plugin_xep_0115.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0115.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.xish import domish from twisted.words.protocols.jabber import jid @@ -32,9 +33,9 @@ except ImportError: from wokkel.subprotocols import XMPPHandler -PRESENCE = '/presence' -NS_ENTITY_CAPABILITY = 'http://jabber.org/protocol/caps' -NS_CAPS_OPTIMIZE = 'http://jabber.org/protocol/caps#optimize' +PRESENCE = "/presence" +NS_ENTITY_CAPABILITY = "http://jabber.org/protocol/caps" +NS_CAPS_OPTIMIZE = "http://jabber.org/protocol/caps#optimize" CAPABILITY_UPDATE = PRESENCE + '/c[@xmlns="' + NS_ENTITY_CAPABILITY + '"]' PLUGIN_INFO = { @@ -46,7 +47,7 @@ C.PI_DEPENDENCIES: [], C.PI_MAIN: "XEP_0115", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of entity capabilities""") + C.PI_DESCRIPTION: _("""Implementation of entity capabilities"""), } @@ -75,24 +76,29 @@ log.warning(_(u"Caps optimisation not available")) # hash generation - _infos = yield client.discoHandler.info(client.jid, client.jid, '') + _infos = yield client.discoHandler.info(client.jid, client.jid, "") disco_infos = disco.DiscoInfo() for item in _infos: disco_infos.append(item) cap_hash = client._caps_hash = self.host.memory.disco.generateHash(disco_infos) - log.info(u"Our capability hash has been generated: [{cap_hash}]".format( - cap_hash = cap_hash)) + log.info( + u"Our capability hash has been generated: [{cap_hash}]".format( + cap_hash=cap_hash + ) + ) log.debug(u"Generating capability domish.Element") - c_elt = domish.Element((NS_ENTITY_CAPABILITY, 'c')) - c_elt['hash'] = 'sha-1' - c_elt['node'] = C.APP_URL - c_elt['ver'] = cap_hash + c_elt = domish.Element((NS_ENTITY_CAPABILITY, "c")) + c_elt["hash"] = "sha-1" + c_elt["node"] = C.APP_URL + c_elt["ver"] = cap_hash client._caps_elt = c_elt if client._caps_optimize: client._caps_sent = False if cap_hash not in self.host.memory.disco.hashes: self.host.memory.disco.hashes[cap_hash] = disco_infos - self.host.memory.updateEntityData(client.jid, C.ENTITY_CAP_HASH, cap_hash, profile_key=client.profile) + self.host.memory.updateEntityData( + client.jid, C.ENTITY_CAP_HASH, cap_hash, profile_key=client.profile + ) def _presenceAddElt(self, client, obj): if client._caps_optimize: @@ -120,10 +126,13 @@ def connectionInitialized(self): self.xmlstream.addObserver(CAPABILITY_UPDATE, self.update) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): - return [disco.DiscoFeature(NS_ENTITY_CAPABILITY), disco.DiscoFeature(NS_CAPS_OPTIMIZE)] + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): + return [ + disco.DiscoFeature(NS_ENTITY_CAPABILITY), + disco.DiscoFeature(NS_CAPS_OPTIMIZE), + ] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] def update(self, presence): @@ -132,35 +141,65 @@ Check if we know the version of this capabilities and get the capabilities if necessary """ - from_jid = jid.JID(presence['from']) - c_elem = presence.elements(NS_ENTITY_CAPABILITY, 'c').next() + from_jid = jid.JID(presence["from"]) + c_elem = presence.elements(NS_ENTITY_CAPABILITY, "c").next() try: - c_ver = c_elem['ver'] - c_hash = c_elem['hash'] - c_node = c_elem['node'] + c_ver = c_elem["ver"] + c_hash = c_elem["hash"] + c_node = c_elem["node"] except KeyError: - log.warning(_(u'Received invalid capabilities tag: %s') % c_elem.toXml()) + log.warning(_(u"Received invalid capabilities tag: %s") % c_elem.toXml()) return if c_ver in self.host.memory.disco.hashes: # we already know the hash, we update the jid entity - log.debug(u"hash [%(hash)s] already in cache, updating entity [%(jid)s]" % {'hash': c_ver, 'jid': from_jid.full()}) - self.host.memory.updateEntityData(from_jid, C.ENTITY_CAP_HASH, c_ver, profile_key=self.profile) + log.debug( + u"hash [%(hash)s] already in cache, updating entity [%(jid)s]" + % {"hash": c_ver, "jid": from_jid.full()} + ) + self.host.memory.updateEntityData( + from_jid, C.ENTITY_CAP_HASH, c_ver, profile_key=self.profile + ) return - if c_hash != 'sha-1': # unknown hash method - log.warning(_(u'Unknown hash method for entity capabilities: [%(hash_method)s] (entity: %(jid)s, node: %(node)s)') % {'hash_method':c_hash, 'jid': from_jid, 'node': c_node}) + if c_hash != "sha-1": # unknown hash method + log.warning( + _( + u"Unknown hash method for entity capabilities: [%(hash_method)s] (entity: %(jid)s, node: %(node)s)" + ) + % {"hash_method": c_hash, "jid": from_jid, "node": c_node} + ) def cb(dummy): - computed_hash = self.host.memory.getEntityDatum(from_jid, C.ENTITY_CAP_HASH, self.profile) + computed_hash = self.host.memory.getEntityDatum( + from_jid, C.ENTITY_CAP_HASH, self.profile + ) if computed_hash != c_ver: - log.warning(_(u'Computed hash differ from given hash:\ngiven: [%(given_hash)s]\ncomputed: [%(computed_hash)s]\n(entity: %(jid)s, node: %(node)s)') % {'given_hash':c_ver, 'computed_hash': computed_hash, 'jid': from_jid, 'node': c_node}) + log.warning( + _( + u"Computed hash differ from given hash:\ngiven: [%(given_hash)s]\ncomputed: [%(computed_hash)s]\n(entity: %(jid)s, node: %(node)s)" + ) + % { + "given_hash": c_ver, + "computed_hash": computed_hash, + "jid": from_jid, + "node": c_node, + } + ) def eb(failure): if isinstance(failure.value, error.ConnectionDone): return - msg = failure.value.condition if hasattr(failure.value, 'condition') else failure.getErrorMessage() - log.error(_(u"Couldn't retrieve disco info for {jid}: {error}").format(jid=from_jid.full(), error=msg)) + msg = ( + failure.value.condition + if hasattr(failure.value, "condition") + else failure.getErrorMessage() + ) + log.error( + _(u"Couldn't retrieve disco info for {jid}: {error}").format( + jid=from_jid.full(), error=msg + ) + ) d = self.host.getDiscoInfos(self.parent, from_jid) d.addCallbacks(cb, eb)
--- a/sat/plugins/plugin_xep_0163.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0163.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,13 +21,14 @@ from sat.core import exceptions from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.xish import domish from wokkel import disco, pubsub from wokkel.formats import Mood -NS_USER_MOOD = 'http://jabber.org/protocol/mood' +NS_USER_MOOD = "http://jabber.org/protocol/mood" PLUGIN_INFO = { C.PI_NAME: "Personal Eventing Protocol Plugin", @@ -37,19 +38,25 @@ C.PI_DEPENDENCIES: ["XEP-0060"], C.PI_MAIN: "XEP_0163", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Implementation of Personal Eventing Protocol""") + C.PI_DESCRIPTION: _("""Implementation of Personal Eventing Protocol"""), } class XEP_0163(object): - def __init__(self, host): log.info(_("PEP plugin initialization")) self.host = host self.pep_events = set() self.pep_out_cb = {} host.trigger.add("PubSub Disco Info", self.disoInfoTrigger) - host.bridge.addMethod("PEPSend", ".plugin", in_sign='sa{ss}s', out_sign='', method=self.PEPSend, async=True) # args: type(MOOD, TUNE, etc), data, profile_key; + host.bridge.addMethod( + "PEPSend", + ".plugin", + in_sign="sa{ss}s", + out_sign="", + method=self.PEPSend, + async=True, + ) # args: type(MOOD, TUNE, etc), data, profile_key; self.addPEPEvent("MOOD", NS_USER_MOOD, self.userMoodCB, self.sendMood) def disoInfoTrigger(self, disco_info, profile): @@ -78,13 +85,18 @@ self.pep_events.add(node) if notify: self.pep_events.add(node + "+notify") + def filterPEPEvent(client, itemsEvent): """Ignore messages which are not coming from PEP (i.e. main server) @param itemsEvent(pubsub.ItemsEvent): pubsub event """ if itemsEvent.sender.user or itemsEvent.sender.resource: - log.debug("ignoring non PEP event from {} (profile={})".format(itemsEvent.sender.full(), client.profile)) + log.debug( + "ignoring non PEP event from {} (profile={})".format( + itemsEvent.sender.full(), client.profile + ) + ) return in_callback(itemsEvent, client.profile) @@ -110,11 +122,14 @@ """ profile = self.host.memory.getProfileName(profile_key) if not profile: - log.error(_(u'Trying to send personal event with an unknown profile key [%s]') % profile_key) + log.error( + _(u"Trying to send personal event with an unknown profile key [%s]") + % profile_key + ) raise exceptions.ProfileUnknownError if not event_type in self.pep_out_cb.keys(): - log.error(_('Trying to send personal event for an unknown type')) - raise exceptions.DataError('Type unknown') + log.error(_("Trying to send personal event for an unknown type")) + raise exceptions.DataError("Type unknown") return self.pep_out_cb[event_type](data, profile) def userMoodCB(self, itemsEvent, profile): @@ -122,7 +137,9 @@ log.debug(_("No item found")) return try: - mood_elt = [child for child in itemsEvent.items[0].elements() if child.name == "mood"][0] + mood_elt = [ + child for child in itemsEvent.items[0].elements() if child.name == "mood" + ][0] except IndexError: log.error(_("Can't find mood element in mood event")) return @@ -130,8 +147,14 @@ if not mood: log.debug(_("No mood found")) return - self.host.bridge.psEvent(C.PS_PEP, itemsEvent.sender.full(), itemsEvent.nodeIdentifier, - "MOOD", {"mood": mood.value or "", "text": mood.text or ""}, profile) + self.host.bridge.psEvent( + C.PS_PEP, + itemsEvent.sender.full(), + itemsEvent.nodeIdentifier, + "MOOD", + {"mood": mood.value or "", "text": mood.text or ""}, + profile, + ) def sendMood(self, data, profile): """Send XEP-0107's User Mood @@ -139,8 +162,8 @@ @param data: must include mood and text @param profile: profile which send the mood""" try: - value = data['mood'].lower() - text = data['text'] if 'text' in data else '' + value = data["mood"].lower() + text = data["text"] if "text" in data else "" except KeyError: raise exceptions.DataError("Mood data must contain at least 'mood' key") mood = UserMood(value, text) @@ -152,7 +175,7 @@ def __init__(self, value, text=None): Mood.__init__(self, value, text) - domish.Element.__init__(self, (NS_USER_MOOD, 'mood')) + domish.Element.__init__(self, (NS_USER_MOOD, "mood")) self.addElement(value) if text: - self.addElement('text', content=text) + self.addElement("text", content=text)
--- a/sat/plugins/plugin_xep_0166.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0166.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core.constants import Const as C from sat.core.log import getLogger from sat.tools import xml_tools + log = getLogger(__name__) from sat.core import exceptions from twisted.words.protocols.jabber import jid @@ -37,7 +38,6 @@ from zope.interface import implements - IQ_SET = '/iq[@type="set"]' NS_JINGLE = "urn:xmpp:jingle:1" NS_JINGLE_ERROR = "urn:xmpp:jingle:errors:1" @@ -55,24 +55,24 @@ C.PI_PROTOCOLS: ["XEP-0166"], C.PI_MAIN: "XEP_0166", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Jingle""") + C.PI_DESCRIPTION: _("""Implementation of Jingle"""), } -ApplicationData = namedtuple('ApplicationData', ('namespace', 'handler')) -TransportData = namedtuple('TransportData', ('namespace', 'handler', 'priority')) +ApplicationData = namedtuple("ApplicationData", ("namespace", "handler")) +TransportData = namedtuple("TransportData", ("namespace", "handler", "priority")) class XEP_0166(object): ROLE_INITIATOR = "initiator" ROLE_RESPONDER = "responder" - TRANSPORT_DATAGRAM='UDP' - TRANSPORT_STREAMING='TCP' - REASON_SUCCESS='success' - REASON_DECLINE='decline' - REASON_FAILED_APPLICATION='failed-application' - REASON_FAILED_TRANSPORT='failed-transport' - REASON_CONNECTIVITY_ERROR='connectivity-error' + TRANSPORT_DATAGRAM = "UDP" + TRANSPORT_STREAMING = "TCP" + REASON_SUCCESS = "success" + REASON_DECLINE = "decline" + REASON_FAILED_APPLICATION = "failed-application" + REASON_FAILED_TRANSPORT = "failed-transport" + REASON_CONNECTIVITY_ERROR = "connectivity-error" A_SESSION_INITIATE = "session-initiate" A_SESSION_ACCEPT = "session-accept" A_SESSION_TERMINATE = "session-terminate" @@ -82,21 +82,26 @@ A_TRANSPORT_REJECT = "transport-reject" A_TRANSPORT_INFO = "transport-info" # non standard actions - A_PREPARE_INITIATOR = "prepare-initiator" # initiator must prepare tranfer - A_PREPARE_RESPONDER = "prepare-responder" # responder must prepare tranfer - A_ACCEPTED_ACK = "accepted-ack" # session accepted ack has been received from initiator - A_START = "start" # application can start - A_DESTROY = "destroy" # called when a transport is destroyed (e.g. because it is remplaced). Used to do cleaning operations + A_PREPARE_INITIATOR = "prepare-initiator" # initiator must prepare tranfer + A_PREPARE_RESPONDER = "prepare-responder" # responder must prepare tranfer + A_ACCEPTED_ACK = ( + "accepted-ack" + ) # session accepted ack has been received from initiator + A_START = "start" # application can start + A_DESTROY = ( + "destroy" + ) # called when a transport is destroyed (e.g. because it is remplaced). Used to do cleaning operations def __init__(self, host): log.info(_("plugin Jingle initialization")) self.host = host - self._applications = {} # key: namespace, value: application data - self._transports = {} # key: namespace, value: transport data + self._applications = {} # key: namespace, value: application data + self._transports = {} # key: namespace, value: transport data # we also keep transports by type, they are then sorted by priority - self._type_transports = { XEP_0166.TRANSPORT_DATAGRAM: [], - XEP_0166.TRANSPORT_STREAMING: [], - } + self._type_transports = { + XEP_0166.TRANSPORT_DATAGRAM: [], + XEP_0166.TRANSPORT_STREAMING: [], + } def profileConnected(self, client): client.jingle_sessions = {} # key = sid, value = session_data @@ -115,12 +120,12 @@ ## helpers methods to build stanzas ## def _buildJingleElt(self, client, session, action): - iq_elt = client.IQ('set') - iq_elt['from'] = client.jid.full() - iq_elt['to'] = session['peer_jid'].full() + iq_elt = client.IQ("set") + iq_elt["from"] = client.jid.full() + iq_elt["to"] = session["peer_jid"].full() jingle_elt = iq_elt.addElement("jingle", NS_JINGLE) - jingle_elt["sid"] = session['id'] - jingle_elt['action'] = action + jingle_elt["sid"] = session["id"] + jingle_elt["action"] = action return iq_elt, jingle_elt def sendError(self, client, error_condition, sid, request, jingle_condition=None): @@ -134,9 +139,13 @@ iq_elt = error.StanzaError(error_condition).toResponse(request) if jingle_condition is not None: iq_elt.error.addElement((NS_JINGLE_ERROR, jingle_condition)) - if error.STANZA_CONDITIONS[error_condition]['type'] == 'cancel' and sid: + if error.STANZA_CONDITIONS[error_condition]["type"] == "cancel" and sid: self._delSession(client, sid) - log.warning(u"Error while managing jingle session, cancelling: {condition}".format(error_condition)) + log.warning( + u"Error while managing jingle session, cancelling: {condition}".format( + error_condition + ) + ) client.send(iq_elt) def _terminateEb(self, failure_): @@ -150,14 +159,16 @@ if a list of element, add them as children of the <reason/> element @param session(dict): data of the session """ - iq_elt, jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_SESSION_TERMINATE) - reason_elt = jingle_elt.addElement('reason') + iq_elt, jingle_elt = self._buildJingleElt( + client, session, XEP_0166.A_SESSION_TERMINATE + ) + reason_elt = jingle_elt.addElement("reason") if isinstance(reason, basestring): reason_elt.addElement(reason) else: for elt in reason: reason_elt.addChild(elt) - self._delSession(client, session['id']) + self._delSession(client, session["id"]) d = iq_elt.send() d.addErrback(self._terminateEb) return d @@ -170,7 +181,11 @@ @param failure_(failure.Failure): the exceptions raised @param sid(unicode): jingle session id """ - log.warning(u"Error while sending jingle <iq/> stanza: {failure_}".format(failure_=failure_.value)) + log.warning( + u"Error while sending jingle <iq/> stanza: {failure_}".format( + failure_=failure_.value + ) + ) self._delSession(client, sid) def _jingleErrorCb(self, fail, sid, request, client): @@ -185,7 +200,7 @@ """ log.warning("Error while processing jingle request") if isinstance(fail, exceptions.DataError): - self.sendError(client, 'bad-request', sid, request) + self.sendError(client, "bad-request", sid, request) else: log.error("Unmanaged jingle exception") self._delSession(client, sid) @@ -211,8 +226,12 @@ May be used to clean session """ if namespace in self._applications: - raise exceptions.ConflictError(u"Trying to register already registered namespace {}".format(namespace)) - self._applications[namespace] = ApplicationData(namespace=namespace, handler=handler) + raise exceptions.ConflictError( + u"Trying to register already registered namespace {}".format(namespace) + ) + self._applications[namespace] = ApplicationData( + namespace=namespace, handler=handler + ) log.debug(u"new jingle application registered") def registerTransport(self, namespace, transport_type, handler, priority=0): @@ -227,12 +246,21 @@ called on several action to negociate the application or transport @param priority(int): priority of this transport """ - assert transport_type in (XEP_0166.TRANSPORT_DATAGRAM, XEP_0166.TRANSPORT_STREAMING) + assert transport_type in ( + XEP_0166.TRANSPORT_DATAGRAM, + XEP_0166.TRANSPORT_STREAMING, + ) if namespace in self._transports: - raise exceptions.ConflictError(u"Trying to register already registered namespace {}".format(namespace)) - transport_data = TransportData(namespace=namespace, handler=handler, priority=priority) + raise exceptions.ConflictError( + u"Trying to register already registered namespace {}".format(namespace) + ) + transport_data = TransportData( + namespace=namespace, handler=handler, priority=priority + ) self._type_transports[transport_type].append(transport_data) - self._type_transports[transport_type].sort(key=lambda transport_data: transport_data.priority, reverse=True) + self._type_transports[transport_type].sort( + key=lambda transport_data: transport_data.priority, reverse=True + ) self._transports[namespace] = transport_data log.debug(u"new jingle transport registered") @@ -247,20 +275,24 @@ # XXX: for now we replace the transport before receiving confirmation from other peer # this is acceptable because we terminate the session if transport is rejected. # this behavious may change in the future. - content_data= session['contents'][content_name] - transport_data = content_data['transport_data'] + content_data = session["contents"][content_name] + transport_data = content_data["transport_data"] try: transport = self._transports[transport_ns] except KeyError: raise exceptions.InternalError(u"Unkown transport") - yield content_data['transport'].handler.jingleHandler(client, XEP_0166.A_DESTROY, session, content_name, None) - content_data['transport'] = transport + yield content_data["transport"].handler.jingleHandler( + client, XEP_0166.A_DESTROY, session, content_name, None + ) + content_data["transport"] = transport transport_data.clear() - iq_elt, jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_TRANSPORT_REPLACE) - content_elt = jingle_elt.addElement('content') - content_elt['name'] = content_name - content_elt['creator'] = content_data['creator'] + iq_elt, jingle_elt = self._buildJingleElt( + client, session, XEP_0166.A_TRANSPORT_REPLACE + ) + content_elt = jingle_elt.addElement("content") + content_elt["name"] = content_name + content_elt["creator"] = content_data["creator"] transport_elt = transport.handler.jingleSessionInit(client, session, content_name) content_elt.addChild(transport_elt) @@ -279,14 +311,16 @@ # we first build iq, jingle and content element which are the same in every cases iq_elt, jingle_elt = self._buildJingleElt(client, session, action) # FIXME: XEP-0260 § 2.3 Ex 5 has an initiator attribute, but it should not according to XEP-0166 §7.1 table 1, must be checked - content_data= session['contents'][content_name] - content_elt = jingle_elt.addElement('content') - content_elt['name'] = content_name - content_elt['creator'] = content_data['creator'] + content_data = session["contents"][content_name] + content_elt = jingle_elt.addElement("content") + content_elt["name"] = content_name + content_elt["creator"] = content_data["creator"] if action == XEP_0166.A_TRANSPORT_INFO: - context_elt = transport_elt = content_elt.addElement('transport', content_data['transport'].namespace) - transport_elt['sid'] = content_data['transport_data']['sid'] + context_elt = transport_elt = content_elt.addElement( + "transport", content_data["transport"].namespace + ) + transport_elt["sid"] = content_data["transport_data"]["sid"] else: raise exceptions.InternalError(u"unmanaged action {}".format(action)) @@ -298,7 +332,7 @@ @param session(dict): jingle session data @return (tuple[domish.Element, domish.Element]): parent <iq> element, <jingle> element """ - return self._buildJingleElt(client, session, XEP_0166.A_SESSION_INFO) + return self._buildJingleElt(client, session, XEP_0166.A_SESSION_INFO) @defer.inlineCallbacks def initiate(self, client, peer_jid, contents): @@ -318,74 +352,88 @@ - app_kwargs(dict): keyword args to pass to the application plugin @return D(unicode): jingle session id """ - assert contents # there must be at least one content + assert contents # there must be at least one content if peer_jid == client.jid: raise ValueError(_(u"You can't do a jingle session with yourself")) initiator = client.jid sid = unicode(uuid.uuid4()) # TODO: session cleaning after timeout ? - session = client.jingle_sessions[sid] = {'id': sid, - 'state': STATE_PENDING, - 'initiator': initiator, - 'role': XEP_0166.ROLE_INITIATOR, - 'peer_jid': peer_jid, - 'started': time.time(), - 'contents': {} - } - iq_elt, jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_SESSION_INITIATE) + session = client.jingle_sessions[sid] = { + "id": sid, + "state": STATE_PENDING, + "initiator": initiator, + "role": XEP_0166.ROLE_INITIATOR, + "peer_jid": peer_jid, + "started": time.time(), + "contents": {}, + } + iq_elt, jingle_elt = self._buildJingleElt( + client, session, XEP_0166.A_SESSION_INITIATE + ) jingle_elt["initiator"] = initiator.full() - contents_dict = session['contents'] + contents_dict = session["contents"] for content in contents: # we get the application plugin - app_ns = content['app_ns'] + app_ns = content["app_ns"] try: application = self._applications[app_ns] except KeyError: - raise exceptions.InternalError(u"No application registered for {}".format(app_ns)) + raise exceptions.InternalError( + u"No application registered for {}".format(app_ns) + ) # and the transport plugin - transport_type = content.get('transport_type', XEP_0166.TRANSPORT_STREAMING) + transport_type = content.get("transport_type", XEP_0166.TRANSPORT_STREAMING) try: transport = self._type_transports[transport_type][0] except IndexError: - raise exceptions.InternalError(u"No transport registered for {}".format(transport_type)) + raise exceptions.InternalError( + u"No transport registered for {}".format(transport_type) + ) # we build the session data - content_data = {'application': application, - 'application_data': {}, - 'transport': transport, - 'transport_data': {}, - 'creator': XEP_0166.ROLE_INITIATOR, - 'senders': content.get('senders', 'both'), - } + content_data = { + "application": application, + "application_data": {}, + "transport": transport, + "transport_data": {}, + "creator": XEP_0166.ROLE_INITIATOR, + "senders": content.get("senders", "both"), + } try: - content_name = content['name'] + content_name = content["name"] except KeyError: content_name = unicode(uuid.uuid4()) else: if content_name in contents_dict: - raise exceptions.InternalError('There is already a content with this name') + raise exceptions.InternalError( + "There is already a content with this name" + ) contents_dict[content_name] = content_data # we construct the content element - content_elt = jingle_elt.addElement('content') - content_elt['creator'] = content_data['creator'] - content_elt['name'] = content_name + content_elt = jingle_elt.addElement("content") + content_elt["creator"] = content_data["creator"] + content_elt["name"] = content_name try: - content_elt['senders'] = content['senders'] + content_elt["senders"] = content["senders"] except KeyError: pass # then the description element - app_args = content.get('app_args', []) - app_kwargs = content.get('app_kwargs', {}) - desc_elt = yield application.handler.jingleSessionInit(client, session, content_name, *app_args, **app_kwargs) + app_args = content.get("app_args", []) + app_kwargs = content.get("app_kwargs", {}) + desc_elt = yield application.handler.jingleSessionInit( + client, session, content_name, *app_args, **app_kwargs + ) content_elt.addChild(desc_elt) # and the transport one - transport_elt = yield transport.handler.jingleSessionInit(client, session, content_name) + transport_elt = yield transport.handler.jingleSessionInit( + client, session, content_name + ) content_elt.addChild(transport_elt) try: @@ -410,17 +458,24 @@ @param content_name(unicode): name of the content terminated @param reason(unicode): reason of the termination """ - contents = session['contents'] + contents = session["contents"] del contents[content_name] if not contents: self.terminate(client, reason, session) ## defaults methods called when plugin doesn't have them ## - def jingleRequestConfirmationDefault(self, client, action, session, content_name, desc_elt): + def jingleRequestConfirmationDefault( + self, client, action, session, content_name, desc_elt + ): """This method request confirmation for a jingle session""" log.debug(u"Using generic jingle confirmation method") - return xml_tools.deferConfirm(self.host, _(CONFIRM_TXT).format(entity=session['peer_jid'].full()), _('Confirm Jingle session'), profile=client.profile) + return xml_tools.deferConfirm( + self.host, + _(CONFIRM_TXT).format(entity=session["peer_jid"].full()), + _("Confirm Jingle session"), + profile=client.profile, + ) ## jingle events ## @@ -432,29 +487,29 @@ @param request(domish.Element): received IQ request """ request.handled = True - jingle_elt = request.elements(NS_JINGLE, 'jingle').next() + jingle_elt = request.elements(NS_JINGLE, "jingle").next() # first we need the session id try: - sid = jingle_elt['sid'] + sid = jingle_elt["sid"] if not sid: raise KeyError except KeyError: log.warning(u"Received jingle request has no sid attribute") - self.sendError(client, 'bad-request', None, request) + self.sendError(client, "bad-request", None, request) return # then the action try: - action = jingle_elt['action'] + action = jingle_elt["action"] if not action: raise KeyError except KeyError: log.warning(u"Received jingle request has no action") - self.sendError(client, 'bad-request', None, request) + self.sendError(client, "bad-request", None, request) return - peer_jid = jid.JID(request['from']) + peer_jid = jid.JID(request["from"]) # we get or create the session try: @@ -463,32 +518,41 @@ if action == XEP_0166.A_SESSION_INITIATE: pass elif action == XEP_0166.A_SESSION_TERMINATE: - log.debug(u"ignoring session terminate action (inexisting session id): {request_id} [{profile}]".format( - request_id=sid, - profile = client.profile)) + log.debug( + u"ignoring session terminate action (inexisting session id): {request_id} [{profile}]".format( + request_id=sid, profile=client.profile + ) + ) return else: - log.warning(u"Received request for an unknown session id: {request_id} [{profile}]".format( - request_id=sid, - profile = client.profile)) - self.sendError(client, 'item-not-found', None, request, 'unknown-session') + log.warning( + u"Received request for an unknown session id: {request_id} [{profile}]".format( + request_id=sid, profile=client.profile + ) + ) + self.sendError(client, "item-not-found", None, request, "unknown-session") return - session = client.jingle_sessions[sid] = {'id': sid, - 'state': STATE_PENDING, - 'initiator': peer_jid, - 'role': XEP_0166.ROLE_RESPONDER, - 'peer_jid': peer_jid, - 'started': time.time(), - } + session = client.jingle_sessions[sid] = { + "id": sid, + "state": STATE_PENDING, + "initiator": peer_jid, + "role": XEP_0166.ROLE_RESPONDER, + "peer_jid": peer_jid, + "started": time.time(), + } else: - if session['peer_jid'] != peer_jid: - log.warning(u"sid conflict ({}), the jid doesn't match. Can be a collision, a hack attempt, or a bad sid generation".format(sid)) - self.sendError(client, 'service-unavailable', sid, request) + if session["peer_jid"] != peer_jid: + log.warning( + u"sid conflict ({}), the jid doesn't match. Can be a collision, a hack attempt, or a bad sid generation".format( + sid + ) + ) + self.sendError(client, "service-unavailable", sid, request) return - if session['id'] != sid: + if session["id"] != sid: log.error(u"session id doesn't match") - self.sendError(client, 'service-unavailable', sid, request) + self.sendError(client, "service-unavailable", sid, request) raise exceptions.InternalError if action == XEP_0166.A_SESSION_INITIATE: @@ -512,7 +576,17 @@ ## Actions callbacks ## - def _parseElements(self, jingle_elt, session, request, client, new=False, creator=ROLE_INITIATOR, with_application=True, with_transport=True): + def _parseElements( + self, + jingle_elt, + session, + request, + client, + new=False, + creator=ROLE_INITIATOR, + with_application=True, + with_transport=True, + ): """Parse contents elements and fill contents_dict accordingly after the parsing, contents_dict will containt handlers, "desc_elt" and "transport_elt" @@ -527,85 +601,98 @@ @param with_transport(bool): if True, raise an error if there is no <transport> element else ignore it @raise exceptions.CancelError: the error is treated and the calling method can cancel the treatment (i.e. return) """ - contents_dict = session['contents'] - content_elts = jingle_elt.elements(NS_JINGLE, 'content') + contents_dict = session["contents"] + content_elts = jingle_elt.elements(NS_JINGLE, "content") for content_elt in content_elts: - name = content_elt['name'] + name = content_elt["name"] if new: # the content must not exist, we check it if not name or name in contents_dict: - self.sendError(client, 'bad-request', session['id'], request) + self.sendError(client, "bad-request", session["id"], request) raise exceptions.CancelError - content_data = contents_dict[name] = {'creator': creator, - 'senders': content_elt.attributes.get('senders', 'both'), - } + content_data = contents_dict[name] = { + "creator": creator, + "senders": content_elt.attributes.get("senders", "both"), + } else: # the content must exist, we check it try: content_data = contents_dict[name] except KeyError: log.warning(u"Other peer try to access an unknown content") - self.sendError(client, 'bad-request', session['id'], request) + self.sendError(client, "bad-request", session["id"], request) raise exceptions.CancelError # application if with_application: desc_elt = content_elt.description if not desc_elt: - self.sendError(client, 'bad-request', session['id'], request) + self.sendError(client, "bad-request", session["id"], request) raise exceptions.CancelError if new: # the content is new, we need to check and link the application app_ns = desc_elt.uri if not app_ns or app_ns == NS_JINGLE: - self.sendError(client, 'bad-request', session['id'], request) + self.sendError(client, "bad-request", session["id"], request) raise exceptions.CancelError try: application = self._applications[app_ns] except KeyError: - log.warning(u"Unmanaged application namespace [{}]".format(app_ns)) - self.sendError(client, 'service-unavailable', session['id'], request) + log.warning( + u"Unmanaged application namespace [{}]".format(app_ns) + ) + self.sendError( + client, "service-unavailable", session["id"], request + ) raise exceptions.CancelError - content_data['application'] = application - content_data['application_data'] = {} + content_data["application"] = application + content_data["application_data"] = {} else: # the content exists, we check that we have not a former desc_elt - if 'desc_elt' in content_data: - raise exceptions.InternalError(u"desc_elt should not exist at this point") + if "desc_elt" in content_data: + raise exceptions.InternalError( + u"desc_elt should not exist at this point" + ) - content_data['desc_elt'] = desc_elt + content_data["desc_elt"] = desc_elt # transport if with_transport: transport_elt = content_elt.transport if not transport_elt: - self.sendError(client, 'bad-request', session['id'], request) + self.sendError(client, "bad-request", session["id"], request) raise exceptions.CancelError if new: # the content is new, we need to check and link the transport transport_ns = transport_elt.uri if not app_ns or app_ns == NS_JINGLE: - self.sendError(client, 'bad-request', session['id'], request) + self.sendError(client, "bad-request", session["id"], request) raise exceptions.CancelError try: transport = self._transports[transport_ns] except KeyError: - raise exceptions.InternalError(u"No transport registered for namespace {}".format(transport_ns)) - content_data['transport'] = transport - content_data['transport_data'] = {} + raise exceptions.InternalError( + u"No transport registered for namespace {}".format( + transport_ns + ) + ) + content_data["transport"] = transport + content_data["transport_data"] = {} else: # the content exists, we check that we have not a former transport_elt - if 'transport_elt' in content_data: - raise exceptions.InternalError(u"transport_elt should not exist at this point") + if "transport_elt" in content_data: + raise exceptions.InternalError( + u"transport_elt should not exist at this point" + ) - content_data['transport_elt'] = transport_elt + content_data["transport_elt"] = transport_elt def _ignore(self, client, action, session, content_name, elt): """Dummy method used when not exception must be raised if a method is not implemented in _callPlugins @@ -614,10 +701,19 @@ """ return elt - def _callPlugins(self, client, action, session, app_method_name='jingleHandler', - transp_method_name='jingleHandler', - app_default_cb=None, transp_default_cb=None, delete=True, - elements=True, force_element=None): + def _callPlugins( + self, + client, + action, + session, + app_method_name="jingleHandler", + transp_method_name="jingleHandler", + app_default_cb=None, + transp_default_cb=None, + delete=True, + elements=True, + force_element=None, + ): """Call application and transport plugin methods for all contents @param action(unicode): jingle action name @@ -640,12 +736,13 @@ @return (list[defer.Deferred]): list of launched Deferred @raise exceptions.NotFound: method is not implemented """ - contents_dict = session['contents'] + contents_dict = session["contents"] defers_list = [] for content_name, content_data in contents_dict.iteritems(): for method_name, handler_key, default_cb, elt_name in ( - (app_method_name, 'application', app_default_cb, 'desc_elt'), - (transp_method_name, 'transport', transp_default_cb, 'transport_elt')): + (app_method_name, "application", app_default_cb, "desc_elt"), + (transp_method_name, "transport", transp_default_cb, "transport_elt"), + ): if method_name is None: continue @@ -654,14 +751,18 @@ method = getattr(handler, method_name) except AttributeError: if default_cb is None: - raise exceptions.NotFound(u'{} not implemented !'.format(method_name)) + raise exceptions.NotFound( + u"{} not implemented !".format(method_name) + ) else: method = default_cb if elements: elt = content_data.pop(elt_name) if delete else content_data[elt_name] else: elt = force_element - d = defer.maybeDeferred(method, client, action, session, content_name, elt) + d = defer.maybeDeferred( + method, client, action, session, content_name, elt + ) defers_list.append(d) return defers_list @@ -678,30 +779,42 @@ @param jingle_elt(domish.Element): <jingle> element @param session(dict): session data """ - if 'contents' in session: - raise exceptions.InternalError("Contents dict should not already exist at this point") - session['contents'] = contents_dict = {} + if "contents" in session: + raise exceptions.InternalError( + "Contents dict should not already exist at this point" + ) + session["contents"] = contents_dict = {} try: - self._parseElements(jingle_elt, session, request, client, True, XEP_0166.ROLE_INITIATOR) + self._parseElements( + jingle_elt, session, request, client, True, XEP_0166.ROLE_INITIATOR + ) except exceptions.CancelError: return if not contents_dict: # there MUST be at least one content - self.sendError(client, 'bad-request', session['id'], request) + self.sendError(client, "bad-request", session["id"], request) return # at this point we can send the <iq/> result to confirm reception of the request - client.send(xmlstream.toResponse(request, 'result')) + client.send(xmlstream.toResponse(request, "result")) # we now request each application plugin confirmation # and if all are accepted, we can accept the session - confirm_defers = self._callPlugins(client, XEP_0166.A_SESSION_INITIATE, session, 'jingleRequestConfirmation', None, self.jingleRequestConfirmationDefault, delete=False) + confirm_defers = self._callPlugins( + client, + XEP_0166.A_SESSION_INITIATE, + session, + "jingleRequestConfirmation", + None, + self.jingleRequestConfirmationDefault, + delete=False, + ) confirm_dlist = defer.gatherResults(confirm_defers) confirm_dlist.addCallback(self._confirmationCb, session, jingle_elt, client) - confirm_dlist.addErrback(self._jingleErrorCb, session['id'], request, client) + confirm_dlist.addErrback(self._jingleErrorCb, session["id"], request, client) def _confirmationCb(self, confirm_results, session, jingle_elt, client): """Method called when confirmation from user has been received @@ -716,8 +829,10 @@ if not confirmed: return self.terminate(client, XEP_0166.REASON_DECLINE, session) - iq_elt, jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_SESSION_ACCEPT) - jingle_elt['responder'] = client.jid.full() + iq_elt, jingle_elt = self._buildJingleElt( + client, session, XEP_0166.A_SESSION_ACCEPT + ) + jingle_elt["responder"] = client.jid.full() # contents @@ -726,52 +841,87 @@ defers_list = [] - for content_name, content_data in session['contents'].iteritems(): - content_elt = jingle_elt.addElement('content') - content_elt['creator'] = XEP_0166.ROLE_INITIATOR - content_elt['name'] = content_name + for content_name, content_data in session["contents"].iteritems(): + content_elt = jingle_elt.addElement("content") + content_elt["creator"] = XEP_0166.ROLE_INITIATOR + content_elt["name"] = content_name - application = content_data['application'] + application = content_data["application"] app_session_accept_cb = application.handler.jingleHandler - app_d = defer.maybeDeferred(app_session_accept_cb, client, - XEP_0166.A_SESSION_INITIATE, session, content_name, content_data.pop('desc_elt')) + app_d = defer.maybeDeferred( + app_session_accept_cb, + client, + XEP_0166.A_SESSION_INITIATE, + session, + content_name, + content_data.pop("desc_elt"), + ) app_d.addCallback(addElement, content_elt) defers_list.append(app_d) - transport = content_data['transport'] + transport = content_data["transport"] transport_session_accept_cb = transport.handler.jingleHandler - transport_d = defer.maybeDeferred(transport_session_accept_cb, client, - XEP_0166.A_SESSION_INITIATE, session, content_name, content_data.pop('transport_elt')) + transport_d = defer.maybeDeferred( + transport_session_accept_cb, + client, + XEP_0166.A_SESSION_INITIATE, + session, + content_name, + content_data.pop("transport_elt"), + ) transport_d.addCallback(addElement, content_elt) defers_list.append(transport_d) d_list = defer.DeferredList(defers_list) - d_list.addCallback(lambda dummy: self._callPlugins(client, XEP_0166.A_PREPARE_RESPONDER, session, app_method_name=None, elements=False)) + d_list.addCallback( + lambda dummy: self._callPlugins( + client, + XEP_0166.A_PREPARE_RESPONDER, + session, + app_method_name=None, + elements=False, + ) + ) d_list.addCallback(lambda dummy: iq_elt.send()) + def changeState(dummy, session): - session['state'] = STATE_ACTIVE + session["state"] = STATE_ACTIVE d_list.addCallback(changeState, session) - d_list.addCallback(lambda dummy: self._callPlugins(client, XEP_0166.A_ACCEPTED_ACK, session, elements=False)) - d_list.addErrback(self._iqError, session['id'], client) + d_list.addCallback( + lambda dummy: self._callPlugins( + client, XEP_0166.A_ACCEPTED_ACK, session, elements=False + ) + ) + d_list.addErrback(self._iqError, session["id"], client) return d_list def onSessionTerminate(self, client, request, jingle_elt, session): # TODO: check reason, display a message to user if needed - log.debug("Jingle Session {} terminated".format(session['id'])) + log.debug("Jingle Session {} terminated".format(session["id"])) try: - reason_elt = jingle_elt.elements(NS_JINGLE, 'reason').next() + reason_elt = jingle_elt.elements(NS_JINGLE, "reason").next() except StopIteration: log.warning(u"No reason given for session termination") - reason_elt = jingle_elt.addElement('reason') + reason_elt = jingle_elt.addElement("reason") - terminate_defers = self._callPlugins(client, XEP_0166.A_SESSION_TERMINATE, session, 'jingleTerminate', 'jingleTerminate', self._ignore, self._ignore, elements=False, force_element=reason_elt) + terminate_defers = self._callPlugins( + client, + XEP_0166.A_SESSION_TERMINATE, + session, + "jingleTerminate", + "jingleTerminate", + self._ignore, + self._ignore, + elements=False, + force_element=reason_elt, + ) terminate_dlist = defer.DeferredList(terminate_defers) - terminate_dlist.addCallback(lambda dummy: self._delSession(client, session['id'])) - client.send(xmlstream.toResponse(request, 'result')) + terminate_dlist.addCallback(lambda dummy: self._delSession(client, session["id"])) + client.send(xmlstream.toResponse(request, "result")) def onSessionAccept(self, client, request, jingle_elt, session): """Method called once session is accepted @@ -782,7 +932,7 @@ @param jingle_elt(domish.Element): the <jingle> element @param session(dict): session data """ - log.debug(u"Jingle session {} has been accepted".format(session['id'])) + log.debug(u"Jingle session {} has been accepted".format(session["id"])) try: self._parseElements(jingle_elt, session, request, client) @@ -790,9 +940,9 @@ return # at this point we can send the <iq/> result to confirm reception of the request - client.send(xmlstream.toResponse(request, 'result')) + client.send(xmlstream.toResponse(request, "result")) # and change the state - session['state'] = STATE_ACTIVE + session["state"] = STATE_ACTIVE negociate_defers = [] negociate_defers = self._callPlugins(client, XEP_0166.A_SESSION_ACCEPT, session) @@ -800,15 +950,21 @@ negociate_dlist = defer.DeferredList(negociate_defers) # after negociations we start the transfer - negociate_dlist.addCallback(lambda dummy: self._callPlugins(client, XEP_0166.A_START, session, app_method_name=None, elements=False)) + negociate_dlist.addCallback( + lambda dummy: self._callPlugins( + client, XEP_0166.A_START, session, app_method_name=None, elements=False + ) + ) def _onSessionCb(self, result, client, request, jingle_elt, session): - client.send(xmlstream.toResponse(request, 'result')) + client.send(xmlstream.toResponse(request, "result")) def _onSessionEb(self, failure_, client, request, jingle_elt, session): log.error(u"Error while handling onSessionInfo: {}".format(failure_.value)) # XXX: only error managed so far, maybe some applications/transports need more - self.sendError(client, 'feature-not-implemented', None, request, 'unsupported-info') + self.sendError( + client, "feature-not-implemented", None, request, "unsupported-info" + ) def onSessionInfo(self, client, request, jingle_elt, session): """Method called when a session-info action is received from other peer @@ -821,14 +977,21 @@ """ if not jingle_elt.children: # this is a session ping, see XEP-0166 §6.8 - client.send(xmlstream.toResponse(request, 'result')) + client.send(xmlstream.toResponse(request, "result")) return try: # XXX: session-info is most likely only used for application, so we don't call transport plugins # if a future transport use it, this behaviour must be adapted - defers = self._callPlugins(client, XEP_0166.A_SESSION_INFO, session, 'jingleSessionInfo', None, - elements=False, force_element=jingle_elt) + defers = self._callPlugins( + client, + XEP_0166.A_SESSION_INFO, + session, + "jingleSessionInfo", + None, + elements=False, + force_element=jingle_elt, + ) except exceptions.NotFound as e: self._onSessionEb(failure.Failure(e), client, request, jingle_elt, session) return @@ -849,32 +1012,40 @@ """ log.debug(u"Other peer wants to replace the transport") try: - self._parseElements(jingle_elt, session, request, client, with_application=False) + self._parseElements( + jingle_elt, session, request, client, with_application=False + ) except exceptions.CancelError: defer.returnValue(None) - client.send(xmlstream.toResponse(request, 'result')) + client.send(xmlstream.toResponse(request, "result")) content_name = None to_replace = [] - for content_name, content_data in session['contents'].iteritems(): + for content_name, content_data in session["contents"].iteritems(): try: - transport_elt = content_data.pop('transport_elt') + transport_elt = content_data.pop("transport_elt") except KeyError: continue transport_ns = transport_elt.uri try: transport = self._transports[transport_ns] except KeyError: - log.warning(u"Other peer want to replace current transport with an unknown one: {}".format(transport_ns)) + log.warning( + u"Other peer want to replace current transport with an unknown one: {}".format( + transport_ns + ) + ) content_name = None break to_replace.append((content_name, content_data, transport, transport_elt)) if content_name is None: # wa can't accept the replacement - iq_elt, reject_jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_TRANSPORT_REJECT) + iq_elt, reject_jingle_elt = self._buildJingleElt( + client, session, XEP_0166.A_TRANSPORT_REJECT + ) for child in jingle_elt.children: reject_jingle_elt.addChild(child) @@ -883,21 +1054,29 @@ # at this point, everything is alright and we can replace the transport(s) # this is similar to an session-accept action, but for transports only - iq_elt, accept_jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_TRANSPORT_ACCEPT) + iq_elt, accept_jingle_elt = self._buildJingleElt( + client, session, XEP_0166.A_TRANSPORT_ACCEPT + ) for content_name, content_data, transport, transport_elt in to_replace: # we can now actually replace the transport - yield content_data['transport'].handler.jingleHandler(client, XEP_0166.A_DESTROY, session, content_name, None) - content_data['transport'] = transport - content_data['transport_data'].clear() + yield content_data["transport"].handler.jingleHandler( + client, XEP_0166.A_DESTROY, session, content_name, None + ) + content_data["transport"] = transport + content_data["transport_data"].clear() # and build the element - content_elt = accept_jingle_elt.addElement('content') - content_elt['name'] = content_name - content_elt['creator'] = content_data['creator'] + content_elt = accept_jingle_elt.addElement("content") + content_elt["name"] = content_name + content_elt["creator"] = content_data["creator"] # we notify the transport and insert its <transport/> in the answer - accept_transport_elt = yield transport.handler.jingleHandler(client, XEP_0166.A_TRANSPORT_REPLACE, session, content_name, transport_elt) + accept_transport_elt = yield transport.handler.jingleHandler( + client, XEP_0166.A_TRANSPORT_REPLACE, session, content_name, transport_elt + ) content_elt.addChild(accept_transport_elt) # there is no confirmation needed here, so we can directly prepare it - yield transport.handler.jingleHandler(client, XEP_0166.A_PREPARE_RESPONDER, session, content_name, None) + yield transport.handler.jingleHandler( + client, XEP_0166.A_PREPARE_RESPONDER, session, content_name, None + ) iq_elt.send() @@ -912,20 +1091,28 @@ log.debug(u"new transport has been accepted") try: - self._parseElements(jingle_elt, session, request, client, with_application=False) + self._parseElements( + jingle_elt, session, request, client, with_application=False + ) except exceptions.CancelError: return # at this point we can send the <iq/> result to confirm reception of the request - client.send(xmlstream.toResponse(request, 'result')) + client.send(xmlstream.toResponse(request, "result")) negociate_defers = [] - negociate_defers = self._callPlugins(client, XEP_0166.A_TRANSPORT_ACCEPT, session, app_method_name=None) + negociate_defers = self._callPlugins( + client, XEP_0166.A_TRANSPORT_ACCEPT, session, app_method_name=None + ) negociate_dlist = defer.DeferredList(negociate_defers) # after negociations we start the transfer - negociate_dlist.addCallback(lambda dummy: self._callPlugins(client, XEP_0166.A_START, session, app_method_name=None, elements=False)) + negociate_dlist.addCallback( + lambda dummy: self._callPlugins( + client, XEP_0166.A_START, session, app_method_name=None, elements=False + ) + ) def onTransportReject(self, client, request, jingle_elt, session): """Method called when a transport replacement is refused @@ -937,7 +1124,7 @@ """ # XXX: for now, we terminate the session in case of transport-reject # this behaviour may change in the future - self.terminate(client, 'failed-transport', session) + self.terminate(client, "failed-transport", session) def onTransportInfo(self, client, request, jingle_elt, session): """Method called when a transport-info action is received from other peer @@ -948,23 +1135,31 @@ @param jingle_elt(domish.Element): the <jingle> element @param session(dict): session data """ - log.debug(u"Jingle session {} has been accepted".format(session['id'])) + log.debug(u"Jingle session {} has been accepted".format(session["id"])) try: - self._parseElements(jingle_elt, session, request, client, with_application=False) + self._parseElements( + jingle_elt, session, request, client, with_application=False + ) except exceptions.CancelError: return # The parsing was OK, we send the <iq> result - client.send(xmlstream.toResponse(request, 'result')) + client.send(xmlstream.toResponse(request, "result")) - for content_name, content_data in session['contents'].iteritems(): + for content_name, content_data in session["contents"].iteritems(): try: - transport_elt = content_data.pop('transport_elt') + transport_elt = content_data.pop("transport_elt") except KeyError: continue else: - content_data['transport'].handler.jingleHandler(client, XEP_0166.A_TRANSPORT_INFO, session, content_name, transport_elt) + content_data["transport"].handler.jingleHandler( + client, + XEP_0166.A_TRANSPORT_INFO, + session, + content_name, + transport_elt, + ) class XEP_0166_handler(xmlstream.XMPPHandler): @@ -974,10 +1169,12 @@ self.plugin_parent = plugin_parent def connectionInitialized(self): - self.xmlstream.addObserver(JINGLE_REQUEST, self.plugin_parent._onJingleRequest, client=self.parent) + self.xmlstream.addObserver( + JINGLE_REQUEST, self.plugin_parent._onJingleRequest, client=self.parent + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_JINGLE)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0184.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0184.py Wed Jun 27 20:14:46 2018 +0200 @@ -22,27 +22,37 @@ from twisted.internet import reactor from twisted.words.protocols.jabber import xmlstream, jid from twisted.words.xish import domish + log = getLogger(__name__) from wokkel import disco, iwokkel from zope.interface import implements + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: from wokkel.subprotocols import XMPPHandler -NS_MESSAGE_DELIVERY_RECEIPTS = 'urn:xmpp:receipts' +NS_MESSAGE_DELIVERY_RECEIPTS = "urn:xmpp:receipts" -MSG = 'message' +MSG = "message" -MSG_CHAT = '/'+MSG+'[@type="chat"]' -MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_REQUEST = MSG_CHAT+'/request[@xmlns="'+NS_MESSAGE_DELIVERY_RECEIPTS+'"]' -MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_RECEIVED = MSG_CHAT+'/received[@xmlns="'+NS_MESSAGE_DELIVERY_RECEIPTS+'"]' +MSG_CHAT = "/" + MSG + '[@type="chat"]' +MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_REQUEST = ( + MSG_CHAT + '/request[@xmlns="' + NS_MESSAGE_DELIVERY_RECEIPTS + '"]' +) +MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_RECEIVED = ( + MSG_CHAT + '/received[@xmlns="' + NS_MESSAGE_DELIVERY_RECEIPTS + '"]' +) -MSG_NORMAL = '/'+MSG+'[@type="normal"]' -MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_REQUEST = MSG_NORMAL+'/request[@xmlns="'+NS_MESSAGE_DELIVERY_RECEIPTS+'"]' -MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_RECEIVED = MSG_NORMAL+'/received[@xmlns="'+NS_MESSAGE_DELIVERY_RECEIPTS+'"]' +MSG_NORMAL = "/" + MSG + '[@type="normal"]' +MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_REQUEST = ( + MSG_NORMAL + '/request[@xmlns="' + NS_MESSAGE_DELIVERY_RECEIPTS + '"]' +) +MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_RECEIVED = ( + MSG_NORMAL + '/received[@xmlns="' + NS_MESSAGE_DELIVERY_RECEIPTS + '"]' +) PARAM_KEY = "Privacy" @@ -51,25 +61,26 @@ PLUGIN_INFO = { -C.PI_NAME: "XEP-0184 Plugin", -C.PI_IMPORT_NAME: "XEP-0184", -C.PI_TYPE: "XEP", -C.PI_PROTOCOLS: ["XEP-0184"], -C.PI_DEPENDENCIES: [], -C.PI_MAIN: "XEP_0184", -C.PI_HANDLER: "yes", -C.PI_DESCRIPTION: _("""Implementation of Message Delivery Receipts""") + C.PI_NAME: "XEP-0184 Plugin", + C.PI_IMPORT_NAME: "XEP-0184", + C.PI_TYPE: "XEP", + C.PI_PROTOCOLS: ["XEP-0184"], + C.PI_DEPENDENCIES: [], + C.PI_MAIN: "XEP_0184", + C.PI_HANDLER: "yes", + C.PI_DESCRIPTION: _("""Implementation of Message Delivery Receipts"""), } STATUS_MESSAGE_DELIVERY_RECEIVED = "delivered" -TEMPO_DELETE_WAITING_ACK_S = 300 # 5 min +TEMPO_DELETE_WAITING_ACK_S = 300 # 5 min class XEP_0184(object): """ Implementation for XEP 0184. """ + params = """ <params> <individual> @@ -79,10 +90,10 @@ </individual> </params> """ % { - 'category_name': PARAM_KEY, - 'category_label': _(PARAM_KEY), - 'param_name': PARAM_NAME, - 'param_label': _('Enable message delivery receipts') + "category_name": PARAM_KEY, + "category_label": _(PARAM_KEY), + "param_name": PARAM_NAME, + "param_label": _("Enable message delivery receipts"), } def __init__(self, host): @@ -94,24 +105,39 @@ host.memory.updateParams(self.params) host.trigger.add("sendMessage", self.sendMessageTrigger) - host.bridge.addSignal("messageState", ".plugin", signature='sss') # message_uid, status, profile + host.bridge.addSignal( + "messageState", ".plugin", signature="sss" + ) # message_uid, status, profile def getHandler(self, client): return XEP_0184_handler(self, client.profile) - def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments): + def sendMessageTrigger( + self, client, mess_data, pre_xml_treatments, post_xml_treatments + ): """Install SendMessage command hook """ + def treatment(mess_data): - message = mess_data['xml'] + message = mess_data["xml"] message_type = message.getAttribute("type") - if self._isActif(client.profile) and (message_type == "chat" or message_type == "normal"): - message.addElement('request', NS_MESSAGE_DELIVERY_RECEIPTS) - uid = mess_data['uid'] + if self._isActif(client.profile) and ( + message_type == "chat" or message_type == "normal" + ): + message.addElement("request", NS_MESSAGE_DELIVERY_RECEIPTS) + uid = mess_data["uid"] msg_id = message.getAttribute("id") self._dictRequest[msg_id] = uid - reactor.callLater(TEMPO_DELETE_WAITING_ACK_S, self._clearDictRequest, msg_id) - log.debug(_("[XEP-0184] Request acknowledgment for message id {}".format(msg_id))) + reactor.callLater( + TEMPO_DELETE_WAITING_ACK_S, self._clearDictRequest, msg_id + ) + log.debug( + _( + "[XEP-0184] Request acknowledgment for message id {}".format( + msg_id + ) + ) + ) return mess_data @@ -122,13 +148,13 @@ """This method is called on message delivery receipts **request** (XEP-0184 #7) @param msg_elt: message element @param client: %(doc_client)s""" - from_jid = jid.JID(msg_elt['from']) + from_jid = jid.JID(msg_elt["from"]) if self._isActif(client.profile) and client.roster.isPresenceAuthorised(from_jid): - received_elt_ret = domish.Element((NS_MESSAGE_DELIVERY_RECEIPTS, 'received')) + received_elt_ret = domish.Element((NS_MESSAGE_DELIVERY_RECEIPTS, "received")) received_elt_ret["id"] = msg_elt["id"] - msg_result_elt = xmlstream.toResponse(msg_elt, 'result') + msg_result_elt = xmlstream.toResponse(msg_elt, "result") msg_result_elt.addChild(received_elt_ret) client.send(msg_result_elt) @@ -137,27 +163,38 @@ @param msg_elt: message element @param client: %(doc_client)s""" msg_elt.handled = True - rcv_elt = msg_elt.elements(NS_MESSAGE_DELIVERY_RECEIPTS, 'received').next() - msg_id = rcv_elt['id'] + rcv_elt = msg_elt.elements(NS_MESSAGE_DELIVERY_RECEIPTS, "received").next() + msg_id = rcv_elt["id"] try: uid = self._dictRequest[msg_id] del self._dictRequest[msg_id] - self.host.bridge.messageState(uid, STATUS_MESSAGE_DELIVERY_RECEIVED, client.profile) - log.debug(_("[XEP-0184] Receive acknowledgment for message id {}".format(msg_id))) + self.host.bridge.messageState( + uid, STATUS_MESSAGE_DELIVERY_RECEIVED, client.profile + ) + log.debug( + _("[XEP-0184] Receive acknowledgment for message id {}".format(msg_id)) + ) except KeyError: pass def _clearDictRequest(self, msg_id): try: del self._dictRequest[msg_id] - log.debug(_("[XEP-0184] Delete waiting acknowledgment for message id {}".format(msg_id))) + log.debug( + _( + "[XEP-0184] Delete waiting acknowledgment for message id {}".format( + msg_id + ) + ) + ) except KeyError: pass def _isActif(self, profile): return self.host.memory.getParamA(PARAM_NAME, PARAM_KEY, profile_key=profile) + class XEP_0184_handler(XMPPHandler): implements(iwokkel.IDisco) @@ -167,14 +204,30 @@ self.profile = profile def connectionInitialized(self): - self.xmlstream.addObserver(MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_REQUEST, self.plugin_parent.onMessageDeliveryReceiptsRequest, client=self.parent) - self.xmlstream.addObserver(MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_RECEIVED, self.plugin_parent.onMessageDeliveryReceiptsReceived, client=self.parent) + self.xmlstream.addObserver( + MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_REQUEST, + self.plugin_parent.onMessageDeliveryReceiptsRequest, + client=self.parent, + ) + self.xmlstream.addObserver( + MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_RECEIVED, + self.plugin_parent.onMessageDeliveryReceiptsReceived, + client=self.parent, + ) - self.xmlstream.addObserver(MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_REQUEST, self.plugin_parent.onMessageDeliveryReceiptsRequest, client=self.parent) - self.xmlstream.addObserver(MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_RECEIVED, self.plugin_parent.onMessageDeliveryReceiptsReceived, client=self.parent) + self.xmlstream.addObserver( + MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_REQUEST, + self.plugin_parent.onMessageDeliveryReceiptsRequest, + client=self.parent, + ) + self.xmlstream.addObserver( + MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_RECEIVED, + self.plugin_parent.onMessageDeliveryReceiptsReceived, + client=self.parent, + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_MESSAGE_DELIVERY_RECEIPTS)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0203.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0203.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,9 +21,11 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from wokkel import disco, iwokkel, delay + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: @@ -31,7 +33,7 @@ from zope.interface import implements -NS_DD = 'urn:xmpp:delay' +NS_DD = "urn:xmpp:delay" PLUGIN_INFO = { C.PI_NAME: "Delayed Delivery", @@ -40,12 +42,11 @@ C.PI_PROTOCOLS: ["XEP-0203"], C.PI_MAIN: "XEP_0203", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Delayed Delivery""") + C.PI_DESCRIPTION: _("""Implementation of Delayed Delivery"""), } class XEP_0203(object): - def __init__(self, host): log.info(_("Delayed Delivery plugin initialization")) self.host = host @@ -53,7 +54,7 @@ def getHandler(self, client): return XEP_0203_handler(self, client.profile) - def delay(self, stamp, sender=None, desc='', parent=None): + def delay(self, stamp, sender=None, desc="", parent=None): """Build a delay element, eventually append it to the given parent element. @param stamp (datetime): offset-aware timestamp of the original sending. @@ -78,8 +79,8 @@ self.host = plugin_parent.host self.profile = profile - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_DD)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0231.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0231.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core.constants import Const as C from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) from sat.tools import xml_tools from wokkel import disco, iwokkel @@ -43,24 +44,29 @@ C.PI_PROTOCOLS: ["XEP-0231"], C.PI_MAIN: "XEP_0231", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of bits of binary (used for small images/files)""") + C.PI_DESCRIPTION: _( + """Implementation of bits of binary (used for small images/files)""" + ), } -NS_BOB = u'urn:xmpp:bob' +NS_BOB = u"urn:xmpp:bob" IQ_BOB_REQUEST = C.IQ_GET + '/data[@xmlns="' + NS_BOB + '"]' class XEP_0231(object): - def __init__(self, host): log.info(_(u"plugin Bits of Binary initialization")) self.host = host - host.registerNamespace('bob', NS_BOB) + host.registerNamespace("bob", NS_BOB) host.trigger.add("xhtml_post_treat", self.XHTMLTrigger) - host.bridge.addMethod("bobGetFile", ".plugin", - in_sign='sss', out_sign='s', - method=self._getFile, - async=True) + host.bridge.addMethod( + "bobGetFile", + ".plugin", + in_sign="sss", + out_sign="s", + method=self._getFile, + async=True, + ) def dumpData(self, cache, data_elt, cid): """save file encoded in data_elt to cache @@ -70,21 +76,19 @@ @param cid(unicode): content-id @return(unicode): full path to dumped file """ - # FIXME: is it needed to use a separate thread? + # FIXME: is it needed to use a separate thread? # probably not with the little data expected with BoB try: - max_age = int(data_elt['max-age']) + max_age = int(data_elt["max-age"]) if max_age < 0: raise ValueError except (KeyError, ValueError): - log.warning(u'invalid max-age found') + log.warning(u"invalid max-age found") max_age = None with cache.cacheData( - PLUGIN_INFO[C.PI_IMPORT_NAME], - cid, - data_elt.getAttribute('type'), - max_age) as f: + PLUGIN_INFO[C.PI_IMPORT_NAME], cid, data_elt.getAttribute("type"), max_age + ) as f: file_path = f.name f.write(base64.b64decode(str(data_elt))) @@ -95,15 +99,16 @@ return XEP_0231_handler(self) def _requestCb(self, iq_elt, cache, cid): - for data_elt in iq_elt.elements(NS_BOB, u'data'): - if data_elt.getAttribute('cid') == cid: + for data_elt in iq_elt.elements(NS_BOB, u"data"): + if data_elt.getAttribute("cid") == cid: file_path = self.dumpData(cache, data_elt, cid) return file_path - log.warning(u"invalid data stanza received, requested cid was not found:\n{iq_elt}\nrequested cid: {cid}".format( - iq_elt = iq_elt, - cid = cid - )) + log.warning( + u"invalid data stanza received, requested cid was not found:\n{iq_elt}\nrequested cid: {cid}".format( + iq_elt=iq_elt, cid=cid + ) + ) raise failure.Failure(exceptions.DataError("missing data")) def _requestEb(self, failure_): @@ -122,39 +127,39 @@ """ if cache is None: cache = client.cache - iq_elt = client.IQ('get') - iq_elt['to'] = to_jid.full() - data_elt = iq_elt.addElement((NS_BOB, 'data')) - data_elt['cid'] = cid + iq_elt = client.IQ("get") + iq_elt["to"] = to_jid.full() + data_elt = iq_elt.addElement((NS_BOB, "data")) + data_elt["cid"] = cid d = iq_elt.send() d.addCallback(self._requestCb, cache, cid) d.addErrback(self._requestEb) return d def _setImgEltSrc(self, path, img_elt): - img_elt[u'src'] = u'file://{}'.format(path) + img_elt[u"src"] = u"file://{}".format(path) def XHTMLTrigger(self, client, message_elt, body_elt, lang, treat_d): - for img_elt in xml_tools.findAll(body_elt, C.NS_XHTML, u'img'): - source = img_elt.getAttribute(u'src','') - if source.startswith(u'cid:'): + for img_elt in xml_tools.findAll(body_elt, C.NS_XHTML, u"img"): + source = img_elt.getAttribute(u"src", "") + if source.startswith(u"cid:"): cid = source[4:] file_path = client.cache.getFilePath(cid) if file_path is not None: - # image is in cache, we change the url - img_elt[u'src'] = u'file://{}'.format(file_path) + # image is in cache, we change the url + img_elt[u"src"] = u"file://{}".format(file_path) continue else: # image is not in cache, is it given locally? - for data_elt in message_elt.elements(NS_BOB, u'data'): - if data_elt.getAttribute('cid') == cid: + for data_elt in message_elt.elements(NS_BOB, u"data"): + if data_elt.getAttribute("cid") == cid: file_path = self.dumpData(client.cache, data_elt, cid) - img_elt[u'src'] = u'file://{}'.format(file_path) + img_elt[u"src"] = u"file://{}".format(file_path) break else: # cid not found locally, we need to request it # so we use the deferred - d = self.requestData(client, jid.JID(message_elt['from']), cid) + d = self.requestData(client, jid.JID(message_elt["from"]), cid) d.addCallback(partial(self._setImgEltSrc, img_elt=img_elt)) treat_d.addCallback(lambda dummy: d) @@ -165,28 +170,28 @@ # An access check should be implemented though. iq_elt.handled = True - data_elt = next(iq_elt.elements(NS_BOB, 'data')) + data_elt = next(iq_elt.elements(NS_BOB, "data")) try: - cid = data_elt[u'cid'] + cid = data_elt[u"cid"] except KeyError: - error_elt = jabber_error.StanzaError('not-acceptable').toResponse(iq_elt) + error_elt = jabber_error.StanzaError("not-acceptable").toResponse(iq_elt) client.send(error_elt) return metadata = self.host.common_cache.getMetadata(cid) if metadata is None: - error_elt = jabber_error.StanzaError('item-not-found').toResponse(iq_elt) + error_elt = jabber_error.StanzaError("item-not-found").toResponse(iq_elt) client.send(error_elt) return - with open(metadata['path']) as f: + with open(metadata["path"]) as f: data = f.read() - result_elt = xmlstream.toResponse(iq_elt, 'result') - data_elt = result_elt.addElement((NS_BOB, 'data'), content = data.encode('base64')) - data_elt[u'cid'] = cid - data_elt[u'type'] = metadata[u'mime_type'] - data_elt[u'max-age'] = unicode(int(max(0, metadata['eol'] - time.time()))) + result_elt = xmlstream.toResponse(iq_elt, "result") + data_elt = result_elt.addElement((NS_BOB, "data"), content=data.encode("base64")) + data_elt[u"cid"] = cid + data_elt[u"type"] = metadata[u"mime_type"] + data_elt[u"max-age"] = unicode(int(max(0, metadata["eol"] - time.time()))) client.send(result_elt) def _getFile(self, peer_jid_s, cid, profile): @@ -207,13 +212,13 @@ """ file_path = client.cache.getFilePath(cid) if file_path is not None: - # file is in cache + # file is in cache return defer.succeed(file_path) else: # file not in cache, is it given locally? if parent_elt is not None: - for data_elt in parent_elt.elements(NS_BOB, u'data'): - if data_elt.getAttribute('cid') == cid: + for data_elt in parent_elt.elements(NS_BOB, u"data"): + if data_elt.getAttribute("cid") == cid: return defer.succeed(self.dumpData(client.cache, data_elt, cid)) # cid not found locally, we need to request it @@ -230,10 +235,12 @@ def connectionInitialized(self): if self.parent.is_component: - self.xmlstream.addObserver(IQ_BOB_REQUEST, self.plugin_parent.onComponentRequest, client=self.parent) + self.xmlstream.addObserver( + IQ_BOB_REQUEST, self.plugin_parent.onComponentRequest, client=self.parent + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_BOB)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0234.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0234.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from wokkel import disco, iwokkel @@ -40,7 +41,7 @@ import mimetypes -NS_JINGLE_FT = 'urn:xmpp:jingle:apps:file-transfer:5' +NS_JINGLE_FT = "urn:xmpp:jingle:apps:file-transfer:5" PLUGIN_INFO = { C.PI_NAME: "Jingle File Transfer", @@ -51,11 +52,11 @@ C.PI_DEPENDENCIES: ["XEP-0166", "XEP-0300", "FILE"], C.PI_MAIN: "XEP_0234", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Jingle File Transfer""") + C.PI_DESCRIPTION: _("""Implementation of Jingle File Transfer"""), } -EXTRA_ALLOWED = {u'path', u'namespace', u'file_desc', u'file_hash'} -Range = namedtuple('Range', ('offset', 'length')) +EXTRA_ALLOWED = {u"path", u"namespace", u"file_desc", u"file_hash"} +Range = namedtuple("Range", ("offset", "length")) class XEP_0234(object): @@ -66,14 +67,30 @@ def __init__(self, host): log.info(_("plugin Jingle File Transfer initialization")) self.host = host - host.registerNamespace('jingle-ft', NS_JINGLE_FT) - self._j = host.plugins["XEP-0166"] # shortcut to access jingle + host.registerNamespace("jingle-ft", NS_JINGLE_FT) + self._j = host.plugins["XEP-0166"] # shortcut to access jingle self._j.registerApplication(NS_JINGLE_FT, self) self._f = host.plugins["FILE"] - self._f.register(NS_JINGLE_FT, self.fileJingleSend, priority = 10000, method_name=u"Jingle") + self._f.register( + NS_JINGLE_FT, self.fileJingleSend, priority=10000, method_name=u"Jingle" + ) self._hash = self.host.plugins["XEP-0300"] - host.bridge.addMethod("fileJingleSend", ".plugin", in_sign='ssssa{ss}s', out_sign='', method=self._fileJingleSend, async=True) - host.bridge.addMethod("fileJingleRequest", ".plugin", in_sign='sssssa{ss}s', out_sign='s', method=self._fileJingleRequest, async=True) + host.bridge.addMethod( + "fileJingleSend", + ".plugin", + in_sign="ssssa{ss}s", + out_sign="", + method=self._fileJingleSend, + async=True, + ) + host.bridge.addMethod( + "fileJingleRequest", + ".plugin", + in_sign="sssssa{ss}s", + out_sign="s", + method=self._fileJingleRequest, + async=True, + ) def getHandler(self, client): return XEP_0234_handler() @@ -85,12 +102,25 @@ @param content_name(unicode): name of the content @return (unicode): unique progress id """ - return u'{}_{}'.format(session['id'], content_name) + return u"{}_{}".format(session["id"], content_name) # generic methods - def buildFileElement(self, name, file_hash=None, hash_algo=None, size=None, mime_type=None, desc=None, - modified=None, transfer_range=None, path=None, namespace=None, file_elt=None, **kwargs): + def buildFileElement( + self, + name, + file_hash=None, + hash_algo=None, + size=None, + mime_type=None, + desc=None, + modified=None, + transfer_range=None, + path=None, + namespace=None, + file_elt=None, + **kwargs + ): """Generate a <file> element with available metadata @param file_hash(unicode, None): hash of the file @@ -109,26 +139,32 @@ @trigger XEP-0234_buildFileElement(file_elt, extra_args): can be used to extend elements to add """ if file_elt is None: - file_elt = domish.Element((NS_JINGLE_FT, u'file')) - for name, value in ((u'name', name), (u'size', size), ('media-type', mime_type), - (u'desc', desc), (u'path', path), (u'namespace', namespace)): + file_elt = domish.Element((NS_JINGLE_FT, u"file")) + for name, value in ( + (u"name", name), + (u"size", size), + ("media-type", mime_type), + (u"desc", desc), + (u"path", path), + (u"namespace", namespace), + ): if value is not None: file_elt.addElement(name, content=unicode(value)) if modified is not None: if isinstance(modified, int): - file_elt.addElement(u'date', utils.xmpp_date(modified or None)) + file_elt.addElement(u"date", utils.xmpp_date(modified or None)) else: - file_elt.addElement(u'date', modified) - elif 'created' in kwargs: - file_elt.addElement(u'date', utils.xmpp_date(kwargs.pop('created'))) + file_elt.addElement(u"date", modified) + elif "created" in kwargs: + file_elt.addElement(u"date", utils.xmpp_date(kwargs.pop("created"))) - range_elt = file_elt.addElement(u'range') + range_elt = file_elt.addElement(u"range") if transfer_range is not None: if transfer_range.offset is not None: - range_elt[u'offset'] = transfer_range.offset + range_elt[u"offset"] = transfer_range.offset if transfer_range.length is not None: - range_elt[u'length'] = transfer_range.length + range_elt[u"length"] = transfer_range.length if file_hash is not None: if not file_hash: file_elt.addChild(self._hash.buildHashUsedElt()) @@ -136,10 +172,10 @@ file_elt.addChild(self._hash.buildHashElt(file_hash, hash_algo)) elif hash_algo is not None: file_elt.addChild(self._hash.buildHashUsedElt(hash_algo)) - self.host.trigger.point(u'XEP-0234_buildFileElement', file_elt, extra_args=kwargs) + self.host.trigger.point(u"XEP-0234_buildFileElement", file_elt, extra_args=kwargs) if kwargs: for kw in kwargs: - log.debug('ignored keyword: {}'.format(kw)) + log.debug("ignored keyword: {}".format(kw)) return file_elt def buildFileElementFromDict(self, file_data, **kwargs): @@ -151,10 +187,16 @@ if kwargs: file_data = file_data.copy() file_data.update(kwargs) - return self. buildFileElement(**file_data) + return self.buildFileElement(**file_data) - - def parseFileElement(self, file_elt, file_data=None, given=False, parent_elt=None, keep_empty_range=False): + def parseFileElement( + self, + file_elt, + file_data=None, + given=False, + parent_elt=None, + keep_empty_range=False, + ): """Parse a <file> element and file dictionary accordingly @param file_data(dict, None): dict where the data will be set @@ -173,77 +215,105 @@ """ if parent_elt is not None: if file_elt is not None: - raise exceptions.InternalError(u'file_elt must be None if parent_elt is set') + raise exceptions.InternalError( + u"file_elt must be None if parent_elt is set" + ) try: - file_elt = next(parent_elt.elements(NS_JINGLE_FT, u'file')) + file_elt = next(parent_elt.elements(NS_JINGLE_FT, u"file")) except StopIteration: raise exceptions.NotFound() else: if not file_elt or file_elt.uri != NS_JINGLE_FT: - raise exceptions.DataError(u'invalid <file> element: {stanza}'.format(stanza = file_elt.toXml())) + raise exceptions.DataError( + u"invalid <file> element: {stanza}".format(stanza=file_elt.toXml()) + ) if file_data is None: file_data = {} - for name in (u'name', u'desc', u'path', u'namespace'): + for name in (u"name", u"desc", u"path", u"namespace"): try: file_data[name] = unicode(next(file_elt.elements(NS_JINGLE_FT, name))) except StopIteration: pass - - name = file_data.get(u'name') - if name == u'..': + name = file_data.get(u"name") + if name == u"..": # we don't want to go to parent dir when joining to a path - name = u'--' - file_data[u'name'] = name - elif name is not None and u'/' in name or u'\\' in name: - file_data[u'name'] = regex.pathEscape(name) + name = u"--" + file_data[u"name"] = name + elif name is not None and u"/" in name or u"\\" in name: + file_data[u"name"] = regex.pathEscape(name) try: - file_data[u'mime_type'] = unicode(next(file_elt.elements(NS_JINGLE_FT, u'media-type'))) + file_data[u"mime_type"] = unicode( + next(file_elt.elements(NS_JINGLE_FT, u"media-type")) + ) except StopIteration: pass try: - file_data[u'size'] = int(unicode(next(file_elt.elements(NS_JINGLE_FT, u'size')))) + file_data[u"size"] = int( + unicode(next(file_elt.elements(NS_JINGLE_FT, u"size"))) + ) except StopIteration: pass try: - file_data[u'modified'] = date_utils.date_parse(next(file_elt.elements(NS_JINGLE_FT, u'date'))) + file_data[u"modified"] = date_utils.date_parse( + next(file_elt.elements(NS_JINGLE_FT, u"date")) + ) except StopIteration: pass try: - range_elt = file_elt.elements(NS_JINGLE_FT, u'range').next() + range_elt = file_elt.elements(NS_JINGLE_FT, u"range").next() except StopIteration: pass else: - offset = range_elt.getAttribute('offset') - length = range_elt.getAttribute('length') + offset = range_elt.getAttribute("offset") + length = range_elt.getAttribute("length") if offset or length or keep_empty_range: - file_data[u'transfer_range'] = Range(offset=offset, length=length) + file_data[u"transfer_range"] = Range(offset=offset, length=length) - prefix = u'given_' if given else u'' - hash_algo_key, hash_key = u'hash_algo', prefix + u'file_hash' + prefix = u"given_" if given else u"" + hash_algo_key, hash_key = u"hash_algo", prefix + u"file_hash" try: - file_data[hash_algo_key], file_data[hash_key] = self._hash.parseHashElt(file_elt) + file_data[hash_algo_key], file_data[hash_key] = self._hash.parseHashElt( + file_elt + ) except exceptions.NotFound: pass - self.host.trigger.point(u'XEP-0234_parseFileElement', file_elt, file_data) + self.host.trigger.point(u"XEP-0234_parseFileElement", file_elt, file_data) return file_data # bridge methods - def _fileJingleSend(self, peer_jid, filepath, name="", file_desc="", extra=None, profile=C.PROF_KEY_NONE): + def _fileJingleSend( + self, + peer_jid, + filepath, + name="", + file_desc="", + extra=None, + profile=C.PROF_KEY_NONE, + ): client = self.host.getClient(profile) - return self.fileJingleSend(client, jid.JID(peer_jid), filepath, name or None, file_desc or None, extra or None) + return self.fileJingleSend( + client, + jid.JID(peer_jid), + filepath, + name or None, + file_desc or None, + extra or None, + ) @defer.inlineCallbacks - def fileJingleSend(self, client, peer_jid, filepath, name, file_desc=None, extra=None): + def fileJingleSend( + self, client, peer_jid, filepath, name, file_desc=None, extra=None + ): """Send a file using jingle file transfer @param peer_jid(jid.JID): destinee jid @@ -256,25 +326,58 @@ if extra is None: extra = {} if file_desc is not None: - extra['file_desc'] = file_desc - yield self._j.initiate(client, - peer_jid, - [{'app_ns': NS_JINGLE_FT, - 'senders': self._j.ROLE_INITIATOR, - 'app_kwargs': {'filepath': filepath, - 'name': name, - 'extra': extra, - 'progress_id_d': progress_id_d}, - }]) + extra["file_desc"] = file_desc + yield self._j.initiate( + client, + peer_jid, + [ + { + "app_ns": NS_JINGLE_FT, + "senders": self._j.ROLE_INITIATOR, + "app_kwargs": { + "filepath": filepath, + "name": name, + "extra": extra, + "progress_id_d": progress_id_d, + }, + } + ], + ) progress_id = yield progress_id_d defer.returnValue(progress_id) - def _fileJingleRequest(self, peer_jid, filepath, name="", file_hash="", hash_algo="", extra=None, profile=C.PROF_KEY_NONE): + def _fileJingleRequest( + self, + peer_jid, + filepath, + name="", + file_hash="", + hash_algo="", + extra=None, + profile=C.PROF_KEY_NONE, + ): client = self.host.getClient(profile) - return self.fileJingleRequest(client, jid.JID(peer_jid), filepath, name or None, file_hash or None, hash_algo or None, extra or None) + return self.fileJingleRequest( + client, + jid.JID(peer_jid), + filepath, + name or None, + file_hash or None, + hash_algo or None, + extra or None, + ) @defer.inlineCallbacks - def fileJingleRequest(self, client, peer_jid, filepath, name=None, file_hash=None, hash_algo=None, extra=None): + def fileJingleRequest( + self, + client, + peer_jid, + filepath, + name=None, + file_hash=None, + hash_algo=None, + extra=None, + ): """Request a file using jingle file transfer @param peer_jid(jid.JID): destinee jid @@ -289,122 +392,152 @@ if file_hash is not None: if hash_algo is None: raise ValueError(_(u"hash_algo must be set if file_hash is set")) - extra['file_hash'] = file_hash - extra['hash_algo'] = hash_algo + extra["file_hash"] = file_hash + extra["hash_algo"] = hash_algo else: if hash_algo is not None: raise ValueError(_(u"file_hash must be set if hash_algo is set")) - yield self._j.initiate(client, - peer_jid, - [{'app_ns': NS_JINGLE_FT, - 'senders': self._j.ROLE_RESPONDER, - 'app_kwargs': {'filepath': filepath, - 'name': name, - 'extra': extra, - 'progress_id_d': progress_id_d}, - }]) + yield self._j.initiate( + client, + peer_jid, + [ + { + "app_ns": NS_JINGLE_FT, + "senders": self._j.ROLE_RESPONDER, + "app_kwargs": { + "filepath": filepath, + "name": name, + "extra": extra, + "progress_id_d": progress_id_d, + }, + } + ], + ) progress_id = yield progress_id_d defer.returnValue(progress_id) # jingle callbacks - def jingleSessionInit(self, client, session, content_name, filepath, name, extra, progress_id_d): + def jingleSessionInit( + self, client, session, content_name, filepath, name, extra, progress_id_d + ): if extra is None: extra = {} else: if not EXTRA_ALLOWED.issuperset(extra): - raise ValueError(_(u"only the following keys are allowed in extra: {keys}").format( - keys=u', '.join(EXTRA_ALLOWED))) + raise ValueError( + _(u"only the following keys are allowed in extra: {keys}").format( + keys=u", ".join(EXTRA_ALLOWED) + ) + ) progress_id_d.callback(self.getProgressId(session, content_name)) - content_data = session['contents'][content_name] - application_data = content_data['application_data'] - assert 'file_path' not in application_data - application_data['file_path'] = filepath - file_data = application_data['file_data'] = {} - desc_elt = domish.Element((NS_JINGLE_FT, 'description')) + content_data = session["contents"][content_name] + application_data = content_data["application_data"] + assert "file_path" not in application_data + application_data["file_path"] = filepath + file_data = application_data["file_data"] = {} + desc_elt = domish.Element((NS_JINGLE_FT, "description")) file_elt = desc_elt.addElement("file") - if content_data[u'senders'] == self._j.ROLE_INITIATOR: + if content_data[u"senders"] == self._j.ROLE_INITIATOR: # we send a file if name is None: name = os.path.basename(filepath) - file_data[u'date'] = utils.xmpp_date() - file_data[u'desc'] = extra.pop(u'file_desc', u'') - file_data[u'name'] = name + file_data[u"date"] = utils.xmpp_date() + file_data[u"desc"] = extra.pop(u"file_desc", u"") + file_data[u"name"] = name mime_type = mimetypes.guess_type(name, strict=False)[0] if mime_type is not None: - file_data[u'mime_type'] = mime_type - file_data[u'size'] = os.path.getsize(filepath) - if u'namespace' in extra: - file_data[u'namespace'] = extra[u'namespace'] - if u'path' in extra: - file_data[u'path'] = extra[u'path'] - self.buildFileElementFromDict(file_data, file_elt=file_elt, file_hash=u'') + file_data[u"mime_type"] = mime_type + file_data[u"size"] = os.path.getsize(filepath) + if u"namespace" in extra: + file_data[u"namespace"] = extra[u"namespace"] + if u"path" in extra: + file_data[u"path"] = extra[u"path"] + self.buildFileElementFromDict(file_data, file_elt=file_elt, file_hash=u"") else: # we request a file - file_hash = extra.pop(u'file_hash', u'') + file_hash = extra.pop(u"file_hash", u"") if not name and not file_hash: - raise ValueError(_(u'you need to provide at least name or file hash')) + raise ValueError(_(u"you need to provide at least name or file hash")) if name: - file_data[u'name'] = name + file_data[u"name"] = name if file_hash: - file_data[u'file_hash'] = file_hash - file_data[u'hash_algo'] = extra[u'hash_algo'] + file_data[u"file_hash"] = file_hash + file_data[u"hash_algo"] = extra[u"hash_algo"] else: - file_data[u'hash_algo'] = self._hash.getDefaultAlgo() - if u'namespace' in extra: - file_data[u'namespace'] = extra[u'namespace'] - if u'path' in extra: - file_data[u'path'] = extra[u'path'] + file_data[u"hash_algo"] = self._hash.getDefaultAlgo() + if u"namespace" in extra: + file_data[u"namespace"] = extra[u"namespace"] + if u"path" in extra: + file_data[u"path"] = extra[u"path"] self.buildFileElementFromDict(file_data, file_elt=file_elt) return desc_elt def jingleRequestConfirmation(self, client, action, session, content_name, desc_elt): """This method request confirmation for a jingle session""" - content_data = session['contents'][content_name] - senders = content_data[u'senders'] + content_data = session["contents"][content_name] + senders = content_data[u"senders"] if senders not in (self._j.ROLE_INITIATOR, self._j.ROLE_RESPONDER): log.warning(u"Bad sender, assuming initiator") - senders = content_data[u'senders'] = self._j.ROLE_INITIATOR + senders = content_data[u"senders"] = self._j.ROLE_INITIATOR # first we grab file informations try: - file_elt = desc_elt.elements(NS_JINGLE_FT, 'file').next() + file_elt = desc_elt.elements(NS_JINGLE_FT, "file").next() except StopIteration: raise failure.Failure(exceptions.DataError) - file_data = {'progress_id': self.getProgressId(session, content_name)} + file_data = {"progress_id": self.getProgressId(session, content_name)} if senders == self._j.ROLE_RESPONDER: # we send the file - return self._fileSendingRequestConf(client, session, content_data, content_name, file_data, file_elt) + return self._fileSendingRequestConf( + client, session, content_data, content_name, file_data, file_elt + ) else: # we receive the file - return self._fileReceivingRequestConf(client, session, content_data, content_name, file_data, file_elt) + return self._fileReceivingRequestConf( + client, session, content_data, content_name, file_data, file_elt + ) @defer.inlineCallbacks - def _fileSendingRequestConf(self, client, session, content_data, content_name, file_data, file_elt): + def _fileSendingRequestConf( + self, client, session, content_data, content_name, file_data, file_elt + ): """parse file_elt, and handle file retrieving/permission checking""" self.parseFileElement(file_elt, file_data) - content_data['application_data']['file_data'] = file_data - finished_d = content_data['finished_d'] = defer.Deferred() + content_data["application_data"]["file_data"] = file_data + finished_d = content_data["finished_d"] = defer.Deferred() # confirmed_d is a deferred returning confimed value (only used if cont is False) - cont, confirmed_d = self.host.trigger.returnPoint("XEP-0234_fileSendingRequest", client, session, content_data, content_name, file_data, file_elt) + cont, confirmed_d = self.host.trigger.returnPoint( + "XEP-0234_fileSendingRequest", + client, + session, + content_data, + content_name, + file_data, + file_elt, + ) if not cont: confirmed = yield confirmed_d if confirmed: args = [client, session, content_name, content_data] - finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args) + finished_d.addCallbacks( + self._finishedCb, self._finishedEb, args, None, args + ) defer.returnValue(confirmed) - log.warning(_(u'File continue is not implemented yet')) + log.warning(_(u"File continue is not implemented yet")) defer.returnValue(False) - def _fileReceivingRequestConf(self, client, session, content_data, content_name, file_data, file_elt): + def _fileReceivingRequestConf( + self, client, session, content_data, content_name, file_data, file_elt + ): """parse file_elt, and handle user permission/file opening""" self.parseFileElement(file_elt, file_data, given=True) try: - hash_algo, file_data['given_file_hash'] = self._hash.parseHashElt(file_elt) + hash_algo, file_data["given_file_hash"] = self._hash.parseHashElt(file_elt) except exceptions.NotFound: try: hash_algo = self._hash.parseHashUsedElt(file_elt) @@ -412,88 +545,94 @@ raise failure.Failure(exceptions.DataError) if hash_algo is not None: - file_data['hash_algo'] = hash_algo - file_data['hash_hasher'] = hasher = self._hash.getHasher(hash_algo) - file_data['data_cb'] = lambda data: hasher.update(data) + file_data["hash_algo"] = hash_algo + file_data["hash_hasher"] = hasher = self._hash.getHasher(hash_algo) + file_data["data_cb"] = lambda data: hasher.update(data) try: - file_data['size'] = int(file_data['size']) + file_data["size"] = int(file_data["size"]) except ValueError: raise failure.Failure(exceptions.DataError) - name = file_data['name'] - if '/' in name or '\\' in name: - log.warning(u"File name contain path characters, we replace them: {}".format(name)) - file_data['name'] = name.replace('/', '_').replace('\\', '_') + name = file_data["name"] + if "/" in name or "\\" in name: + log.warning( + u"File name contain path characters, we replace them: {}".format(name) + ) + file_data["name"] = name.replace("/", "_").replace("\\", "_") - content_data['application_data']['file_data'] = file_data + content_data["application_data"]["file_data"] = file_data # now we actualy request permission to user def gotConfirmation(confirmed): if confirmed: args = [client, session, content_name, content_data] - finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args) + finished_d.addCallbacks( + self._finishedCb, self._finishedEb, args, None, args + ) return confirmed # deferred to track end of transfer - finished_d = content_data['finished_d'] = defer.Deferred() - d = self._f.getDestDir(client, session['peer_jid'], content_data, file_data, stream_object=True) + finished_d = content_data["finished_d"] = defer.Deferred() + d = self._f.getDestDir( + client, session["peer_jid"], content_data, file_data, stream_object=True + ) d.addCallback(gotConfirmation) return d def jingleHandler(self, client, action, session, content_name, desc_elt): - content_data = session['contents'][content_name] - application_data = content_data['application_data'] + content_data = session["contents"][content_name] + application_data = content_data["application_data"] if action in (self._j.A_ACCEPTED_ACK,): pass elif action == self._j.A_SESSION_INITIATE: - file_elt = desc_elt.elements(NS_JINGLE_FT, 'file').next() + file_elt = desc_elt.elements(NS_JINGLE_FT, "file").next() try: - file_elt.elements(NS_JINGLE_FT, 'range').next() + file_elt.elements(NS_JINGLE_FT, "range").next() except StopIteration: # initiator doesn't manage <range>, but we do so we advertise it - # FIXME: to be checked + # FIXME: to be checked log.debug("adding <range> element") - file_elt.addElement('range') + file_elt.addElement("range") elif action == self._j.A_SESSION_ACCEPT: - assert not 'stream_object' in content_data - file_data = application_data['file_data'] - file_path = application_data['file_path'] - senders = content_data[u'senders'] - if senders != session[u'role']: + assert not "stream_object" in content_data + file_data = application_data["file_data"] + file_path = application_data["file_path"] + senders = content_data[u"senders"] + if senders != session[u"role"]: # we are receiving the file try: # did the responder specified the size of the file? - file_elt = next(desc_elt.elements(NS_JINGLE_FT, u'file')) - size_elt = next(file_elt.elements(NS_JINGLE_FT, u'size')) + file_elt = next(desc_elt.elements(NS_JINGLE_FT, u"file")) + size_elt = next(file_elt.elements(NS_JINGLE_FT, u"size")) size = int(unicode(size_elt)) except (StopIteration, ValueError): size = None # XXX: hash security is not critical here, so we just take the higher mandatory one - hasher = file_data['hash_hasher'] = self._hash.getHasher() - content_data['stream_object'] = stream.FileStreamObject( + hasher = file_data["hash_hasher"] = self._hash.getHasher() + content_data["stream_object"] = stream.FileStreamObject( self.host, client, file_path, - mode='wb', + mode="wb", uid=self.getProgressId(session, content_name), size=size, data_cb=lambda data: hasher.update(data), - ) + ) else: # we are sending the file - size = file_data['size'] + size = file_data["size"] # XXX: hash security is not critical here, so we just take the higher mandatory one - hasher = file_data['hash_hasher'] = self._hash.getHasher() - content_data['stream_object'] = stream.FileStreamObject( + hasher = file_data["hash_hasher"] = self._hash.getHasher() + content_data["stream_object"] = stream.FileStreamObject( self.host, client, file_path, uid=self.getProgressId(session, content_name), size=size, data_cb=lambda data: hasher.update(data), - ) - finished_d = content_data['finished_d'] = defer.Deferred() + ) + finished_d = content_data["finished_d"] = defer.Deferred() args = [client, session, content_name, content_data] finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args) else: @@ -506,33 +645,42 @@ manage checksum, and ignore <received/> element """ # TODO: manage <received/> element - content_data = session['contents'][content_name] + content_data = session["contents"][content_name] elts = [elt for elt in jingle_elt.elements() if elt.uri == NS_JINGLE_FT] if not elts: return for elt in elts: - if elt.name == 'received': + if elt.name == "received": pass - elif elt.name == 'checksum': + elif elt.name == "checksum": # we have received the file hash, we need to parse it - if content_data['senders'] == session['role']: - log.warning(u"unexpected checksum received while we are the file sender") + if content_data["senders"] == session["role"]: + log.warning( + u"unexpected checksum received while we are the file sender" + ) raise exceptions.DataError - info_content_name = elt['name'] + info_content_name = elt["name"] if info_content_name != content_name: # it was for an other content... return - file_data = content_data['application_data']['file_data'] + file_data = content_data["application_data"]["file_data"] try: - file_elt = elt.elements((NS_JINGLE_FT, 'file')).next() + file_elt = elt.elements((NS_JINGLE_FT, "file")).next() except StopIteration: raise exceptions.DataError - algo, file_data['given_file_hash'] = self._hash.parseHashElt(file_elt) - if algo != file_data.get('hash_algo'): - log.warning(u"Hash algorithm used in given hash ({peer_algo}) doesn't correspond to the one we have used ({our_algo}) [{profile}]" - .format(peer_algo=algo, our_algo=file_data.get('hash_algo'), profile=client.profile)) + algo, file_data["given_file_hash"] = self._hash.parseHashElt(file_elt) + if algo != file_data.get("hash_algo"): + log.warning( + u"Hash algorithm used in given hash ({peer_algo}) doesn't correspond to the one we have used ({our_algo}) [{profile}]".format( + peer_algo=algo, + our_algo=file_data.get("hash_algo"), + profile=client.profile, + ) + ) else: - self._receiverTryTerminate(client, session, content_name, content_data) + self._receiverTryTerminate( + client, session, content_name, content_data + ) else: raise NotImplementedError @@ -540,23 +688,27 @@ if jingle_elt.decline: # progress is the only way to tell to frontends that session has been declined progress_id = self.getProgressId(session, content_name) - self.host.bridge.progressError(progress_id, C.PROGRESS_ERROR_DECLINED, client.profile) + self.host.bridge.progressError( + progress_id, C.PROGRESS_ERROR_DECLINED, client.profile + ) def _sendCheckSum(self, client, session, content_name, content_data): """Send the session-info with the hash checksum""" - file_data = content_data['application_data']['file_data'] - hasher = file_data['hash_hasher'] + file_data = content_data["application_data"]["file_data"] + hasher = file_data["hash_hasher"] hash_ = hasher.hexdigest() log.debug(u"Calculated hash: {}".format(hash_)) iq_elt, jingle_elt = self._j.buildSessionInfo(client, session) - checksum_elt = jingle_elt.addElement((NS_JINGLE_FT, 'checksum')) - checksum_elt['creator'] = content_data['creator'] - checksum_elt['name'] = content_name - file_elt = checksum_elt.addElement('file') + checksum_elt = jingle_elt.addElement((NS_JINGLE_FT, "checksum")) + checksum_elt["creator"] = content_data["creator"] + checksum_elt["name"] = content_name + file_elt = checksum_elt.addElement("file") file_elt.addChild(self._hash.buildHashElt(hash_)) iq_elt.send() - def _receiverTryTerminate(self, client, session, content_name, content_data, last_try=False): + def _receiverTryTerminate( + self, client, session, content_name, content_data, last_try=False + ): """Try to terminate the session This method must only be used by the receiver. @@ -565,70 +717,85 @@ @param last_try(bool): if True this mean than session must be terminated even given hash is not available @return (bool): True if session was terminated """ - if not content_data.get('transfer_finished', False): + if not content_data.get("transfer_finished", False): return False - file_data = content_data['application_data']['file_data'] - given_hash = file_data.get('given_file_hash') + file_data = content_data["application_data"]["file_data"] + given_hash = file_data.get("given_file_hash") if given_hash is None: if last_try: - log.warning(u"sender didn't sent hash checksum, we can't check the file [{profile}]".format(profile=client.profile)) + log.warning( + u"sender didn't sent hash checksum, we can't check the file [{profile}]".format( + profile=client.profile + ) + ) self._j.delayedContentTerminate(client, session, content_name) - content_data['stream_object'].close() + content_data["stream_object"].close() return True return False - hasher = file_data['hash_hasher'] + hasher = file_data["hash_hasher"] hash_ = hasher.hexdigest() if hash_ == given_hash: log.info(u"Hash checked, file was successfully transfered: {}".format(hash_)) - progress_metadata = {'hash': hash_, - 'hash_algo': file_data['hash_algo'], - 'hash_verified': C.BOOL_TRUE - } + progress_metadata = { + "hash": hash_, + "hash_algo": file_data["hash_algo"], + "hash_verified": C.BOOL_TRUE, + } error = None else: log.warning(u"Hash mismatch, the file was not transfered correctly") - progress_metadata=None + progress_metadata = None error = u"Hash mismatch: given={algo}:{given}, calculated={algo}:{our}".format( - algo = file_data['hash_algo'], - given = given_hash, - our = hash_) + algo=file_data["hash_algo"], given=given_hash, our=hash_ + ) self._j.delayedContentTerminate(client, session, content_name) - content_data['stream_object'].close(progress_metadata, error) + content_data["stream_object"].close(progress_metadata, error) # we may have the last_try timer still active, so we try to cancel it try: - content_data['last_try_timer'].cancel() + content_data["last_try_timer"].cancel() except (KeyError, internet_error.AlreadyCalled): pass return True def _finishedCb(self, dummy, client, session, content_name, content_data): log.info(u"File transfer terminated") - if content_data['senders'] != session['role']: + if content_data["senders"] != session["role"]: # we terminate the session only if we are the receiver, # as recommanded in XEP-0234 §2 (after example 6) - content_data['transfer_finished'] = True - if not self._receiverTryTerminate(client, session, content_name, content_data): + content_data["transfer_finished"] = True + if not self._receiverTryTerminate( + client, session, content_name, content_data + ): # we have not received the hash yet, we wait 5 more seconds - content_data['last_try_timer'] = reactor.callLater( - 5, self._receiverTryTerminate, client, session, content_name, content_data, last_try=True) + content_data["last_try_timer"] = reactor.callLater( + 5, + self._receiverTryTerminate, + client, + session, + content_name, + content_data, + last_try=True, + ) else: # we are the sender, we send the checksum self._sendCheckSum(client, session, content_name, content_data) - content_data['stream_object'].close() + content_data["stream_object"].close() def _finishedEb(self, failure, client, session, content_name, content_data): log.warning(u"Error while streaming file: {}".format(failure)) - content_data['stream_object'].close() - self._j.contentTerminate(client, session, content_name, reason=self._j.REASON_FAILED_TRANSPORT) + content_data["stream_object"].close() + self._j.contentTerminate( + client, session, content_name, reason=self._j.REASON_FAILED_TRANSPORT + ) class XEP_0234_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_JINGLE_FT)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0249.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0249.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core.constants import Const as C from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) from sat.tools import xml_tools from twisted.words.xish import domish @@ -36,8 +37,8 @@ except ImportError: from wokkel.subprotocols import XMPPHandler -MESSAGE = '/message' -NS_DIRECT_MUC_INVITATION = 'jabber:x:conference' +MESSAGE = "/message" +NS_DIRECT_MUC_INVITATION = "jabber:x:conference" DIRECT_MUC_INVITATION_REQUEST = MESSAGE + '/x[@xmlns="' + NS_DIRECT_MUC_INVITATION + '"]' AUTOJOIN_KEY = "Misc" AUTOJOIN_NAME = "Auto-join MUC on invitation" @@ -52,7 +53,7 @@ C.PI_RECOMMENDATIONS: [C.TEXT_CMDS], C.PI_MAIN: "XEP_0249", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Direct MUC Invitations""") + C.PI_DESCRIPTION: _("""Implementation of Direct MUC Invitations"""), } @@ -69,20 +70,26 @@ </individual> </params> """ % { - 'category_name': AUTOJOIN_KEY, - 'category_label': _("Misc"), - 'param_name': AUTOJOIN_NAME, - 'param_label': _("Auto-join MUC on invitation"), - 'param_options': '\n'.join(['<option value="%s" %s/>' % \ - (value, 'selected="true"' if value == AUTOJOIN_VALUES[0] else '') \ - for value in AUTOJOIN_VALUES]) + "category_name": AUTOJOIN_KEY, + "category_label": _("Misc"), + "param_name": AUTOJOIN_NAME, + "param_label": _("Auto-join MUC on invitation"), + "param_options": "\n".join( + [ + '<option value="%s" %s/>' + % (value, 'selected="true"' if value == AUTOJOIN_VALUES[0] else "") + for value in AUTOJOIN_VALUES + ] + ), } def __init__(self, host): log.info(_("Plugin XEP_0249 initialization")) self.host = host host.memory.updateParams(self.params) - host.bridge.addMethod("inviteMUC", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._invite) + host.bridge.addMethod( + "inviteMUC", ".plugin", in_sign="ssa{ss}s", out_sign="", method=self._invite + ) try: self.host.plugins[C.TEXT_CMDS].registerTextCommands(self) except KeyError: @@ -99,7 +106,7 @@ @param roomId: name of the room @param profile_key: %(doc_profile_key)s """ - #TODO: check parameters validity + # TODO: check parameters validity client = self.host.getClient(profile_key) self.invite(client, jid.JID(guest_jid_s), jid.JID(room_jid_s, options)) @@ -110,16 +117,16 @@ @param room(jid.JID): jid of the room where the user is invited @param options(dict): attribute with extra info (reason, password) as in #XEP-0249 """ - message = domish.Element((None, 'message')) + message = domish.Element((None, "message")) message["to"] = guest.full() - x_elt = message.addElement((NS_DIRECT_MUC_INVITATION, 'x')) - x_elt['jid'] = room.userhost() + x_elt = message.addElement((NS_DIRECT_MUC_INVITATION, "x")) + x_elt["jid"] = room.userhost() for key, value in options.iteritems(): - if key not in ('password', 'reason', 'thread'): + if key not in ("password", "reason", "thread"): log.warning(u"Ignoring invalid invite option: {}".format(key)) continue x_elt[key] = value - # there is not body in this message, so we can use directly send() + # there is not body in this message, so we can use directly send() client.send(message) def _accept(self, room_jid, profile_key=C.PROF_KEY_NONE): @@ -128,7 +135,10 @@ @param room (jid.JID): JID of the room """ client = self.host.getClient(profile_key) - log.info(_(u'Invitation accepted for room %(room)s [%(profile)s]') % {'room': room_jid.userhost(), 'profile': client.profile}) + log.info( + _(u"Invitation accepted for room %(room)s [%(profile)s]") + % {"room": room_jid.userhost(), "profile": client.profile} + ) d = self.host.plugins["XEP-0045"].join(client, room_jid, client.jid.user, {}) return d @@ -140,10 +150,13 @@ """ client = self.host.getClient(profile) try: - room_jid_s = message.firstChildElement()['jid'] - log.info(_(u'Invitation received for room %(room)s [%(profile)s]') % {'room': room_jid_s, 'profile': profile}) + room_jid_s = message.firstChildElement()["jid"] + log.info( + _(u"Invitation received for room %(room)s [%(profile)s]") + % {"room": room_jid_s, "profile": profile} + ) except: - log.error(_('Error while parsing invitation')) + log.error(_("Error while parsing invitation")) return from_jid_s = message["from"] room_jid = jid.JID(room_jid_s) @@ -152,21 +165,32 @@ except exceptions.NotFound: pass else: - log.info(_(u"Invitation silently discarded because user is already in the room.")) + log.info( + _(u"Invitation silently discarded because user is already in the room.") + ) return - autojoin = self.host.memory.getParamA(AUTOJOIN_NAME, AUTOJOIN_KEY, profile_key=profile) + autojoin = self.host.memory.getParamA( + AUTOJOIN_NAME, AUTOJOIN_KEY, profile_key=profile + ) if autojoin == "always": self._accept(room_jid, profile) elif autojoin == "never": - msg = D_("An invitation from %(user)s to join the room %(room)s has been declined according to your personal settings.") % {'user': from_jid_s, 'room': room_jid_s} + msg = D_( + "An invitation from %(user)s to join the room %(room)s has been declined according to your personal settings." + ) % {"user": from_jid_s, "room": room_jid_s} title = D_("MUC invitation") xml_tools.quickNote(self.host, client, msg, title, C.XMLUI_DATA_LVL_INFO) else: # leave the default value here - confirm_msg = D_("You have been invited by %(user)s to join the room %(room)s. Do you accept?") % {'user': from_jid_s, 'room': room_jid_s} + confirm_msg = D_( + "You have been invited by %(user)s to join the room %(room)s. Do you accept?" + ) % {"user": from_jid_s, "room": room_jid_s} confirm_title = D_("MUC invitation") - d = xml_tools.deferConfirm(self.host, confirm_msg, confirm_title, profile=profile) + d = xml_tools.deferConfirm( + self.host, confirm_msg, confirm_title, profile=profile + ) + def accept_cb(accepted): if accepted: self._accept(room_jid, profile) @@ -184,7 +208,9 @@ try: contact_jid = jid.JID(contact_jid_s) except (RuntimeError, jid.InvalidFormat, AttributeError): - feedback = _(u"You must provide a valid JID to invite, like in '/invite contact@{host}'").format(host=my_host) + feedback = _( + u"You must provide a valid JID to invite, like in '/invite contact@{host}'" + ).format(host=my_host) self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback, mess_data) return False if not contact_jid.user: @@ -201,10 +227,14 @@ self.host = plugin_parent.host def connectionInitialized(self): - self.xmlstream.addObserver(DIRECT_MUC_INVITATION_REQUEST, self.plugin_parent.onInvitation, profile=self.parent.profile) + self.xmlstream.addObserver( + DIRECT_MUC_INVITATION_REQUEST, + self.plugin_parent.onInvitation, + profile=self.parent.profile, + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_DIRECT_MUC_INVITATION)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0260.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0260.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from wokkel import disco, iwokkel @@ -35,7 +36,7 @@ from wokkel.subprotocols import XMPPHandler -NS_JINGLE_S5B = 'urn:xmpp:jingle:transports:s5b:1' +NS_JINGLE_S5B = "urn:xmpp:jingle:transports:s5b:1" PLUGIN_INFO = { C.PI_NAME: "Jingle SOCKS5 Bytestreams", @@ -44,15 +45,14 @@ C.PI_MODES: C.PLUG_MODE_BOTH, C.PI_PROTOCOLS: ["XEP-0260"], C.PI_DEPENDENCIES: ["XEP-0166", "XEP-0065"], - C.PI_RECOMMENDATIONS: ["XEP-0261"], # needed for fallback + C.PI_RECOMMENDATIONS: ["XEP-0261"], # needed for fallback C.PI_MAIN: "XEP_0260", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Jingle SOCKS5 Bytestreams""") + C.PI_DESCRIPTION: _("""Implementation of Jingle SOCKS5 Bytestreams"""), } class ProxyError(Exception): - def __str__(self): return "an error happened while trying to use the proxy" @@ -63,8 +63,8 @@ def __init__(self, host): log.info(_("plugin Jingle SOCKS5 Bytestreams")) self.host = host - self._j = host.plugins["XEP-0166"] # shortcut to access jingle - self._s5b = host.plugins["XEP-0065"] # and socks5 bytestream + self._j = host.plugins["XEP-0166"] # shortcut to access jingle + self._s5b = host.plugins["XEP-0065"] # and socks5 bytestream try: self._jingle_ibb = host.plugins["XEP-0261"] except KeyError: @@ -81,14 +81,14 @@ @return (list[plugin_xep_0065.Candidate): list of parsed candidates """ candidates = [] - for candidate_elt in transport_elt.elements(NS_JINGLE_S5B, 'candidate'): + for candidate_elt in transport_elt.elements(NS_JINGLE_S5B, "candidate"): try: - cid = candidate_elt['cid'] - host = candidate_elt['host'] - jid_= jid.JID(candidate_elt['jid']) - port = int(candidate_elt.getAttribute('port', 1080)) - priority = int(candidate_elt['priority']) - type_ = candidate_elt.getAttribute('type', self._s5b.TYPE_DIRECT) + cid = candidate_elt["cid"] + host = candidate_elt["host"] + jid_ = jid.JID(candidate_elt["jid"]) + port = int(candidate_elt.getAttribute("port", 1080)) + priority = int(candidate_elt["priority"]) + type_ = candidate_elt.getAttribute("type", self._s5b.TYPE_DIRECT) except (KeyError, ValueError): raise exceptions.DataError() candidate = self._s5b.Candidate(host, port, type_, priority, jid_, cid) @@ -106,38 +106,51 @@ @param mode(str, None): 'tcp' or 'udp', or None to have no attribute @return (domish.Element): parent <transport> element where <candidate> elements must be added """ - proxy = next((candidate for candidate in candidates if candidate.type == self._s5b.TYPE_PROXY), None) + proxy = next( + ( + candidate + for candidate in candidates + if candidate.type == self._s5b.TYPE_PROXY + ), + None, + ) transport_elt = domish.Element((NS_JINGLE_S5B, "transport")) - transport_elt['sid'] = sid + transport_elt["sid"] = sid if proxy is not None: - transport_elt['dstaddr'] = session_hash + transport_elt["dstaddr"] = session_hash if mode is not None: - transport_elt['mode'] = 'tcp' # XXX: we only manage tcp for now + transport_elt["mode"] = "tcp" # XXX: we only manage tcp for now for candidate in candidates: log.debug(u"Adding candidate: {}".format(candidate)) - candidate_elt = transport_elt.addElement('candidate', NS_JINGLE_S5B) + candidate_elt = transport_elt.addElement("candidate", NS_JINGLE_S5B) if candidate.id is None: candidate.id = unicode(uuid.uuid4()) - candidate_elt['cid'] = candidate.id - candidate_elt['host'] = candidate.host - candidate_elt['jid'] = candidate.jid.full() - candidate_elt['port'] = unicode(candidate.port) - candidate_elt['priority'] = unicode(candidate.priority) - candidate_elt['type'] = candidate.type + candidate_elt["cid"] = candidate.id + candidate_elt["host"] = candidate.host + candidate_elt["jid"] = candidate.jid.full() + candidate_elt["port"] = unicode(candidate.port) + candidate_elt["priority"] = unicode(candidate.priority) + candidate_elt["type"] = candidate.type return transport_elt @defer.inlineCallbacks def jingleSessionInit(self, client, session, content_name): - content_data = session['contents'][content_name] - transport_data = content_data['transport_data'] - sid = transport_data['sid'] = unicode(uuid.uuid4()) - session_hash = transport_data['session_hash'] = self._s5b.getSessionHash(client.jid, session['peer_jid'], sid) - transport_data['peer_session_hash'] = self._s5b.getSessionHash(session['peer_jid'], client.jid, sid) # requester and target are inversed for peer candidates - transport_data['stream_d'] = self._s5b.registerHash(client, session_hash, None) - candidates = transport_data['candidates'] = yield self._s5b.getCandidates(client) - mode = 'tcp' # XXX: we only manage tcp for now - transport_elt = self._buildCandidates(session, candidates, sid, session_hash, client, mode) + content_data = session["contents"][content_name] + transport_data = content_data["transport_data"] + sid = transport_data["sid"] = unicode(uuid.uuid4()) + session_hash = transport_data["session_hash"] = self._s5b.getSessionHash( + client.jid, session["peer_jid"], sid + ) + transport_data["peer_session_hash"] = self._s5b.getSessionHash( + session["peer_jid"], client.jid, sid + ) # requester and target are inversed for peer candidates + transport_data["stream_d"] = self._s5b.registerHash(client, session_hash, None) + candidates = transport_data["candidates"] = yield self._s5b.getCandidates(client) + mode = "tcp" # XXX: we only manage tcp for now + transport_elt = self._buildCandidates( + session, candidates, sid, session_hash, client, mode + ) defer.returnValue(transport_elt) @@ -147,9 +160,11 @@ cf XEP-0260 § 2.4 """ # now that the proxy is activated, we have to inform other peer - iq_elt, transport_elt = self._j.buildAction(client, self._j.A_TRANSPORT_INFO, session, content_name) - activated_elt = transport_elt.addElement('activated') - activated_elt['cid'] = candidate.id + iq_elt, transport_elt = self._j.buildAction( + client, self._j.A_TRANSPORT_INFO, session, content_name + ) + activated_elt = transport_elt.addElement("activated") + activated_elt["cid"] = candidate.id iq_elt.send() def _proxyActivatedEb(self, stanza_error, client, candidate, session, content_name): @@ -159,14 +174,21 @@ """ # TODO: fallback to IBB # now that the proxy is activated, we have to inform other peer - iq_elt, transport_elt = self._j.buildAction(client, self._j.A_TRANSPORT_INFO, session, content_name) - transport_elt.addElement('proxy-error') + iq_elt, transport_elt = self._j.buildAction( + client, self._j.A_TRANSPORT_INFO, session, content_name + ) + transport_elt.addElement("proxy-error") iq_elt.send() - log.warning(u"Can't activate proxy, we need to fallback to IBB: {reason}" - .format(reason = stanza_error.value.condition)) + log.warning( + u"Can't activate proxy, we need to fallback to IBB: {reason}".format( + reason=stanza_error.value.condition + ) + ) self.doFallback(session, content_name, client) - def _foundPeerCandidate(self, candidate, session, transport_data, content_name, client): + def _foundPeerCandidate( + self, candidate, session, transport_data, content_name, client + ): """Called when the best candidate from other peer is found @param candidate(XEP_0065.Candidate, None): selected candidate, @@ -177,22 +199,24 @@ @param client(unicode): %(doc_client)s """ - transport_data['best_candidate'] = candidate + transport_data["best_candidate"] = candidate # we need to disconnect all non selected candidates before removing them - for c in transport_data['peer_candidates']: + for c in transport_data["peer_candidates"]: if c is None or c is candidate: continue c.discard() - del transport_data['peer_candidates'] - iq_elt, transport_elt = self._j.buildAction(client, self._j.A_TRANSPORT_INFO, session, content_name) + del transport_data["peer_candidates"] + iq_elt, transport_elt = self._j.buildAction( + client, self._j.A_TRANSPORT_INFO, session, content_name + ) if candidate is None: log.warning(u"Can't connect to any peer candidate") - candidate_elt = transport_elt.addElement('candidate-error') + candidate_elt = transport_elt.addElement("candidate-error") else: log.info(u"Found best peer candidate: {}".format(unicode(candidate))) - candidate_elt = transport_elt.addElement('candidate-used') - candidate_elt['cid'] = candidate.id - iq_elt.send() # TODO: check result stanza + candidate_elt = transport_elt.addElement("candidate-used") + candidate_elt["cid"] = candidate.id + iq_elt.send() # TODO: check result stanza self._checkCandidates(session, content_name, transport_data, client) def _checkCandidates(self, session, content_name, transport_data, client): @@ -204,14 +228,14 @@ @param transport_data(dict): transport data @param client(unicode): %(doc_client)s """ - content_data = session['contents'][content_name] + content_data = session["contents"][content_name] try: - best_candidate = transport_data['best_candidate'] + best_candidate = transport_data["best_candidate"] except KeyError: # we have not our best candidate yet return try: - peer_best_candidate = transport_data['peer_best_candidate'] + peer_best_candidate = transport_data["peer_best_candidate"] except KeyError: # we have not peer best candidate yet return @@ -222,13 +246,17 @@ else: if best_candidate.priority == peer_best_candidate.priority: # same priority, we choose initiator one according to XEP-0260 §2.4 #4 - log.debug(u"Candidates have same priority, we select the one choosed by initiator") - if session['initiator'] == client.jid: + log.debug( + u"Candidates have same priority, we select the one choosed by initiator" + ) + if session["initiator"] == client.jid: choosed_candidate = best_candidate else: choosed_candidate = peer_best_candidate else: - choosed_candidate = max(best_candidate, peer_best_candidate, key=lambda c:c.priority) + choosed_candidate = max( + best_candidate, peer_best_candidate, key=lambda c: c.priority + ) if choosed_candidate is None: log.warning(u"Socks5 negociation failed, we need to fallback to IBB") @@ -241,34 +269,49 @@ # than also mean that best_candidate must be discarded ! try: best_candidate.discard() - except AttributeError: # but it can be None + except AttributeError: # but it can be None pass else: our_candidate = False - log.info(u"Socks5 negociation successful, {who} candidate will be used: {candidate}".format( - who = u'our' if our_candidate else u'other peer', - candidate = choosed_candidate)) - del transport_data['best_candidate'] - del transport_data['peer_best_candidate'] + log.info( + u"Socks5 negociation successful, {who} candidate will be used: {candidate}".format( + who=u"our" if our_candidate else u"other peer", + candidate=choosed_candidate, + ) + ) + del transport_data["best_candidate"] + del transport_data["peer_best_candidate"] if choosed_candidate.type == self._s5b.TYPE_PROXY: # the stream transfer need to wait for proxy activation # (see XEP-0260 § 2.4) if our_candidate: - d = self._s5b.connectCandidate(client, choosed_candidate, transport_data['session_hash']) - d.addCallback(lambda dummy: choosed_candidate.activate(transport_data['sid'], session['peer_jid'], client)) + d = self._s5b.connectCandidate( + client, choosed_candidate, transport_data["session_hash"] + ) + d.addCallback( + lambda dummy: choosed_candidate.activate( + transport_data["sid"], session["peer_jid"], client + ) + ) args = [client, choosed_candidate, session, content_name] - d.addCallbacks(self._proxyActivatedCb, self._proxyActivatedEb, args, None, args) + d.addCallbacks( + self._proxyActivatedCb, self._proxyActivatedEb, args, None, args + ) else: # this Deferred will be called when we'll receive activation confirmation from other peer - d = transport_data['activation_d'] = defer.Deferred() + d = transport_data["activation_d"] = defer.Deferred() else: d = defer.succeed(None) - if content_data['senders'] == session['role']: + if content_data["senders"] == session["role"]: # we can now start the stream transfer (or start it after proxy activation) - d.addCallback(lambda dummy: choosed_candidate.startTransfer(transport_data['session_hash'])) + d.addCallback( + lambda dummy: choosed_candidate.startTransfer( + transport_data["session_hash"] + ) + ) d.addErrback(self._startEb, session, content_name, client) def _startEb(self, fail, session, content_name, client): @@ -283,7 +326,9 @@ log.warning(u"Cant start transfert, we'll try fallback method: {}".format(reason)) self.doFallback(session, content_name, client) - def _candidateInfo(self, candidate_elt, session, content_name, transport_data, client): + def _candidateInfo( + self, candidate_elt, session, content_name, transport_data, client + ): """Called when best candidate has been received from peer (or if none is working) @param candidate_elt(domish.Element): candidate-used or candidate-error element @@ -293,34 +338,40 @@ @param transport_data(dict): transport data @param client(unicode): %(doc_client)s """ - if candidate_elt.name == 'candidate-error': + if candidate_elt.name == "candidate-error": # candidate-error, no candidate worked - transport_data['peer_best_candidate'] = None + transport_data["peer_best_candidate"] = None else: # candidate-used, one candidate was choosed try: - cid = candidate_elt.attributes['cid'] + cid = candidate_elt.attributes["cid"] except KeyError: log.warning(u"No cid found in <candidate-used>") raise exceptions.DataError try: - candidate = (c for c in transport_data['candidates'] if c.id == cid).next() + candidate = ( + c for c in transport_data["candidates"] if c.id == cid + ).next() except StopIteration: log.warning(u"Given cid doesn't correspond to any known candidate !") - raise exceptions.DataError # TODO: send an error to other peer, and use better exception + raise exceptions.DataError # TODO: send an error to other peer, and use better exception except KeyError: # a transport-info can also be intentionaly sent too early by other peer # but there is little probability - log.error(u'"candidates" key doesn\'t exists in transport_data, it should at this point') + log.error( + u'"candidates" key doesn\'t exists in transport_data, it should at this point' + ) raise exceptions.InternalError # at this point we have the candidate choosed by other peer - transport_data['peer_best_candidate'] = candidate + transport_data["peer_best_candidate"] = candidate log.info(u"Other peer best candidate: {}".format(candidate)) - del transport_data['candidates'] + del transport_data["candidates"] self._checkCandidates(session, content_name, transport_data, client) - def _proxyActivationInfo(self, proxy_elt, session, content_name, transport_data, client): + def _proxyActivationInfo( + self, proxy_elt, session, content_name, transport_data, client + ): """Called when proxy has been activated (or has sent an error) @param proxy_elt(domish.Element): <activated/> or <proxy-error/> element @@ -331,81 +382,107 @@ @param client(unicode): %(doc_client)s """ try: - activation_d = transport_data.pop('activation_d') + activation_d = transport_data.pop("activation_d") except KeyError: log.warning(u"Received unexpected transport-info for proxy activation") - if proxy_elt.name == 'activated': + if proxy_elt.name == "activated": activation_d.callback(None) else: activation_d.errback(ProxyError()) @defer.inlineCallbacks def jingleHandler(self, client, action, session, content_name, transport_elt): - content_data = session['contents'][content_name] - transport_data = content_data['transport_data'] + content_data = session["contents"][content_name] + transport_data = content_data["transport_data"] if action in (self._j.A_ACCEPTED_ACK, self._j.A_PREPARE_RESPONDER): pass elif action == self._j.A_SESSION_ACCEPT: # initiator side, we select a candidate in the ones sent by responder - assert 'peer_candidates' not in transport_data - transport_data['peer_candidates'] = self._parseCandidates(transport_elt) + assert "peer_candidates" not in transport_data + transport_data["peer_candidates"] = self._parseCandidates(transport_elt) elif action == self._j.A_START: - session_hash = transport_data['session_hash'] - peer_candidates = transport_data['peer_candidates'] - stream_object = content_data['stream_object'] + session_hash = transport_data["session_hash"] + peer_candidates = transport_data["peer_candidates"] + stream_object = content_data["stream_object"] self._s5b.associateStreamObject(client, session_hash, stream_object) - stream_d = transport_data.pop('stream_d') - stream_d.chainDeferred(content_data['finished_d']) - peer_session_hash = transport_data['peer_session_hash'] - d = self._s5b.getBestCandidate(client, peer_candidates, session_hash, peer_session_hash) - d.addCallback(self._foundPeerCandidate, session, transport_data, content_name, client) + stream_d = transport_data.pop("stream_d") + stream_d.chainDeferred(content_data["finished_d"]) + peer_session_hash = transport_data["peer_session_hash"] + d = self._s5b.getBestCandidate( + client, peer_candidates, session_hash, peer_session_hash + ) + d.addCallback( + self._foundPeerCandidate, session, transport_data, content_name, client + ) elif action == self._j.A_SESSION_INITIATE: # responder side, we select a candidate in the ones sent by initiator # and we give our candidates - assert 'peer_candidates' not in transport_data - sid = transport_data['sid'] = transport_elt['sid'] - session_hash = transport_data['session_hash'] = self._s5b.getSessionHash(client.jid, session['peer_jid'], sid) - peer_session_hash = transport_data['peer_session_hash'] = self._s5b.getSessionHash(session['peer_jid'], client.jid, sid) # requester and target are inversed for peer candidates - peer_candidates = transport_data['peer_candidates'] = self._parseCandidates(transport_elt) - stream_object = content_data['stream_object'] + assert "peer_candidates" not in transport_data + sid = transport_data["sid"] = transport_elt["sid"] + session_hash = transport_data["session_hash"] = self._s5b.getSessionHash( + client.jid, session["peer_jid"], sid + ) + peer_session_hash = transport_data[ + "peer_session_hash" + ] = self._s5b.getSessionHash( + session["peer_jid"], client.jid, sid + ) # requester and target are inversed for peer candidates + peer_candidates = transport_data["peer_candidates"] = self._parseCandidates( + transport_elt + ) + stream_object = content_data["stream_object"] stream_d = self._s5b.registerHash(client, session_hash, stream_object) - stream_d.chainDeferred(content_data['finished_d']) - d = self._s5b.getBestCandidate(client, peer_candidates, session_hash, peer_session_hash) - d.addCallback(self._foundPeerCandidate, session, transport_data, content_name, client) + stream_d.chainDeferred(content_data["finished_d"]) + d = self._s5b.getBestCandidate( + client, peer_candidates, session_hash, peer_session_hash + ) + d.addCallback( + self._foundPeerCandidate, session, transport_data, content_name, client + ) candidates = yield self._s5b.getCandidates(client) # we remove duplicate candidates - candidates = [candidate for candidate in candidates if candidate not in peer_candidates] + candidates = [ + candidate for candidate in candidates if candidate not in peer_candidates + ] - transport_data['candidates'] = candidates + transport_data["candidates"] = candidates # we can now build a new <transport> element with our candidates - transport_elt = self._buildCandidates(session, candidates, sid, session_hash, client) + transport_elt = self._buildCandidates( + session, candidates, sid, session_hash, client + ) elif action == self._j.A_TRANSPORT_INFO: # transport-info can be about candidate or proxy activation candidate_elt = None - for method, names in ((self._candidateInfo, ('candidate-used', 'candidate-error')), - (self._proxyActivationInfo, ('activated', 'proxy-error'))): + for method, names in ( + (self._candidateInfo, ("candidate-used", "candidate-error")), + (self._proxyActivationInfo, ("activated", "proxy-error")), + ): for name in names: try: candidate_elt = transport_elt.elements(NS_JINGLE_S5B, name).next() except StopIteration: continue else: - method(candidate_elt, session, content_name, transport_data, client) + method( + candidate_elt, session, content_name, transport_data, client + ) break if candidate_elt is None: - log.warning(u"Unexpected transport element: {}".format(transport_elt.toXml())) + log.warning( + u"Unexpected transport element: {}".format(transport_elt.toXml()) + ) elif action == self._j.A_DESTROY: # the transport is replaced (fallback ?), We need mainly to kill XEP-0065 session. # note that sid argument is not necessary for sessions created by this plugin - self._s5b.killSession(None, transport_data['session_hash'], None, client) + self._s5b.killSession(None, transport_data["session_hash"], None, client) else: log.warning(u"FIXME: unmanaged action {}".format(action)) @@ -415,9 +492,9 @@ if reason_elt.decline: log.debug(u"Session declined, deleting S5B session") # we just need to clean the S5B session if it is declined - content_data = session['contents'][content_name] - transport_data = content_data['transport_data'] - self._s5b.killSession(None, transport_data['session_hash'], None, client) + content_data = session["contents"][content_name] + transport_data = content_data["transport_data"] + self._s5b.killSession(None, transport_data["session_hash"], None, client) def _doFallback(self, feature_checked, session, content_name, client): """Do the fallback, method called once feature is checked @@ -425,10 +502,14 @@ @param feature_checked(bool): True if other peer can do IBB """ if not feature_checked: - log.warning(u"Other peer can't manage jingle IBB, be have to terminate the session") + log.warning( + u"Other peer can't manage jingle IBB, be have to terminate the session" + ) self._j.terminate(client, self._j.REASON_CONNECTIVITY_ERROR, session) else: - self._j.transportReplace(client, self._jingle_ibb.NAMESPACE, session, content_name) + self._j.transportReplace( + client, self._jingle_ibb.NAMESPACE, session, content_name + ) def doFallback(self, session, content_name, client): """Fallback to IBB transport, used in last resort @@ -437,14 +518,18 @@ @param content_name(unicode): name of the current content @param client(unicode): %(doc_client)s """ - if session['role'] != self._j.ROLE_INITIATOR: + if session["role"] != self._j.ROLE_INITIATOR: # only initiator must do the fallback, see XEP-0260 §3 return if self._jingle_ibb is None: - log.warning(u"Jingle IBB (XEP-0261) plugin is not available, we have to close the session") + log.warning( + u"Jingle IBB (XEP-0261) plugin is not available, we have to close the session" + ) self._j.terminate(client, self._j.REASON_CONNECTIVITY_ERROR, session) else: - d = self.host.hasFeature(client, self._jingle_ibb.NAMESPACE, session['peer_jid']) + d = self.host.hasFeature( + client, self._jingle_ibb.NAMESPACE, session["peer_jid"] + ) d.addCallback(self._doFallback, session, content_name, client) return d @@ -452,8 +537,8 @@ class XEP_0260_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_JINGLE_S5B)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0261.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0261.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from wokkel import disco, iwokkel from zope.interface import implements @@ -32,7 +33,7 @@ from wokkel.subprotocols import XMPPHandler -NS_JINGLE_IBB = 'urn:xmpp:jingle:transports:ibb:1' +NS_JINGLE_IBB = "urn:xmpp:jingle:transports:ibb:1" PLUGIN_INFO = { C.PI_NAME: "Jingle In-Band Bytestreams", @@ -43,52 +44,58 @@ C.PI_DEPENDENCIES: ["XEP-0166", "XEP-0047"], C.PI_MAIN: "XEP_0261", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Jingle In-Band Bytestreams""") + C.PI_DESCRIPTION: _("""Implementation of Jingle In-Band Bytestreams"""), } class XEP_0261(object): - NAMESPACE = NS_JINGLE_IBB # used by XEP-0260 plugin for transport-replace + NAMESPACE = NS_JINGLE_IBB # used by XEP-0260 plugin for transport-replace def __init__(self, host): log.info(_("plugin Jingle In-Band Bytestreams")) self.host = host - self._j = host.plugins["XEP-0166"] # shortcut to access jingle - self._ibb = host.plugins["XEP-0047"] # and in-band bytestream - self._j.registerTransport(NS_JINGLE_IBB, self._j.TRANSPORT_STREAMING, self, -10000) # must be the lowest priority + self._j = host.plugins["XEP-0166"] # shortcut to access jingle + self._ibb = host.plugins["XEP-0047"] # and in-band bytestream + self._j.registerTransport( + NS_JINGLE_IBB, self._j.TRANSPORT_STREAMING, self, -10000 + ) # must be the lowest priority def getHandler(self, client): return XEP_0261_handler() def jingleSessionInit(self, client, session, content_name): transport_elt = domish.Element((NS_JINGLE_IBB, "transport")) - content_data = session['contents'][content_name] - transport_data = content_data['transport_data'] - transport_data['block_size'] = self._ibb.BLOCK_SIZE - transport_elt['block-size'] = unicode(transport_data['block_size']) - transport_elt['sid'] = transport_data['sid'] = unicode(uuid.uuid4()) + content_data = session["contents"][content_name] + transport_data = content_data["transport_data"] + transport_data["block_size"] = self._ibb.BLOCK_SIZE + transport_elt["block-size"] = unicode(transport_data["block_size"]) + transport_elt["sid"] = transport_data["sid"] = unicode(uuid.uuid4()) return transport_elt def jingleHandler(self, client, action, session, content_name, transport_elt): - content_data = session['contents'][content_name] - transport_data = content_data['transport_data'] - if action in (self._j.A_SESSION_ACCEPT, - self._j.A_ACCEPTED_ACK, - self._j.A_TRANSPORT_ACCEPT): + content_data = session["contents"][content_name] + transport_data = content_data["transport_data"] + if action in ( + self._j.A_SESSION_ACCEPT, + self._j.A_ACCEPTED_ACK, + self._j.A_TRANSPORT_ACCEPT, + ): pass elif action in (self._j.A_SESSION_INITIATE, self._j.A_TRANSPORT_REPLACE): - transport_data['sid'] = transport_elt['sid'] + transport_data["sid"] = transport_elt["sid"] elif action in (self._j.A_START, self._j.A_PREPARE_RESPONDER): - peer_jid = session['peer_jid'] - sid = transport_data['sid'] - stream_object = content_data['stream_object'] + peer_jid = session["peer_jid"] + sid = transport_data["sid"] + stream_object = content_data["stream_object"] if action == self._j.A_START: - block_size = transport_data['block_size'] - d = self._ibb.startStream(client, stream_object, peer_jid, sid, block_size) - d.chainDeferred(content_data['finished_d']) + block_size = transport_data["block_size"] + d = self._ibb.startStream( + client, stream_object, peer_jid, sid, block_size + ) + d.chainDeferred(content_data["finished_d"]) else: d = self._ibb.createSession(client, stream_object, peer_jid, sid) - d.chainDeferred(content_data['finished_d']) + d.chainDeferred(content_data["finished_d"]) else: log.warning(u"FIXME: unmanaged action {}".format(action)) return transport_elt @@ -97,8 +104,8 @@ class XEP_0261_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_JINGLE_IBB)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0264.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0264.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.internet import threads from twisted.python.failure import Failure @@ -31,13 +32,17 @@ from sat.core import exceptions import hashlib + try: from PIL import Image except: - raise exceptions.MissingModule(u"Missing module pillow, please download/install it from https://python-pillow.github.io") + raise exceptions.MissingModule( + u"Missing module pillow, please download/install it from https://python-pillow.github.io" + ) -# cf. https://stackoverflow.com/a/23575424 +# cf. https://stackoverflow.com/a/23575424 from PIL import ImageFile + ImageFile.LOAD_TRUNCATED_IMAGES = True try: @@ -46,10 +51,10 @@ from wokkel.subprotocols import XMPPHandler -MIME_TYPE = u'image/jpeg' -SAVE_FORMAT = u'JPEG' # (cf. Pillow documentation) +MIME_TYPE = u"image/jpeg" +SAVE_FORMAT = u"JPEG" # (cf. Pillow documentation) -NS_THUMBS = 'urn:xmpp:thumbs:1' +NS_THUMBS = "urn:xmpp:thumbs:1" PLUGIN_INFO = { C.PI_NAME: "XEP-0264", @@ -60,7 +65,7 @@ C.PI_DEPENDENCIES: ["XEP-0234"], C.PI_MAIN: "XEP_0264", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Thumbnails handling""") + C.PI_DESCRIPTION: _("""Thumbnails handling"""), } @@ -81,39 +86,39 @@ def _addFileThumbnails(self, file_elt, extra_args): try: - thumbnails = extra_args[u'extra'][C.KEY_THUMBNAILS] + thumbnails = extra_args[u"extra"][C.KEY_THUMBNAILS] except KeyError: return for thumbnail in thumbnails: - thumbnail_elt = file_elt.addElement((NS_THUMBS, u'thumbnail')) - thumbnail_elt['uri'] = u'cid:' + thumbnail['id'] - thumbnail_elt['media-type'] = MIME_TYPE - width, height = thumbnail['size'] - thumbnail_elt['width'] = unicode(width) - thumbnail_elt['height'] = unicode(height) + thumbnail_elt = file_elt.addElement((NS_THUMBS, u"thumbnail")) + thumbnail_elt["uri"] = u"cid:" + thumbnail["id"] + thumbnail_elt["media-type"] = MIME_TYPE + width, height = thumbnail["size"] + thumbnail_elt["width"] = unicode(width) + thumbnail_elt["height"] = unicode(height) return True def _getFileThumbnails(self, file_elt, file_data): thumbnails = [] - for thumbnail_elt in file_elt.elements(NS_THUMBS, u'thumbnail'): - uri = thumbnail_elt['uri'] - if uri.startswith ('cid:'): - thumbnail = {'id': uri[4:]} - width = thumbnail_elt.getAttribute('width') - height = thumbnail_elt.getAttribute('height') + for thumbnail_elt in file_elt.elements(NS_THUMBS, u"thumbnail"): + uri = thumbnail_elt["uri"] + if uri.startswith("cid:"): + thumbnail = {"id": uri[4:]} + width = thumbnail_elt.getAttribute("width") + height = thumbnail_elt.getAttribute("height") if width and height: try: - thumbnail['size'] = int(width), int(height) + thumbnail["size"] = int(width), int(height) except ValueError: pass try: - thumbnail['mime_type'] = thumbnail_elt['media-type'] + thumbnail["mime_type"] = thumbnail_elt["media-type"] except KeyError: pass thumbnails.append(thumbnail) if thumbnails: - file_data.setdefault('extra', {})[C.KEY_THUMBNAILS] = thumbnails + file_data.setdefault("extra", {})[C.KEY_THUMBNAILS] = thumbnails return True ## thumbnails generation ## @@ -145,11 +150,8 @@ uid = self.getThumbId(image_uid or source_path, size) with self.host.common_cache.cacheData( - PLUGIN_INFO[C.PI_IMPORT_NAME], - uid, - MIME_TYPE, - max_age, - ) as f: + PLUGIN_INFO[C.PI_IMPORT_NAME], uid, MIME_TYPE, max_age + ) as f: img.save(f, SAVE_FORMAT) return img.size, uid @@ -171,17 +173,19 @@ """ d = threads.deferToThread( - self._blockingGenThumb, source_path, size, max_age, image_uid=image_uid) - d.addErrback(lambda failure_: - log.error(u"thumbnail generation error: {}".format(failure_))) + self._blockingGenThumb, source_path, size, max_age, image_uid=image_uid + ) + d.addErrback( + lambda failure_: log.error(u"thumbnail generation error: {}".format(failure_)) + ) return d class XEP_0264_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_THUMBS)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0277.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0277.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.protocols.jabber import jid, error from twisted.words.protocols.jabber.xmlstream import XMPPHandler @@ -43,10 +44,10 @@ import calendar import urlparse -NS_MICROBLOG = 'urn:xmpp:microblog:0' -NS_ATOM = 'http://www.w3.org/2005/Atom' +NS_MICROBLOG = "urn:xmpp:microblog:0" +NS_ATOM = "http://www.w3.org/2005/Atom" NS_PUBSUB_EVENT = "{}{}".format(pubsub.NS_PUBSUB, "#event") -NS_COMMENT_PREFIX = '{}:comments/'.format(NS_MICROBLOG) +NS_COMMENT_PREFIX = "{}:comments/".format(NS_MICROBLOG) PLUGIN_INFO = { @@ -58,7 +59,7 @@ C.PI_RECOMMENDATIONS: ["XEP-0059", "EXTRA-PEP"], C.PI_MAIN: "XEP_0277", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of microblogging Protocol""") + C.PI_DESCRIPTION: _("""Implementation of microblogging Protocol"""), } @@ -72,49 +73,97 @@ def __init__(self, host): log.info(_(u"Microblogging plugin initialization")) self.host = host - host.registerNamespace('microblog', NS_MICROBLOG) - self._p = self.host.plugins["XEP-0060"] # this facilitate the access to pubsub plugin + host.registerNamespace("microblog", NS_MICROBLOG) + self._p = self.host.plugins[ + "XEP-0060" + ] # this facilitate the access to pubsub plugin self.rt_sessions = sat_defer.RTDeferredSessions() - self.host.plugins["XEP-0060"].addManagedNode(NS_MICROBLOG, items_cb=self._itemsReceived) + self.host.plugins["XEP-0060"].addManagedNode( + NS_MICROBLOG, items_cb=self._itemsReceived + ) - host.bridge.addMethod("mbSend", ".plugin", - in_sign='ssa{ss}s', out_sign='', - method=self._mbSend, - async=True) - host.bridge.addMethod("mbRetract", ".plugin", - in_sign='ssss', out_sign='', - method=self._mbRetract, - async=True) - host.bridge.addMethod("mbGet", ".plugin", - in_sign='ssiasa{ss}s', out_sign='(aa{ss}a{ss})', - method=self._mbGet, - async=True) - host.bridge.addMethod("mbSetAccess", ".plugin", in_sign='ss', out_sign='', - method=self.mbSetAccess, - async=True) - host.bridge.addMethod("mbSubscribeToMany", ".plugin", in_sign='sass', out_sign='s', - method=self._mbSubscribeToMany) - host.bridge.addMethod("mbGetFromManyRTResult", ".plugin", in_sign='ss', out_sign='(ua(sssaa{ss}a{ss}))', - method=self._mbGetFromManyRTResult, async=True) - host.bridge.addMethod("mbGetFromMany", ".plugin", in_sign='sasia{ss}s', out_sign='s', - method=self._mbGetFromMany) - host.bridge.addMethod("mbGetFromManyWithCommentsRTResult", ".plugin", in_sign='ss', out_sign='(ua(sssa(a{ss}a(sssaa{ss}a{ss}))a{ss}))', - method=self._mbGetFromManyWithCommentsRTResult, async=True) - host.bridge.addMethod("mbGetFromManyWithComments", ".plugin", in_sign='sasiia{ss}a{ss}s', out_sign='s', - method=self._mbGetFromManyWithComments) + host.bridge.addMethod( + "mbSend", + ".plugin", + in_sign="ssa{ss}s", + out_sign="", + method=self._mbSend, + async=True, + ) + host.bridge.addMethod( + "mbRetract", + ".plugin", + in_sign="ssss", + out_sign="", + method=self._mbRetract, + async=True, + ) + host.bridge.addMethod( + "mbGet", + ".plugin", + in_sign="ssiasa{ss}s", + out_sign="(aa{ss}a{ss})", + method=self._mbGet, + async=True, + ) + host.bridge.addMethod( + "mbSetAccess", + ".plugin", + in_sign="ss", + out_sign="", + method=self.mbSetAccess, + async=True, + ) + host.bridge.addMethod( + "mbSubscribeToMany", + ".plugin", + in_sign="sass", + out_sign="s", + method=self._mbSubscribeToMany, + ) + host.bridge.addMethod( + "mbGetFromManyRTResult", + ".plugin", + in_sign="ss", + out_sign="(ua(sssaa{ss}a{ss}))", + method=self._mbGetFromManyRTResult, + async=True, + ) + host.bridge.addMethod( + "mbGetFromMany", + ".plugin", + in_sign="sasia{ss}s", + out_sign="s", + method=self._mbGetFromMany, + ) + host.bridge.addMethod( + "mbGetFromManyWithCommentsRTResult", + ".plugin", + in_sign="ss", + out_sign="(ua(sssa(a{ss}a(sssaa{ss}a{ss}))a{ss}))", + method=self._mbGetFromManyWithCommentsRTResult, + async=True, + ) + host.bridge.addMethod( + "mbGetFromManyWithComments", + ".plugin", + in_sign="sasiia{ss}a{ss}s", + out_sign="s", + method=self._mbGetFromManyWithComments, + ) def getHandler(self, client): return XEP_0277_handler() def _checkFeaturesCb(self, available): - return {'available': C.BOOL_TRUE} + return {"available": C.BOOL_TRUE} def _checkFeaturesEb(self, fail): - return {'available': C.BOOL_FALSE} + return {"available": C.BOOL_FALSE} def getFeatures(self, profile): client = self.host.getClient(profile) - d = self.host.checkFeatures(client, [], identity=('pubsub', 'pep')) + d = self.host.checkFeatures(client, [], identity=("pubsub", "pep")) d.addCallbacks(self._checkFeaturesCb, self._checkFeaturesEb) return d @@ -122,18 +171,27 @@ def _itemsReceived(self, client, itemsEvent): """Callback which manage items notifications (publish + retract)""" + def manageItem(data, event): - self.host.bridge.psEvent(C.PS_MICROBLOG, itemsEvent.sender.full(), itemsEvent.nodeIdentifier, event, data, client.profile) + self.host.bridge.psEvent( + C.PS_MICROBLOG, + itemsEvent.sender.full(), + itemsEvent.nodeIdentifier, + event, + data, + client.profile, + ) for item in itemsEvent.items: if item.name == C.PS_ITEM: - self.item2mbdata(item).addCallbacks(manageItem, lambda failure: None, (C.PS_PUBLISH,)) + self.item2mbdata(item).addCallbacks( + manageItem, lambda failure: None, (C.PS_PUBLISH,) + ) elif item.name == C.PS_RETRACT: - manageItem({'id': item['id']}, C.PS_RETRACT) + manageItem({"id": item["id"]}, C.PS_RETRACT) else: raise exceptions.InternalError("Invalid event value") - ## data/item transformation ## @defer.inlineCallbacks @@ -156,13 +214,17 @@ """ if key in microblog_data: if not increment: - raise failure.Failure(exceptions.DataError("key {} is already present for item {}").format(key, item_elt['id'])) + raise failure.Failure( + exceptions.DataError( + "key {} is already present for item {}" + ).format(key, item_elt["id"]) + ) else: - idx=1 # the idx 0 is the key without suffix + idx = 1 # the idx 0 is the key without suffix fmt = "{}#{}" new_key = fmt.format(key, idx) while new_key in microblog_data: - idx+=1 + idx += 1 new_key = fmt.format(key, idx) key = new_key return key @@ -170,49 +232,62 @@ @defer.inlineCallbacks def parseElement(elem): """Parse title/content elements and fill microblog_data accordingly""" - type_ = elem.getAttribute('type') - if type_ == 'xhtml': + type_ = elem.getAttribute("type") + if type_ == "xhtml": data_elt = elem.firstChildElement() if data_elt is None: - raise failure.Failure(exceptions.DataError(u"XHML content not wrapped in a <div/> element, this is not standard !")) + raise failure.Failure( + exceptions.DataError( + u"XHML content not wrapped in a <div/> element, this is not standard !" + ) + ) if data_elt.uri != C.NS_XHTML: - raise failure.Failure(exceptions.DataError(_('Content of type XHTML must declare its namespace!'))) - key = check_conflict(u'{}_xhtml'.format(elem.name)) + raise failure.Failure( + exceptions.DataError( + _("Content of type XHTML must declare its namespace!") + ) + ) + key = check_conflict(u"{}_xhtml".format(elem.name)) data = data_elt.toXml() - microblog_data[key] = yield self.host.plugins["TEXT-SYNTAXES"].cleanXHTML(data) + microblog_data[key] = yield self.host.plugins["TEXT-SYNTAXES"].cleanXHTML( + data + ) else: key = check_conflict(elem.name) microblog_data[key] = unicode(elem) - - id_ = item_elt.getAttribute('id', '') # there can be no id for transient nodes - microblog_data[u'id'] = id_ + id_ = item_elt.getAttribute("id", "") # there can be no id for transient nodes + microblog_data[u"id"] = id_ if item_elt.uri not in (pubsub.NS_PUBSUB, NS_PUBSUB_EVENT): - msg = u"Unsupported namespace {ns} in pubsub item {id_}".format(ns=item_elt.uri, id_=id_) + msg = u"Unsupported namespace {ns} in pubsub item {id_}".format( + ns=item_elt.uri, id_=id_ + ) log.warning(msg) raise failure.Failure(exceptions.DataError(msg)) try: - entry_elt = item_elt.elements(NS_ATOM, 'entry').next() + entry_elt = item_elt.elements(NS_ATOM, "entry").next() except StopIteration: - msg = u'No atom entry found in the pubsub item {}'.format(id_) + msg = u"No atom entry found in the pubsub item {}".format(id_) raise failure.Failure(exceptions.DataError(msg)) # language try: - microblog_data[u'language'] = entry_elt[(C.NS_XML, u'lang')].strip() + microblog_data[u"language"] = entry_elt[(C.NS_XML, u"lang")].strip() except KeyError: pass # atom:id try: - id_elt = entry_elt.elements(NS_ATOM, 'id').next() + id_elt = entry_elt.elements(NS_ATOM, "id").next() except StopIteration: - msg = u'No atom id found in the pubsub item {}, this is not standard !'.format(id_) + msg = u"No atom id found in the pubsub item {}, this is not standard !".format( + id_ + ) log.warning(msg) - microblog_data[u'atom_id'] = "" + microblog_data[u"atom_id"] = "" else: - microblog_data[u'atom_id'] = unicode(id_elt) + microblog_data[u"atom_id"] = unicode(id_elt) # title/content(s) @@ -225,9 +300,9 @@ # except StopIteration: # msg = u'No atom title found in the pubsub item {}'.format(id_) # raise failure.Failure(exceptions.DataError(msg)) - title_elts = list(entry_elt.elements(NS_ATOM, 'title')) + title_elts = list(entry_elt.elements(NS_ATOM, "title")) if not title_elts: - msg = u'No atom title found in the pubsub item {}'.format(id_) + msg = u"No atom title found in the pubsub item {}".format(id_) raise failure.Failure(exceptions.DataError(msg)) for title_elt in title_elts: yield parseElement(title_elt) @@ -235,113 +310,146 @@ # FIXME: as for <title/>, Atom only authorise at most 1 content # but XEP-0277 allows several ones. So for no we handle as # if more than one can be present - for content_elt in entry_elt.elements(NS_ATOM, 'content'): + for content_elt in entry_elt.elements(NS_ATOM, "content"): yield parseElement(content_elt) # we check that text content is present - for key in ('title', 'content'): - if key not in microblog_data and ('{}_xhtml'.format(key)) in microblog_data: - log.warning(u"item {id_} provide a {key}_xhtml data but not a text one".format(id_=id_, key=key)) + for key in ("title", "content"): + if key not in microblog_data and ("{}_xhtml".format(key)) in microblog_data: + log.warning( + u"item {id_} provide a {key}_xhtml data but not a text one".format( + id_=id_, key=key + ) + ) # ... and do the conversion if it's not - microblog_data[key] = yield self.host.plugins["TEXT-SYNTAXES"].\ - convert(microblog_data[u'{}_xhtml'.format(key)], - self.host.plugins["TEXT-SYNTAXES"].SYNTAX_XHTML, - self.host.plugins["TEXT-SYNTAXES"].SYNTAX_TEXT, - False) + microblog_data[key] = yield self.host.plugins["TEXT-SYNTAXES"].convert( + microblog_data[u"{}_xhtml".format(key)], + self.host.plugins["TEXT-SYNTAXES"].SYNTAX_XHTML, + self.host.plugins["TEXT-SYNTAXES"].SYNTAX_TEXT, + False, + ) - if 'content' not in microblog_data: + if "content" not in microblog_data: # use the atom title data as the microblog body content - microblog_data[u'content'] = microblog_data[u'title'] - del microblog_data[u'title'] - if 'title_xhtml' in microblog_data: - microblog_data[u'content_xhtml'] = microblog_data[u'title_xhtml'] - del microblog_data[u'title_xhtml'] + microblog_data[u"content"] = microblog_data[u"title"] + del microblog_data[u"title"] + if "title_xhtml" in microblog_data: + microblog_data[u"content_xhtml"] = microblog_data[u"title_xhtml"] + del microblog_data[u"title_xhtml"] # published/updated dates try: - updated_elt = entry_elt.elements(NS_ATOM, 'updated').next() + updated_elt = entry_elt.elements(NS_ATOM, "updated").next() except StopIteration: - msg = u'No atom updated element found in the pubsub item {}'.format(id_) + msg = u"No atom updated element found in the pubsub item {}".format(id_) raise failure.Failure(exceptions.DataError(msg)) - microblog_data[u'updated'] = unicode(calendar.timegm(dateutil.parser.parse(unicode(updated_elt)).utctimetuple())) + microblog_data[u"updated"] = unicode( + calendar.timegm(dateutil.parser.parse(unicode(updated_elt)).utctimetuple()) + ) try: - published_elt = entry_elt.elements(NS_ATOM, 'published').next() + published_elt = entry_elt.elements(NS_ATOM, "published").next() except StopIteration: - microblog_data[u'published'] = microblog_data[u'updated'] + microblog_data[u"published"] = microblog_data[u"updated"] else: - microblog_data[u'published'] = unicode(calendar.timegm(dateutil.parser.parse(unicode(published_elt)).utctimetuple())) + microblog_data[u"published"] = unicode( + calendar.timegm( + dateutil.parser.parse(unicode(published_elt)).utctimetuple() + ) + ) # links - for link_elt in entry_elt.elements(NS_ATOM, 'link'): - if link_elt.getAttribute('rel') == 'replies' and link_elt.getAttribute('title') == 'comments': - key = check_conflict('comments', True) - microblog_data[key] = link_elt['href'] + for link_elt in entry_elt.elements(NS_ATOM, "link"): + if ( + link_elt.getAttribute("rel") == "replies" + and link_elt.getAttribute("title") == "comments" + ): + key = check_conflict("comments", True) + microblog_data[key] = link_elt["href"] try: service, node = self.parseCommentUrl(microblog_data[key]) except: log.warning(u"Can't parse url {}".format(microblog_data[key])) del microblog_data[key] else: - microblog_data[u'{}_service'.format(key)] = service.full() - microblog_data[u'{}_node'.format(key)] = node + microblog_data[u"{}_service".format(key)] = service.full() + microblog_data[u"{}_node".format(key)] = node else: - rel = link_elt.getAttribute('rel','') - title = link_elt.getAttribute('title','') - href = link_elt.getAttribute('href','') - log.warning(u"Unmanaged link element: rel={rel} title={title} href={href}".format(rel=rel, title=title, href=href)) + rel = link_elt.getAttribute("rel", "") + title = link_elt.getAttribute("title", "") + href = link_elt.getAttribute("href", "") + log.warning( + u"Unmanaged link element: rel={rel} title={title} href={href}".format( + rel=rel, title=title, href=href + ) + ) # author try: - author_elt = entry_elt.elements(NS_ATOM, 'author').next() + author_elt = entry_elt.elements(NS_ATOM, "author").next() except StopIteration: log.debug(u"Can't find author element in item {}".format(id_)) else: publisher = item_elt.getAttribute("publisher") # name try: - name_elt = author_elt.elements(NS_ATOM, 'name').next() + name_elt = author_elt.elements(NS_ATOM, "name").next() except StopIteration: - log.warning(u"No name element found in author element of item {}".format(id_)) + log.warning( + u"No name element found in author element of item {}".format(id_) + ) else: - microblog_data[u'author'] = unicode(name_elt) + microblog_data[u"author"] = unicode(name_elt) # uri try: - uri_elt = author_elt.elements(NS_ATOM, 'uri').next() + uri_elt = author_elt.elements(NS_ATOM, "uri").next() except StopIteration: - log.debug(u"No uri element found in author element of item {}".format(id_)) + log.debug( + u"No uri element found in author element of item {}".format(id_) + ) if publisher: - microblog_data[u'author_jid'] = publisher + microblog_data[u"author_jid"] = publisher else: uri = unicode(uri_elt) if uri.startswith("xmpp:"): uri = uri[5:] - microblog_data[u'author_jid'] = uri + microblog_data[u"author_jid"] = uri else: - microblog_data[u'author_jid'] = item_elt.getAttribute(u"publisher") or "" + microblog_data[u"author_jid"] = ( + item_elt.getAttribute(u"publisher") or "" + ) if not publisher: log.debug(u"No publisher attribute, we can't verify author jid") - microblog_data[u'author_jid_verified'] = C.BOOL_FALSE + microblog_data[u"author_jid_verified"] = C.BOOL_FALSE elif jid.JID(publisher).userhostJID() == jid.JID(uri).userhostJID(): - microblog_data[u'author_jid_verified'] = C.BOOL_TRUE + microblog_data[u"author_jid_verified"] = C.BOOL_TRUE else: - log.warning(u"item atom:uri differ from publisher attribute, spoofing attempt ? atom:uri = {} publisher = {}".format(uri, item_elt.getAttribute("publisher"))) - microblog_data[u'author_jid_verified'] = C.BOOL_FALSE + log.warning( + u"item atom:uri differ from publisher attribute, spoofing attempt ? atom:uri = {} publisher = {}".format( + uri, item_elt.getAttribute("publisher") + ) + ) + microblog_data[u"author_jid_verified"] = C.BOOL_FALSE # email try: - email_elt = author_elt.elements(NS_ATOM, 'email').next() + email_elt = author_elt.elements(NS_ATOM, "email").next() except StopIteration: pass else: - microblog_data[u'author_email'] = unicode(email_elt) + microblog_data[u"author_email"] = unicode(email_elt) # categories - categories = (category_elt.getAttribute('term','') for category_elt in entry_elt.elements(NS_ATOM, 'category')) - data_format.iter2dict('tag', categories, microblog_data) + categories = ( + category_elt.getAttribute("term", "") + for category_elt in entry_elt.elements(NS_ATOM, "category") + ) + data_format.iter2dict("tag", categories, microblog_data) ## the trigger ## # if other plugins have things to add or change - yield self.host.trigger.point("XEP-0277_item2data", item_elt, entry_elt, microblog_data) + yield self.host.trigger.point( + "XEP-0277_item2data", item_elt, entry_elt, microblog_data + ) defer.returnValue(microblog_data) @@ -357,115 +465,144 @@ Needed to construct Atom id @return: deferred which fire domish.Element """ - entry_elt = domish.Element((NS_ATOM, 'entry')) + entry_elt = domish.Element((NS_ATOM, "entry")) ## language ## - if u'language' in data: - entry_elt[(C.NS_XML, u'lang')] = data[u'language'].strip() + if u"language" in data: + entry_elt[(C.NS_XML, u"lang")] = data[u"language"].strip() ## content and title ## synt = self.host.plugins["TEXT-SYNTAXES"] - for elem_name in ('title', 'content'): - for type_ in ['', '_rich', '_xhtml']: + for elem_name in ("title", "content"): + for type_ in ["", "_rich", "_xhtml"]: attr = "{}{}".format(elem_name, type_) if attr in data: elem = entry_elt.addElement(elem_name) if type_: - if type_ == '_rich': # convert input from current syntax to XHTML - xml_content = yield synt.convert(data[attr], synt.getCurrentSyntax(client.profile), "XHTML") - if '{}_xhtml'.format(elem_name) in data: - raise failure.Failure(exceptions.DataError(_("Can't have xhtml and rich content at the same time"))) + if type_ == "_rich": # convert input from current syntax to XHTML + xml_content = yield synt.convert( + data[attr], synt.getCurrentSyntax(client.profile), "XHTML" + ) + if "{}_xhtml".format(elem_name) in data: + raise failure.Failure( + exceptions.DataError( + _( + "Can't have xhtml and rich content at the same time" + ) + ) + ) else: xml_content = data[attr] - div_elt = xml_tools.ElementParser()(xml_content, namespace=C.NS_XHTML) - if div_elt.name != 'div' or div_elt.uri != C.NS_XHTML or div_elt.attributes: + div_elt = xml_tools.ElementParser()( + xml_content, namespace=C.NS_XHTML + ) + if ( + div_elt.name != "div" + or div_elt.uri != C.NS_XHTML + or div_elt.attributes + ): # we need a wrapping <div/> at the top with XHTML namespace - wrap_div_elt = domish.Element((C.NS_XHTML, 'div')) + wrap_div_elt = domish.Element((C.NS_XHTML, "div")) wrap_div_elt.addChild(div_elt) div_elt = wrap_div_elt elem.addChild(div_elt) - elem['type'] = 'xhtml' + elem["type"] = "xhtml" if elem_name not in data: # there is raw text content, which is mandatory # so we create one from xhtml content elem_txt = entry_elt.addElement(elem_name) - text_content = yield self.host.plugins["TEXT-SYNTAXES"].convert(xml_content, + text_content = yield self.host.plugins[ + "TEXT-SYNTAXES" + ].convert( + xml_content, self.host.plugins["TEXT-SYNTAXES"].SYNTAX_XHTML, self.host.plugins["TEXT-SYNTAXES"].SYNTAX_TEXT, - False) + False, + ) elem_txt.addContent(text_content) - elem_txt['type'] = 'text' + elem_txt["type"] = "text" else: # raw text only needs to be escaped to get HTML-safe sequence elem.addContent(data[attr]) - elem['type'] = 'text' + elem["type"] = "text" try: - entry_elt.elements(NS_ATOM, 'title').next() + entry_elt.elements(NS_ATOM, "title").next() except StopIteration: # we have no title element which is mandatory # so we transform content element to title - elems = list(entry_elt.elements(NS_ATOM, 'content')) + elems = list(entry_elt.elements(NS_ATOM, "content")) if not elems: - raise exceptions.DataError("There must be at least one content or title element") + raise exceptions.DataError( + "There must be at least one content or title element" + ) for elem in elems: - elem.name = 'title' + elem.name = "title" ## author ## - author_elt = entry_elt.addElement('author') + author_elt = entry_elt.addElement("author") try: - author_name = data['author'] + author_name = data["author"] except KeyError: # FIXME: must use better name author_name = client.jid.user - author_elt.addElement('name', content=author_name) + author_elt.addElement("name", content=author_name) try: - author_jid_s = data['author_jid'] + author_jid_s = data["author_jid"] except KeyError: author_jid_s = client.jid.userhost() - author_elt.addElement('uri', content="xmpp:{}".format(author_jid_s)) + author_elt.addElement("uri", content="xmpp:{}".format(author_jid_s)) try: - author_jid_s = data['author_email'] + author_jid_s = data["author_email"] except KeyError: pass ## published/updated time ## current_time = time.time() - entry_elt.addElement('updated', - content = utils.xmpp_date(float(data.get('updated', current_time)))) - entry_elt.addElement('published', - content = utils.xmpp_date(float(data.get('published', current_time)))) + entry_elt.addElement( + "updated", content=utils.xmpp_date(float(data.get("updated", current_time))) + ) + entry_elt.addElement( + "published", + content=utils.xmpp_date(float(data.get("published", current_time))), + ) ## categories ## for tag in data_format.dict2iter("tag", data): category_elt = entry_elt.addElement("category") - category_elt['term'] = tag + category_elt["term"] = tag ## id ## - entry_id = data.get('id', xmpp_uri.buildXMPPUri( - u'pubsub', - path=service.full() if service is not None else client.jid.userhost(), - node=node, - item=item_id)) - entry_elt.addElement('id', content=entry_id) # + entry_id = data.get( + "id", + xmpp_uri.buildXMPPUri( + u"pubsub", + path=service.full() if service is not None else client.jid.userhost(), + node=node, + item=item_id, + ), + ) + entry_elt.addElement("id", content=entry_id) # ## comments ## - if 'comments' in data: - link_elt = entry_elt.addElement('link') - link_elt['href'] = data['comments'] - link_elt['rel'] = 'replies' - link_elt['title'] = 'comments' + if "comments" in data: + link_elt = entry_elt.addElement("link") + link_elt["href"] = data["comments"] + link_elt["rel"] = "replies" + link_elt["title"] = "comments" ## final item building ## item_elt = pubsub.Item(id=item_id, payload=entry_elt) ## the trigger ## # if other plugins have things to add or change - yield self.host.trigger.point("XEP-0277_data2entry", client, data, entry_elt, item_elt) + yield self.host.trigger.point( + "XEP-0277_data2entry", client, data, entry_elt, item_elt + ) defer.returnValue(item_elt) @@ -489,17 +626,21 @@ if parent_service.user: # we are on a PEP if parent_service.host == client.jid.host: - # it's our server, we use already found client.pubsub_service below + # it's our server, we use already found client.pubsub_service below pass else: # other server, let's try to find a non PEP service there - d = self.host.findServiceEntity(client, "pubsub", "service", parent_service) + d = self.host.findServiceEntity( + client, "pubsub", "service", parent_service + ) d.addCallback(lambda entity: entity or parent_service) else: # parent is already on a normal Pubsub service, we re-use it return defer.succeed(parent_service) - return defer.succeed(client.pubsub_service if client.pubsub_service is not None else parent_service) + return defer.succeed( + client.pubsub_service if client.pubsub_service is not None else parent_service + ) @defer.inlineCallbacks def _manageComments(self, client, mb_data, service, node, item_id, access=None): @@ -517,9 +658,13 @@ # FIXME: if 'comments' already exists in mb_data, it is not used to create the Node allow_comments = C.bool(mb_data.pop("allow_comments", "false")) if not allow_comments: - if 'comments' in mb_data: - log.warning(u"comments are not allowed but there is already a comments node, it may be lost: {uri}".format(uri=mb_data['comments'])) - del mb_data['comments'] + if "comments" in mb_data: + log.warning( + u"comments are not allowed but there is already a comments node, it may be lost: {uri}".format( + uri=mb_data["comments"] + ) + ) + del mb_data["comments"] return if access is None: @@ -527,60 +672,75 @@ parent_node_config = yield self._p.getConfiguration(client, service, node) access = parent_node_config.get(self._p.OPT_ACCESS_MODEL, self._p.ACCESS_OPEN) - options = {self._p.OPT_ACCESS_MODEL: access, - self._p.OPT_PERSIST_ITEMS: 1, - self._p.OPT_MAX_ITEMS: -1, - self._p.OPT_DELIVER_PAYLOADS: 1, - self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, - # FIXME: would it make sense to restrict publish model to subscribers? - self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN, - } + options = { + self._p.OPT_ACCESS_MODEL: access, + self._p.OPT_PERSIST_ITEMS: 1, + self._p.OPT_MAX_ITEMS: -1, + self._p.OPT_DELIVER_PAYLOADS: 1, + self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, + # FIXME: would it make sense to restrict publish model to subscribers? + self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN, + } # if other plugins need to change the options yield self.host.trigger.point("XEP-0277_comments", client, mb_data, options) try: - comments_node = mb_data['comments_node'] + comments_node = mb_data["comments_node"] except KeyError: comments_node = self.getCommentsNode(item_id) else: if not comments_node: - raise exceptions.DataError(u"if comments_node is present, it must not be empty") + raise exceptions.DataError( + u"if comments_node is present, it must not be empty" + ) try: - comments_service = jid.JID(mb_data['comments_service']) + comments_service = jid.JID(mb_data["comments_service"]) except KeyError: comments_service = yield self.getCommentsService(client, service) try: yield self._p.createNode(client, comments_service, comments_node, options) except error.StanzaError as e: - if e.condition == 'conflict': - log.info(u"node {} already exists on service {}".format(comments_node, comments_service)) + if e.condition == "conflict": + log.info( + u"node {} already exists on service {}".format( + comments_node, comments_service + ) + ) else: raise e else: if access == self._p.ACCESS_WHITELIST: # for whitelist access we need to copy affiliations from parent item - comments_affiliations = yield self._p.getNodeAffiliations(client, service, node) + comments_affiliations = yield self._p.getNodeAffiliations( + client, service, node + ) # …except for "member", that we transform to publisher # because we wants members to be able to write to comments for jid_, affiliation in comments_affiliations.items(): - if affiliation == 'member': - comments_affiliations[jid_] == 'publisher' + if affiliation == "member": + comments_affiliations[jid_] == "publisher" - yield self._p.setNodeAffiliations(client, comments_service, comments_node, comments_affiliations) + yield self._p.setNodeAffiliations( + client, comments_service, comments_node, comments_affiliations + ) if comments_service is None: comments_service = client.jid.userhostJID() - if 'comments' in mb_data: - if not mb_data['comments']: - raise exceptions.DataError(u"if comments is present, it must not be empty") - if 'comments_node' in mb_data or 'comments_service' in mb_data: - raise exceptions.DataError(u"You can't use comments_service/comments_node and comments at the same time") + if "comments" in mb_data: + if not mb_data["comments"]: + raise exceptions.DataError( + u"if comments is present, it must not be empty" + ) + if "comments_node" in mb_data or "comments_service" in mb_data: + raise exceptions.DataError( + u"You can't use comments_service/comments_node and comments at the same time" + ) else: - mb_data['comments'] = self._p.getNodeURI(comments_service, comments_node) + mb_data["comments"] = self._p.getNodeURI(comments_service, comments_node) def _mbSend(self, service, node, data, profile_key): service = jid.JID(service) if service else None @@ -604,7 +764,7 @@ if node is None: node = NS_MICROBLOG - item_id = data.get('id') or unicode(shortuuid.uuid()) + item_id = data.get("id") or unicode(shortuuid.uuid()) try: yield self._manageComments(client, data, service, node, item_id, access=None) @@ -618,11 +778,25 @@ def _mbRetract(self, service_jid_s, nodeIdentifier, itemIdentifier, profile_key): """Call self._p._retractItem, but use default node if node is empty""" - return self._p._retractItem(service_jid_s, nodeIdentifier or NS_MICROBLOG, itemIdentifier, True, profile_key) + return self._p._retractItem( + service_jid_s, + nodeIdentifier or NS_MICROBLOG, + itemIdentifier, + True, + profile_key, + ) ## get ## - def _mbGet(self, service='', node='', max_items=10, item_ids=None, extra_dict=None, profile_key=C.PROF_KEY_NONE): + def _mbGet( + self, + service="", + node="", + max_items=10, + item_ids=None, + extra_dict=None, + profile_key=C.PROF_KEY_NONE, + ): """ @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit @param item_ids (list[unicode]): list of item IDs @@ -631,11 +805,27 @@ service = jid.JID(service) if service else None max_items = None if max_items == C.NO_LIMIT else max_items extra = self._p.parseExtra(extra_dict) - return self.mbGet(client, service, node or None, max_items, item_ids, extra.rsm_request, extra.extra) - + return self.mbGet( + client, + service, + node or None, + max_items, + item_ids, + extra.rsm_request, + extra.extra, + ) @defer.inlineCallbacks - def mbGet(self, client, service=None, node=None, max_items=10, item_ids=None, rsm_request=None, extra=None): + def mbGet( + self, + client, + service=None, + node=None, + max_items=10, + item_ids=None, + rsm_request=None, + extra=None, + ): """Get some microblogs @param service(jid.JID, None): jid of the publisher @@ -650,7 +840,15 @@ """ if node is None: node = NS_MICROBLOG - items_data = yield self._p.getItems(client, service, node, max_items=max_items, item_ids=item_ids, rsm_request=rsm_request, extra=extra) + items_data = yield self._p.getItems( + client, + service, + node, + max_items=max_items, + item_ids=item_ids, + rsm_request=rsm_request, + extra=extra, + ) serialised = yield self._p.serItemsDataD(items_data, self.item2mbdata) defer.returnValue(serialised) @@ -663,13 +861,13 @@ will return(JID(u'sat-pubsub.example.net'), 'urn:xmpp:comments:_af43b363-3259-4b2a-ba4c-1bc33aa87634__urn:xmpp:groupblog:somebody@example.net') @return (tuple[jid.JID, unicode]): service and node """ - parsed_url = urlparse.urlparse(node_url, 'xmpp') + parsed_url = urlparse.urlparse(node_url, "xmpp") service = jid.JID(parsed_url.path) - parsed_queries = urlparse.parse_qs(parsed_url.query.encode('utf-8')) - node = parsed_queries.get('node', [''])[0].decode('utf-8') + parsed_queries = urlparse.parse_qs(parsed_url.query.encode("utf-8")) + node = parsed_queries.get("node", [""])[0].decode("utf-8") if not node: - raise failure.Failure(exceptions.DataError('Invalid comments link')) + raise failure.Failure(exceptions.DataError("Invalid comments link")) return (service, node) @@ -682,35 +880,49 @@ @param access: Node access model, according to xep-0060 #4.5 @param profile_key: profile key """ - # FIXME: check if this mehtod is need, deprecate it if not + # FIXME: check if this mehtod is need, deprecate it if not client = self.host.getClient(profile_key) - _options = {self._p.OPT_ACCESS_MODEL: access, self._p.OPT_PERSIST_ITEMS: 1, self._p.OPT_MAX_ITEMS: -1, self._p.OPT_DELIVER_PAYLOADS: 1, self._p.OPT_SEND_ITEM_SUBSCRIBE: 1} + _options = { + self._p.OPT_ACCESS_MODEL: access, + self._p.OPT_PERSIST_ITEMS: 1, + self._p.OPT_MAX_ITEMS: -1, + self._p.OPT_DELIVER_PAYLOADS: 1, + self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, + } def cb(result): - #Node is created with right permission + # Node is created with right permission log.debug(_(u"Microblog node has now access %s") % access) def fatal_err(s_error): - #Something went wrong + # Something went wrong log.error(_(u"Can't set microblog access")) raise NodeAccessChangeException() def err_cb(s_error): - #If the node already exists, the condition is "conflict", - #else we have an unmanaged error - if s_error.value.condition == 'conflict': - #d = self.host.plugins["XEP-0060"].deleteNode(client, client.jid.userhostJID(), NS_MICROBLOG) - #d.addCallback(lambda x: create_node().addCallback(cb).addErrback(fatal_err)) + # If the node already exists, the condition is "conflict", + # else we have an unmanaged error + if s_error.value.condition == "conflict": + # d = self.host.plugins["XEP-0060"].deleteNode(client, client.jid.userhostJID(), NS_MICROBLOG) + # d.addCallback(lambda x: create_node().addCallback(cb).addErrback(fatal_err)) change_node_options().addCallback(cb).addErrback(fatal_err) else: fatal_err(s_error) def create_node(): - return self._p.createNode(client, client.jid.userhostJID(), NS_MICROBLOG, _options) + return self._p.createNode( + client, client.jid.userhostJID(), NS_MICROBLOG, _options + ) def change_node_options(): - return self._p.setOptions(client.jid.userhostJID(), NS_MICROBLOG, client.jid.userhostJID(), _options, profile_key=profile_key) + return self._p.setOptions( + client.jid.userhostJID(), + NS_MICROBLOG, + client.jid.userhostJID(), + _options, + profile_key=profile_key, + ) create_node().addCallback(cb).addErrback(err_cb) @@ -735,12 +947,17 @@ jids_set = client.roster.getJidsSet(publishers_type, publishers) if publishers_type == C.ALL: try: # display messages from salut-a-toi@libervia.org or other PEP services - services = self.host.plugins["EXTRA-PEP"].getFollowedEntities(profile_key) + services = self.host.plugins["EXTRA-PEP"].getFollowedEntities( + profile_key + ) except KeyError: pass # plugin is not loaded else: if services: - log.debug("Extra PEP followed entities: %s" % ", ".join([unicode(service) for service in services])) + log.debug( + "Extra PEP followed entities: %s" + % ", ".join([unicode(service) for service in services]) + ) jids_set.update(services) node_data = [] @@ -757,7 +974,11 @@ """ if publishers_type == C.ALL: if publishers: - raise failure.Failure(ValueError("Can't use publishers with {} type".format(publishers_type))) + raise failure.Failure( + ValueError( + "Can't use publishers with {} type".format(publishers_type) + ) + ) else: publishers = None elif publishers_type == C.JID: @@ -785,8 +1006,12 @@ @param profile: %(doc_profile)s @return (str): session id """ - client, node_data = self._getClientAndNodeData(publishers_type, publishers, profile_key) - return self._p.subscribeToMany(node_data, client.jid.userhostJID(), profile_key=profile_key) + client, node_data = self._getClientAndNodeData( + publishers_type, publishers, profile_key + ) + return self._p.subscribeToMany( + node_data, client.jid.userhostJID(), profile_key=profile_key + ) # get # @@ -804,32 +1029,65 @@ - items_metadata(dict): metadata as returned by [mbGet] @param profile_key: %(doc_profile_key)s """ + def onSuccess(items_data): """convert items elements to list of microblog data in items_data""" d = self._p.serItemsDataD(items_data, self.item2mbdata) - d.addCallback(lambda serialised:('', serialised)) + d.addCallback(lambda serialised: ("", serialised)) return d profile = self.host.getClient(profile_key).profile - d = self._p.getRTResults(session_id, - on_success = onSuccess, - on_error = lambda failure: (unicode(failure.value), ([],{})), - profile = profile) - d.addCallback(lambda ret: (ret[0], - [(service.full(), node, failure, items, metadata) - for (service, node), (success, (failure, (items, metadata))) in ret[1].iteritems()])) + d = self._p.getRTResults( + session_id, + on_success=onSuccess, + on_error=lambda failure: (unicode(failure.value), ([], {})), + profile=profile, + ) + d.addCallback( + lambda ret: ( + ret[0], + [ + (service.full(), node, failure, items, metadata) + for (service, node), (success, (failure, (items, metadata))) in ret[ + 1 + ].iteritems() + ], + ) + ) return d - def _mbGetFromMany(self, publishers_type, publishers, max_items=10, extra_dict=None, profile_key=C.PROF_KEY_NONE): + def _mbGetFromMany( + self, + publishers_type, + publishers, + max_items=10, + extra_dict=None, + profile_key=C.PROF_KEY_NONE, + ): """ @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit """ max_items = None if max_items == C.NO_LIMIT else max_items publishers_type, publishers = self._checkPublishers(publishers_type, publishers) extra = self._p.parseExtra(extra_dict) - return self.mbGetFromMany(publishers_type, publishers, max_items, extra.rsm_request, extra.extra, profile_key) + return self.mbGetFromMany( + publishers_type, + publishers, + max_items, + extra.rsm_request, + extra.extra, + profile_key, + ) - def mbGetFromMany(self, publishers_type, publishers, max_items=None, rsm_request=None, extra=None, profile_key=C.PROF_KEY_NONE): + def mbGetFromMany( + self, + publishers_type, + publishers, + max_items=None, + rsm_request=None, + extra=None, + profile_key=C.PROF_KEY_NONE, + ): """Get the published microblogs for a list of groups or jids @param publishers_type (str): type of the list of publishers (one of "GROUP" or "JID" or "ALL") @@ -841,12 +1099,18 @@ @return (str): RT Deferred session id """ # XXX: extra is unused here so far - client, node_data = self._getClientAndNodeData(publishers_type, publishers, profile_key) - return self._p.getFromMany(node_data, max_items, rsm_request, profile_key=profile_key) + client, node_data = self._getClientAndNodeData( + publishers_type, publishers, profile_key + ) + return self._p.getFromMany( + node_data, max_items, rsm_request, profile_key=profile_key + ) # comments # - def _mbGetFromManyWithCommentsRTResult(self, session_id, profile_key=C.PROF_KEY_DEFAULT): + def _mbGetFromManyWithCommentsRTResult( + self, session_id, profile_key=C.PROF_KEY_DEFAULT + ): """Get real-time results for [mbGetFromManyWithComments] session @param session_id: id of the real-time deferred session @@ -869,12 +1133,29 @@ """ profile = self.host.getClient(profile_key).profile d = self.rt_sessions.getResults(session_id, profile=profile) - d.addCallback(lambda ret: (ret[0], - [(service.full(), node, failure, items, metadata) - for (service, node), (success, (failure, (items, metadata))) in ret[1].iteritems()])) + d.addCallback( + lambda ret: ( + ret[0], + [ + (service.full(), node, failure, items, metadata) + for (service, node), (success, (failure, (items, metadata))) in ret[ + 1 + ].iteritems() + ], + ) + ) return d - def _mbGetFromManyWithComments(self, publishers_type, publishers, max_items=10, max_comments=C.NO_LIMIT, extra_dict=None, extra_comments_dict=None, profile_key=C.PROF_KEY_NONE): + def _mbGetFromManyWithComments( + self, + publishers_type, + publishers, + max_items=10, + max_comments=C.NO_LIMIT, + extra_dict=None, + extra_comments_dict=None, + profile_key=C.PROF_KEY_NONE, + ): """ @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit @param max_comments(int): maximum number of comments to get, C.NO_LIMIT for no limit @@ -884,14 +1165,30 @@ publishers_type, publishers = self._checkPublishers(publishers_type, publishers) extra = self._p.parseExtra(extra_dict) extra_comments = self._p.parseExtra(extra_comments_dict) - return self.mbGetFromManyWithComments(publishers_type, publishers, max_items, max_comments or None, - extra.rsm_request, - extra.extra, - extra_comments.rsm_request, - extra_comments.extra, - profile_key) + return self.mbGetFromManyWithComments( + publishers_type, + publishers, + max_items, + max_comments or None, + extra.rsm_request, + extra.extra, + extra_comments.rsm_request, + extra_comments.extra, + profile_key, + ) - def mbGetFromManyWithComments(self, publishers_type, publishers, max_items=None, max_comments=None, rsm_request=None, extra=None, rsm_comments=None, extra_comments=None, profile_key=C.PROF_KEY_NONE): + def mbGetFromManyWithComments( + self, + publishers_type, + publishers, + max_items=None, + max_comments=None, + rsm_request=None, + extra=None, + rsm_comments=None, + extra_comments=None, + profile_key=C.PROF_KEY_NONE, + ): """Helper method to get the microblogs and their comments in one shot @param publishers_type (str): type of the list of publishers (one of "GROUP" or "JID" or "ALL") @@ -909,7 +1206,9 @@ # to serialise and associate the data, but it make life in frontends side # a lot easier - client, node_data = self._getClientAndNodeData(publishers_type, publishers, profile_key) + client, node_data = self._getClientAndNodeData( + publishers_type, publishers, profile_key + ) def getComments(items_data): """Retrieve comments and add them to the items_data @@ -919,29 +1218,50 @@ with a list of comments data (service, node, list of items, metadata) """ items, metadata = items_data - items_dlist = [] # deferred list for items + items_dlist = [] # deferred list for items for item in items: - dlist = [] # deferred list for comments + dlist = [] # deferred list for comments for key, value in item.iteritems(): # we look for comments - if key.startswith('comments') and key.endswith('_service'): - prefix = key[:key.find('_')] + if key.startswith("comments") and key.endswith("_service"): + prefix = key[: key.find("_")] service_s = value node = item["{}{}".format(prefix, "_node")] # time to get the comments - d = self._p.getItems(client, jid.JID(service_s), node, max_comments, rsm_request=rsm_comments, extra=extra_comments) + d = self._p.getItems( + client, + jid.JID(service_s), + node, + max_comments, + rsm_request=rsm_comments, + extra=extra_comments, + ) # then serialise - d.addCallback(lambda items_data: self._p.serItemsDataD(items_data, self.item2mbdata)) + d.addCallback( + lambda items_data: self._p.serItemsDataD( + items_data, self.item2mbdata + ) + ) # with failure handling - d.addCallback(lambda serialised_items_data: ('',) + serialised_items_data) + d.addCallback( + lambda serialised_items_data: ("",) + serialised_items_data + ) d.addErrback(lambda failure: (unicode(failure.value), [], {})) # and associate with service/node (needed if there are several comments nodes) - d.addCallback(lambda serialised, service_s=service_s, node=node: (service_s, node) + serialised) + d.addCallback( + lambda serialised, service_s=service_s, node=node: ( + service_s, + node, + ) + + serialised + ) dlist.append(d) # we get the comments comments_d = defer.gatherResults(dlist) # and add them to the item data - comments_d.addCallback(lambda comments_data, item=item: (item, comments_data)) + comments_d.addCallback( + lambda comments_data, item=item: (item, comments_data) + ) items_dlist.append(comments_d) # we gather the items + comments in a list items_d = defer.gatherResults(items_dlist) @@ -951,11 +1271,15 @@ deferreds = {} for service, node in node_data: - d = deferreds[(service, node)] = self._p.getItems(client, service, node, max_items, rsm_request=rsm_request, extra=extra) - d.addCallback(lambda items_data: self._p.serItemsDataD(items_data, self.item2mbdata)) + d = deferreds[(service, node)] = self._p.getItems( + client, service, node, max_items, rsm_request=rsm_request, extra=extra + ) + d.addCallback( + lambda items_data: self._p.serItemsDataD(items_data, self.item2mbdata) + ) d.addCallback(getComments) - d.addCallback(lambda items_comments_data: ('', items_comments_data)) - d.addErrback(lambda failure: (unicode(failure.value), ([],{}))) + d.addCallback(lambda items_comments_data: ("", items_comments_data)) + d.addErrback(lambda failure: (unicode(failure.value), ([], {}))) return self.rt_sessions.newSession(deferreds, client.profile) @@ -963,8 +1287,8 @@ class XEP_0277_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_MICROBLOG)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0280.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0280.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,6 +19,7 @@ from sat.core.i18n import _, D_ from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from sat.core.constants import Const as C @@ -27,6 +28,7 @@ from twisted.internet import defer from wokkel import disco, iwokkel from zope.interface import implements + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: @@ -36,7 +38,7 @@ PARAM_CATEGORY = "Misc" PARAM_NAME = "carbon" PARAM_LABEL = D_(u"Message carbons") -NS_CARBONS = 'urn:xmpp:carbons:2' +NS_CARBONS = "urn:xmpp:carbons:2" PLUGIN_INFO = { C.PI_NAME: u"XEP-0280 Plugin", @@ -46,12 +48,12 @@ C.PI_DEPENDENCIES: [], C.PI_MAIN: u"XEP_0280", C.PI_HANDLER: u"yes", - C.PI_DESCRIPTION: D_(u"""Implementation of Message Carbons""") + C.PI_DESCRIPTION: D_(u"""Implementation of Message Carbons"""), } class XEP_0280(object): - # TODO: param is only checked at profile connection + # TODO: param is only checked at profile connection # activate carbons on param change even after profile connection # TODO: chat state notifications are not handled yet (and potentially other XEPs?) @@ -64,11 +66,11 @@ </individual> </params> """.format( - category_name = PARAM_CATEGORY, - category_label = D_(PARAM_CATEGORY), - param_name = PARAM_NAME, - param_label = PARAM_LABEL, - ) + category_name=PARAM_CATEGORY, + category_label=D_(PARAM_CATEGORY), + param_name=PARAM_NAME, + param_label=PARAM_LABEL, + ) def __init__(self, host): log.info(_("Plugin XEP_0280 initialization")) @@ -86,15 +88,17 @@ (in particular end 2 end encryption plugins) @param message_elt(domish.Element): <message> stanza """ - if message_elt.name != u'message': + if message_elt.name != u"message": log.error(u"addPrivateElt must be used with <message> stanzas") return - message_elt.addElement((NS_CARBONS, u'private')) + message_elt.addElement((NS_CARBONS, u"private")) @defer.inlineCallbacks def profileConnected(self, client): """activate message carbons on connection if possible and activated in config""" - activate = self.host.memory.getParamA(PARAM_NAME, PARAM_CATEGORY, profile_key=client.profile) + activate = self.host.memory.getParamA( + PARAM_NAME, PARAM_CATEGORY, profile_key=client.profile + ) if not activate: log.info(_(u"Not activating message carbons as requested in params")) return @@ -105,7 +109,7 @@ else: log.info(_(u"message carbons available, enabling it")) iq_elt = client.IQ() - iq_elt.addElement((NS_CARBONS, 'enable')) + iq_elt.addElement((NS_CARBONS, "enable")) try: yield iq_elt.send() except StanzaError as e: @@ -126,40 +130,45 @@ # we continue normal behaviour return True - if message_elt['from'] != client.jid.userhost(): - log.warning(u"The message carbon received is not from our server, hack attempt?\n{xml}".format( - xml = message_elt.toXml(), - )) + if message_elt["from"] != client.jid.userhost(): + log.warning( + u"The message carbon received is not from our server, hack attempt?\n{xml}".format( + xml=message_elt.toXml() + ) + ) return - forwarded_elt = next(carbons_elt.elements(C.NS_FORWARD, 'forwarded')) - cc_message_elt = next(forwarded_elt.elements(C.NS_CLIENT, 'message')) - if carbons_elt.name == 'received': + forwarded_elt = next(carbons_elt.elements(C.NS_FORWARD, "forwarded")) + cc_message_elt = next(forwarded_elt.elements(C.NS_CLIENT, "message")) + if carbons_elt.name == "received": # on receive we replace the wrapping message with the CCed one # and continue the normal behaviour - message_elt['from'] = cc_message_elt['from'] + message_elt["from"] = cc_message_elt["from"] del message_elt.children[:] for c in cc_message_elt.children: message_elt.addChild(c) return True - elif carbons_elt.name == 'sent': + elif carbons_elt.name == "sent": # on send we parse the message and just add it to history # and send it to frontends (without normal sending treatments) mess_data = SatMessageProtocol.parseMessage(cc_message_elt, client) - if not mess_data['message'] and not mess_data['subject']: + if not mess_data["message"] and not mess_data["subject"]: return False client.messageAddToHistory(mess_data) client.messageSendToBridge(mess_data) else: - log.warning(u"invalid message carbons received:\n{xml}".format( - xml = message_elt.toXml())) + log.warning( + u"invalid message carbons received:\n{xml}".format( + xml=message_elt.toXml() + ) + ) return False class XEP_0280_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_CARBONS)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0297.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0297.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,9 +21,11 @@ from sat.core.constants import Const as C from sat.core.i18n import _, D_ from sat.core.log import getLogger + log = getLogger(__name__) from wokkel import disco, iwokkel + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: @@ -41,7 +43,7 @@ C.PI_PROTOCOLS: [u"XEP-0297"], C.PI_MAIN: "XEP_0297", C.PI_HANDLER: u"yes", - C.PI_DESCRIPTION: D_(u"""Implementation of Stanza Forwarding""") + C.PI_DESCRIPTION: D_(u"""Implementation of Stanza Forwarding"""), } @@ -70,7 +72,7 @@ if isinstance(child, domish.Element) and not child.uri: XEP_0297.updateUri(child, uri) - def forward(self, stanza, to_jid, stamp, body='', profile_key=C.PROF_KEY_NONE): + def forward(self, stanza, to_jid, stamp, body="", profile_key=C.PROF_KEY_NONE): """Forward a message to the given JID. @param stanza (domish.Element): original stanza to be forwarded. @@ -82,27 +84,29 @@ """ # FIXME: this method is not used and doesn't use mess_data which should be used for client.sendMessageData # should it be deprecated? A method constructing the element without sending it seems more natural - log.warning(u"THIS METHOD IS DEPRECATED") # FIXME: we use this warning until we check the method - msg = domish.Element((None, 'message')) - msg['to'] = to_jid.full() - msg['type'] = stanza['type'] + log.warning( + u"THIS METHOD IS DEPRECATED" + ) # FIXME: we use this warning until we check the method + msg = domish.Element((None, "message")) + msg["to"] = to_jid.full() + msg["type"] = stanza["type"] - body_elt = domish.Element((None, 'body')) + body_elt = domish.Element((None, "body")) if body: body_elt.addContent(body) - forwarded_elt = domish.Element((NS_SF, 'forwarded')) - delay_elt = self.host.plugins['XEP-0203'].delay(stamp) + forwarded_elt = domish.Element((NS_SF, "forwarded")) + delay_elt = self.host.plugins["XEP-0203"].delay(stamp) forwarded_elt.addChild(delay_elt) if not stanza.uri: # None or '' - XEP_0297.updateUri(stanza, 'jabber:client') + XEP_0297.updateUri(stanza, "jabber:client") forwarded_elt.addChild(stanza) msg.addChild(body_elt) msg.addChild(forwarded_elt) client = self.host.getClient(profile_key) - return client.sendMessageData({u'xml': msg}) + return client.sendMessageData({u"xml": msg}) class XEP_0297_handler(XMPPHandler): @@ -113,8 +117,8 @@ self.host = plugin_parent.host self.profile = profile - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_SF)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0300.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0300.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from twisted.words.xish import domish @@ -41,27 +42,29 @@ C.PI_PROTOCOLS: ["XEP-0300"], C.PI_MAIN: "XEP_0300", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Management of cryptographic hashes""") + C.PI_DESCRIPTION: _("""Management of cryptographic hashes"""), } NS_HASHES = "urn:xmpp:hashes:2" NS_HASHES_FUNCTIONS = u"urn:xmpp:hash-function-text-names:{}" -BUFFER_SIZE = 2**12 -ALGO_DEFAULT = 'sha-256' +BUFFER_SIZE = 2 ** 12 +ALGO_DEFAULT = "sha-256" class XEP_0300(object): # TODO: add blake after moving to Python 3 - ALGOS = OrderedDict(( - (u'md5', hashlib.md5), - (u'sha-1', hashlib.sha1), - (u'sha-256', hashlib.sha256), - (u'sha-512', hashlib.sha512), - )) + ALGOS = OrderedDict( + ( + (u"md5", hashlib.md5), + (u"sha-1", hashlib.sha1), + (u"sha-256", hashlib.sha256), + (u"sha-512", hashlib.sha512), + ) + ) def __init__(self, host): log.info(_("plugin Hashes initialization")) - host.registerNamespace('hashes', NS_HASHES) + host.registerNamespace("hashes", NS_HASHES) def getHandler(self, client): return XEP_0300_handler() @@ -90,11 +93,15 @@ """ client = self.host.getClient(profile) for algo in reversed(XEP_0300.ALGOS): - has_feature = yield self.host.hasFeature(client, NS_HASHES_FUNCTIONS.format(algo), to_jid) + has_feature = yield self.host.hasFeature( + client, NS_HASHES_FUNCTIONS.format(algo), to_jid + ) if has_feature: - log.debug(u"Best hashing algorithm found for {jid}: {algo}".format( - jid=to_jid.full(), - algo=algo)) + log.debug( + u"Best hashing algorithm found for {jid}: {algo}".format( + jid=to_jid.full(), algo=algo + ) + ) defer.returnValue(algo) def _calculateHashBlocking(self, file_obj, hasher): @@ -123,16 +130,18 @@ @param algo(unicode): algorithme to use, must be a key of XEP_0300.ALGOS @return (D(domish.Element)): hash element """ + def hashCalculated(hash_): return self.buildHashElt(hash_, algo) + hasher = self.ALGOS[algo] hash_d = self.calculateHash(file_obj, hasher) hash_d.addCallback(hashCalculated) return hash_d def buildHashUsedElt(self, algo=ALGO_DEFAULT): - hash_used_elt = domish.Element((NS_HASHES, 'hash-used')) - hash_used_elt['algo'] = algo + hash_used_elt = domish.Element((NS_HASHES, "hash-used")) + hash_used_elt["algo"] = algo return hash_used_elt def parseHashUsedElt(self, parent): @@ -144,10 +153,10 @@ @raise exceptions.DataError: the element is invalid """ try: - hash_used_elt = next(parent.elements(NS_HASHES, 'hash-used')) + hash_used_elt = next(parent.elements(NS_HASHES, "hash-used")) except StopIteration: raise exceptions.NotFound - algo = hash_used_elt[u'algo'] + algo = hash_used_elt[u"algo"] if not algo: raise exceptions.DataError return algo @@ -161,10 +170,10 @@ """ assert hash_ assert algo - hash_elt = domish.Element((NS_HASHES, 'hash')) + hash_elt = domish.Element((NS_HASHES, "hash")) if hash_ is not None: hash_elt.addContent(base64.b64encode(hash_)) - hash_elt['algo'] = algo + hash_elt["algo"] = algo return hash_elt def parseHashElt(self, parent): @@ -181,8 +190,8 @@ hash_elt = None best_algo = None best_value = None - for hash_elt in parent.elements(NS_HASHES, 'hash'): - algo = hash_elt.getAttribute('algo') + for hash_elt in parent.elements(NS_HASHES, "hash"): + algo = hash_elt.getAttribute("algo") try: idx = algos.index(algo) except ValueError: @@ -204,9 +213,12 @@ class XEP_0300_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): - hash_functions_names = [disco.DiscoFeature(NS_HASHES_FUNCTIONS.format(algo)) for algo in XEP_0300.ALGOS] + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): + hash_functions_names = [ + disco.DiscoFeature(NS_HASHES_FUNCTIONS.format(algo)) + for algo in XEP_0300.ALGOS + ] return [disco.DiscoFeature(NS_HASHES)] + hash_functions_names - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0313.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0313.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core.constants import Const as C from sat.core.i18n import _ from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions @@ -45,12 +46,11 @@ C.PI_PROTOCOLS: ["XEP-0313"], C.PI_MAIN: "XEP_0313", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Message Archive Management""") + C.PI_DESCRIPTION: _("""Implementation of Message Archive Management"""), } class XEP_0313(object): - def __init__(self, host): log.info(_("Message Archive Management plugin initialization")) self.host = host @@ -86,7 +86,7 @@ def _queryFinished(self, iq_result, client, elt_list, event): client.xmlstream.removeObserver(event, self._appendMessage) try: - fin_elt = iq_result.elements(mam.NS_MAM, 'fin').next() + fin_elt = iq_result.elements(mam.NS_MAM, "fin").next() except StopIteration: raise exceptions.DataError(u"Invalid MAM result") @@ -119,14 +119,21 @@ # http://xmpp.org/extensions/xep-0313.html#prefs return client._mam.queryPrefs(service) - def _setPrefs(self, service_s=None, default='roster', always=None, never=None, profile_key=C.PROF_KEY_NONE): + def _setPrefs( + self, + service_s=None, + default="roster", + always=None, + never=None, + profile_key=C.PROF_KEY_NONE, + ): service = jid.JID(service_s) if service_s else None always_jid = [jid.JID(entity) for entity in always] never_jid = [jid.JID(entity) for entity in never] - #TODO: why not build here a MAMPrefs object instead of passing the args separately? + # TODO: why not build here a MAMPrefs object instead of passing the args separately? return self.setPrefs(service, default, always_jid, never_jid, profile_key) - def setPrefs(self, client, service=None, default='roster', always=None, never=None): + def setPrefs(self, client, service=None, default="roster", always=None, never=None): """Set news user preferences. @param service: entity offering the MAM service (None for user archives) @@ -143,8 +150,8 @@ class SatMAMClient(mam.MAMClient): implements(disco.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(mam.NS_MAM)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0329.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0329.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core import exceptions from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.tools import stream from sat.tools.common import regex @@ -44,30 +45,35 @@ C.PI_DEPENDENCIES: ["XEP-0234", "XEP-0300"], C.PI_MAIN: "XEP_0329", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _(u"""Implementation of File Information Sharing""") + C.PI_DESCRIPTION: _(u"""Implementation of File Information Sharing"""), } -NS_FIS = 'urn:xmpp:fis:0' +NS_FIS = "urn:xmpp:fis:0" IQ_FIS_REQUEST = C.IQ_GET + '/query[@xmlns="' + NS_FIS + '"]' SINGLE_FILES_DIR = u"files" -TYPE_VIRTUAL= u'virtual' -TYPE_PATH = u'path' -SHARE_TYPES = (TYPE_PATH, TYPE_VIRTUAL) -KEY_TYPE = u'type' +TYPE_VIRTUAL = u"virtual" +TYPE_PATH = u"path" +SHARE_TYPES = (TYPE_PATH, TYPE_VIRTUAL) +KEY_TYPE = u"type" class ShareNode(object): """node containing directory or files to share, virtual or real""" + host = None def __init__(self, name, parent, type_, access, path=None): assert type_ in SHARE_TYPES if name is not None: - if name == u'..' or u'/' in name or u'\\' in name: - log.warning(_(u'path change chars found in name [{name}], hack attempt?').format(name=name)) - if name == u'..': - name = u'--' + if name == u".." or u"/" in name or u"\\" in name: + log.warning( + _(u"path change chars found in name [{name}], hack attempt?").format( + name=name + ) + ) + if name == u"..": + name = u"--" else: name = regex.pathEscape(name) self.name = name @@ -125,7 +131,9 @@ try: del self.parent.children[self.name] except TypeError: - raise exceptions.InternalError(u'trying to remove a node from inexisting parent') + raise exceptions.InternalError( + u"trying to remove a node from inexisting parent" + ) except KeyError: raise exceptions.InternalError(u"node not found in parent's children") self.parent = None @@ -138,7 +146,7 @@ @param peer_jid(jid.JID): entity which try to access the node @return (bool): True if entity can access """ - file_data = {u'access':self.access, u'owner': client.jid.userhostJID()} + file_data = {u"access": self.access, u"owner": client.jid.userhostJID()} try: self.host.memory.checkFilePermission(file_data, peer_jid, perms) except exceptions.PermissionError: @@ -146,7 +154,9 @@ else: return True - def checkPermissions(self, client, peer_jid, perms=(C.ACCESS_PERM_READ,), check_parents=True): + def checkPermissions( + self, client, peer_jid, perms=(C.ACCESS_PERM_READ,), check_parents=True + ): """check that peer_jid can access this node and all its parents @param peer_jid(jid.JID): entrity trying to access the node @@ -178,15 +188,18 @@ @raise exceptions.DataError: path is invalid @raise NotFound: path lead to a non existing file/directory """ - path_elts = filter(None, path.split(u'/')) + path_elts = filter(None, path.split(u"/")) - if u'..' in path_elts: - log.warning(_(u'parent dir ("..") found in path, hack attempt? path is {path} [{profile}]').format( - path=path, profile=client.profile)) + if u".." in path_elts: + log.warning( + _( + u'parent dir ("..") found in path, hack attempt? path is {path} [{profile}]' + ).format(path=path, profile=client.profile) + ) raise exceptions.PermissionError(u"illegal path elements") if not path_elts: - raise exceptions.DataError(_(u'path is invalid: {path}').format(path=path)) + raise exceptions.DataError(_(u"path is invalid: {path}").format(path=path)) node = client._XEP_0329_root_node @@ -199,10 +212,10 @@ elif node.type == TYPE_PATH: break - if not node.checkPermissions(client, peer_jid, perms = perms): + if not node.checkPermissions(client, peer_jid, perms=perms): raise exceptions.PermissionError(u"permission denied") - return node, u'/'.join(path_elts) + return node, u"/".join(path_elts) def findByLocalPath(self, path): """retrieve nodes linking to local path @@ -223,8 +236,9 @@ elif node.type == TYPE_PATH: paths.setdefault(node.path, []).append(node) else: - raise exceptions.InternalError(u'unknown node type: {type}'.format( - type = node.type)) + raise exceptions.InternalError( + u"unknown node type: {type}".format(type=node.type) + ) def getSharedPaths(self): """retrieve nodes by shared path @@ -233,38 +247,71 @@ @return (dict): map from shared path to list of nodes """ if self.type == TYPE_PATH: - raise exceptions.InternalError("getSharedPaths must be used on a virtual node") + raise exceptions.InternalError( + "getSharedPaths must be used on a virtual node" + ) paths = {} self._getSharedPaths(self, paths) return paths class XEP_0329(object): - def __init__(self, host): log.info(_("File Information Sharing initialization")) self.host = host ShareNode.host = host - self._h = host.plugins['XEP-0300'] - self._jf = host.plugins['XEP-0234'] - host.bridge.addMethod("FISList", ".plugin", in_sign='ssa{ss}s', out_sign='aa{ss}', method=self._listFiles, async=True) - host.bridge.addMethod("FISLocalSharesGet", ".plugin", in_sign='s', out_sign='as', method=self._localSharesGet) - host.bridge.addMethod("FISSharePath", ".plugin", in_sign='ssss', out_sign='s', method=self._sharePath) - host.bridge.addMethod("FISUnsharePath", ".plugin", in_sign='ss', out_sign='', method=self._unsharePath) - host.bridge.addSignal("FISSharedPathNew", ".plugin", signature='sss') - host.bridge.addSignal("FISSharedPathRemoved", ".plugin", signature='ss') + self._h = host.plugins["XEP-0300"] + self._jf = host.plugins["XEP-0234"] + host.bridge.addMethod( + "FISList", + ".plugin", + in_sign="ssa{ss}s", + out_sign="aa{ss}", + method=self._listFiles, + async=True, + ) + host.bridge.addMethod( + "FISLocalSharesGet", + ".plugin", + in_sign="s", + out_sign="as", + method=self._localSharesGet, + ) + host.bridge.addMethod( + "FISSharePath", + ".plugin", + in_sign="ssss", + out_sign="s", + method=self._sharePath, + ) + host.bridge.addMethod( + "FISUnsharePath", + ".plugin", + in_sign="ss", + out_sign="", + method=self._unsharePath, + ) + host.bridge.addSignal("FISSharedPathNew", ".plugin", signature="sss") + host.bridge.addSignal("FISSharedPathRemoved", ".plugin", signature="ss") host.trigger.add("XEP-0234_fileSendingRequest", self._fileSendingRequestTrigger) - host.registerNamespace('fis', NS_FIS) + host.registerNamespace("fis", NS_FIS) def getHandler(self, client): return XEP_0329_handler(self) def profileConnected(self, client): if not client.is_component: - client._XEP_0329_root_node = ShareNode(None, None, TYPE_VIRTUAL, {C.ACCESS_PERM_READ: {KEY_TYPE: C.ACCESS_TYPE_PUBLIC}}) - client._XEP_0329_names_data = {} # name to share map + client._XEP_0329_root_node = ShareNode( + None, + None, + TYPE_VIRTUAL, + {C.ACCESS_PERM_READ: {KEY_TYPE: C.ACCESS_TYPE_PUBLIC}}, + ) + client._XEP_0329_names_data = {} # name to share map - def _fileSendingRequestTrigger(self, client, session, content_data, content_name, file_data, file_elt): + def _fileSendingRequestTrigger( + self, client, session, content_data, content_name, file_data, file_elt + ): """this trigger check that a requested file is available, and fill suitable data if so path and name are used to retrieve the file. If path is missing, we try our luck with known names @@ -273,21 +320,21 @@ return True, None try: - name = file_data[u'name'] + name = file_data[u"name"] except KeyError: return True, None - assert u'/' not in name + assert u"/" not in name - path = file_data.get(u'path') + path = file_data.get(u"path") if path is not None: # we have a path, we can follow it to find node try: - node, rem_path = ShareNode.find(client, path, session[u'peer_jid']) + node, rem_path = ShareNode.find(client, path, session[u"peer_jid"]) except (exceptions.PermissionError, exceptions.NotFound): - # no file, or file not allowed, we continue normal workflow + # no file, or file not allowed, we continue normal workflow return True, None except exceptions.DataError: - log.warning(_(u'invalid path: {path}').format(path=path)) + log.warning(_(u"invalid path: {path}").format(path=path)) return True, None if node.type == TYPE_VIRTUAL: @@ -300,7 +347,9 @@ # we have a path node, so we can retrieve the full path now path = os.path.join(node.path, rem_path, name) else: - raise exceptions.InternalError(u'unknown type: {type}'.format(type=node.type)) + raise exceptions.InternalError( + u"unknown type: {type}".format(type=node.type) + ) if not os.path.exists(path): return True, None size = os.path.getsize(path) @@ -312,49 +361,56 @@ return True, None for path, shared_file in name_data.iteritems(): - if True: # FIXME: filters are here + if True: # FIXME: filters are here break else: return True, None - parent_node = shared_file[u'parent'] - if not parent_node.checkPermissions(client, session[u'peer_jid']): - log.warning(_(u"{peer_jid} requested a file (s)he can't access [{profile}]").format( - peer_jid = session[u'peer_jid'], profile = client.profile)) + parent_node = shared_file[u"parent"] + if not parent_node.checkPermissions(client, session[u"peer_jid"]): + log.warning( + _( + u"{peer_jid} requested a file (s)he can't access [{profile}]" + ).format(peer_jid=session[u"peer_jid"], profile=client.profile) + ) return True, None - size = shared_file[u'size'] + size = shared_file[u"size"] - file_data[u'size'] = size - file_elt.addElement(u'size', content=unicode(size)) - hash_algo = file_data[u'hash_algo'] = self._h.getDefaultAlgo() - hasher = file_data[u'hash_hasher'] = self._h.getHasher(hash_algo) + file_data[u"size"] = size + file_elt.addElement(u"size", content=unicode(size)) + hash_algo = file_data[u"hash_algo"] = self._h.getDefaultAlgo() + hasher = file_data[u"hash_hasher"] = self._h.getHasher(hash_algo) file_elt.addChild(self._h.buildHashUsedElt(hash_algo)) - content_data['stream_object'] = stream.FileStreamObject( + content_data["stream_object"] = stream.FileStreamObject( self.host, client, path, uid=self._jf.getProgressId(session, content_name), size=size, data_cb=lambda data: hasher.update(data), - ) + ) return False, True # common methods def _requestHandler(self, client, iq_elt, root_nodes_cb, files_from_node_cb): iq_elt.handled = True - owner = jid.JID(iq_elt['from']).userhostJID() - node = iq_elt.query.getAttribute('node') + owner = jid.JID(iq_elt["from"]).userhostJID() + node = iq_elt.query.getAttribute("node") if not node: d = defer.maybeDeferred(root_nodes_cb, client, iq_elt, owner) else: d = defer.maybeDeferred(files_from_node_cb, client, iq_elt, owner, node) - d.addErrback(lambda failure_: log.error(_(u"error while retrieving files: {msg}").format(msg=failure_))) + d.addErrback( + lambda failure_: log.error( + _(u"error while retrieving files: {msg}").format(msg=failure_) + ) + ) def _iqError(self, client, iq_elt, condition="item-not-found"): error_elt = jabber_error.StanzaError(condition).toResponse(iq_elt) client.send(error_elt) - # client + # client def _addPathData(self, client, query_elt, path, parent_node): """Fill query_elt with files/directories found in path""" @@ -362,23 +418,24 @@ if os.path.isfile(path): size = os.path.getsize(path) mime_type = mimetypes.guess_type(path, strict=False)[0] - file_elt = self._jf.buildFileElement(name = name, - size = size, - mime_type = mime_type, - modified = os.path.getmtime(path)) + file_elt = self._jf.buildFileElement( + name=name, size=size, mime_type=mime_type, modified=os.path.getmtime(path) + ) query_elt.addChild(file_elt) # we don't specify hash as it would be too resource intensive to calculate it for all files # we add file to name_data, so users can request it later name_data = client._XEP_0329_names_data.setdefault(name, {}) if path not in name_data: - name_data[path] = {'size': size, - 'mime_type': mime_type, - 'parent': parent_node} + name_data[path] = { + "size": size, + "mime_type": mime_type, + "parent": parent_node, + } else: # we have a directory - directory_elt = query_elt.addElement('directory') - directory_elt['name'] = name + directory_elt = query_elt.addElement("directory") + directory_elt["name"] = name def _pathNodeHandler(self, client, iq_elt, query_elt, node, path): """Fill query_elt for path nodes, i.e. physical directories""" @@ -390,13 +447,15 @@ elif os.path.isfile(path): self._addPathData(client, query_elt, path, node) else: - for name in sorted(os.listdir(path.encode('utf-8')), key=lambda n: n.lower()): + for name in sorted(os.listdir(path.encode("utf-8")), key=lambda n: n.lower()): try: - name = name.decode('utf-8', 'strict') + name = name.decode("utf-8", "strict") except UnicodeDecodeError as e: - log.warning(_(u"ignoring invalid unicode name ({name}): {msg}").format( - name = name.decode('utf-8', 'replace'), - msg = e)) + log.warning( + _(u"ignoring invalid unicode name ({name}): {msg}").format( + name=name.decode("utf-8", "replace"), msg=e + ) + ) continue full_path = os.path.join(path, name) self._addPathData(client, query_elt, full_path, node) @@ -408,68 +467,79 @@ continue node_type = child_node.type if node_type == TYPE_VIRTUAL: - directory_elt = query_elt.addElement('directory') - directory_elt['name'] = name + directory_elt = query_elt.addElement("directory") + directory_elt["name"] = name elif node_type == TYPE_PATH: self._addPathData(client, query_elt, child_node.path, child_node) else: - raise exceptions.InternalError(_(u'unexpected type: {type}').format(type=node_type)) + raise exceptions.InternalError( + _(u"unexpected type: {type}").format(type=node_type) + ) def _getRootNodesCb(self, client, iq_elt, owner): - peer_jid = jid.JID(iq_elt['from']) - iq_result_elt = xmlstream.toResponse(iq_elt, 'result') - query_elt = iq_result_elt.addElement((NS_FIS, 'query')) + peer_jid = jid.JID(iq_elt["from"]) + iq_result_elt = xmlstream.toResponse(iq_elt, "result") + query_elt = iq_result_elt.addElement((NS_FIS, "query")) for name, node in client._XEP_0329_root_node.iteritems(): if not node.checkPermissions(client, peer_jid, check_parents=False): continue - directory_elt = query_elt.addElement('directory') - directory_elt['name'] = name + directory_elt = query_elt.addElement("directory") + directory_elt["name"] = name client.send(iq_result_elt) def _getFilesFromNodeCb(self, client, iq_elt, owner, node_path): """Main method to retrieve files/directories from a node_path""" - peer_jid = jid.JID(iq_elt[u'from']) + peer_jid = jid.JID(iq_elt[u"from"]) try: node, path = ShareNode.find(client, node_path, peer_jid) except (exceptions.PermissionError, exceptions.NotFound): return self._iqError(client, iq_elt) except exceptions.DataError: - return self._iqError(client, iq_elt, condition='not-acceptable') + return self._iqError(client, iq_elt, condition="not-acceptable") node_type = node.type - peer_jid = jid.JID(iq_elt['from']) - iq_result_elt = xmlstream.toResponse(iq_elt, 'result') - query_elt = iq_result_elt.addElement((NS_FIS, 'query')) - query_elt[u'node'] = node_path + peer_jid = jid.JID(iq_elt["from"]) + iq_result_elt = xmlstream.toResponse(iq_elt, "result") + query_elt = iq_result_elt.addElement((NS_FIS, "query")) + query_elt[u"node"] = node_path # we now fill query_elt according to node_type if node_type == TYPE_PATH: - # it's a physical path + # it's a physical path self._pathNodeHandler(client, iq_elt, query_elt, node, path) elif node_type == TYPE_VIRTUAL: assert not path self._virtualNodeHandler(client, peer_jid, iq_elt, query_elt, node) else: - raise exceptions.InternalError(_(u'unknown node type: {type}').format(type=node_type)) + raise exceptions.InternalError( + _(u"unknown node type: {type}").format(type=node_type) + ) client.send(iq_result_elt) def onRequest(self, iq_elt, client): - return self._requestHandler(client, iq_elt, self._getRootNodesCb, self._getFilesFromNodeCb) + return self._requestHandler( + client, iq_elt, self._getRootNodesCb, self._getFilesFromNodeCb + ) # Component @defer.inlineCallbacks def _compGetRootNodesCb(self, client, iq_elt, owner): - peer_jid = jid.JID(iq_elt['from']) - files_data = yield self.host.memory.getFiles(client, peer_jid=peer_jid, parent=u'', - type_=C.FILE_TYPE_DIRECTORY, owner=owner) - iq_result_elt = xmlstream.toResponse(iq_elt, 'result') - query_elt = iq_result_elt.addElement((NS_FIS, 'query')) + peer_jid = jid.JID(iq_elt["from"]) + files_data = yield self.host.memory.getFiles( + client, + peer_jid=peer_jid, + parent=u"", + type_=C.FILE_TYPE_DIRECTORY, + owner=owner, + ) + iq_result_elt = xmlstream.toResponse(iq_elt, "result") + query_elt = iq_result_elt.addElement((NS_FIS, "query")) for file_data in files_data: - name = file_data[u'name'] - directory_elt = query_elt.addElement(u'directory') - directory_elt[u'name'] = name + name = file_data[u"name"] + directory_elt = query_elt.addElement(u"directory") + directory_elt[u"name"] = name client.send(iq_result_elt) @defer.inlineCallbacks @@ -479,44 +549,53 @@ result stanza is then built and sent to requestor @trigger XEP-0329_compGetFilesFromNode(client, iq_elt, owner, node_path, files_data): can be used to add data/elements """ - peer_jid = jid.JID(iq_elt['from']) + peer_jid = jid.JID(iq_elt["from"]) try: - files_data = yield self.host.memory.getFiles(client, peer_jid=peer_jid, path=node_path, owner=owner) + files_data = yield self.host.memory.getFiles( + client, peer_jid=peer_jid, path=node_path, owner=owner + ) except exceptions.NotFound: self._iqError(client, iq_elt) return - iq_result_elt = xmlstream.toResponse(iq_elt, 'result') - query_elt = iq_result_elt.addElement((NS_FIS, 'query')) - query_elt[u'node'] = node_path - if not self.host.trigger.point(u'XEP-0329_compGetFilesFromNode', client, iq_elt, owner, node_path, files_data): + iq_result_elt = xmlstream.toResponse(iq_elt, "result") + query_elt = iq_result_elt.addElement((NS_FIS, "query")) + query_elt[u"node"] = node_path + if not self.host.trigger.point( + u"XEP-0329_compGetFilesFromNode", client, iq_elt, owner, node_path, files_data + ): return for file_data in files_data: - file_elt = self._jf.buildFileElementFromDict(file_data, - modified=file_data.get(u'modified', file_data[u'created'])) + file_elt = self._jf.buildFileElementFromDict( + file_data, modified=file_data.get(u"modified", file_data[u"created"]) + ) query_elt.addChild(file_elt) client.send(iq_result_elt) def onComponentRequest(self, iq_elt, client): - return self._requestHandler(client, iq_elt, self._compGetRootNodesCb, self._compGetFilesFromNodeCb) + return self._requestHandler( + client, iq_elt, self._compGetRootNodesCb, self._compGetFilesFromNodeCb + ) def _parseResult(self, iq_elt): - query_elt = next(iq_elt.elements(NS_FIS, 'query')) + query_elt = next(iq_elt.elements(NS_FIS, "query")) files = [] for elt in query_elt.elements(): - if elt.name == 'file': + if elt.name == "file": # we have a file try: file_data = self._jf.parseFileElement(elt) except exceptions.DataError: continue - file_data[u'type'] = C.FILE_TYPE_FILE - elif elt.name == 'directory' and elt.uri == NS_FIS: + file_data[u"type"] = C.FILE_TYPE_FILE + elif elt.name == "directory" and elt.uri == NS_FIS: # we have a directory - file_data = {'name': elt['name'], 'type': C.FILE_TYPE_DIRECTORY} + file_data = {"name": elt["name"], "type": C.FILE_TYPE_DIRECTORY} else: - log.warning(_(u"unexpected element, ignoring: {elt}").format(elt=elt.toXml())) + log.warning( + _(u"unexpected element, ignoring: {elt}").format(elt=elt.toXml()) + ) continue files.append(file_data) return files @@ -526,7 +605,9 @@ def _serializeData(self, files_data): for file_data in files_data: for key, value in file_data.iteritems(): - file_data[key] = json.dumps(value) if key in ('extra',) else unicode(value) + file_data[key] = ( + json.dumps(value) if key in ("extra",) else unicode(value) + ) return files_data def _listFiles(self, target_jid, path, extra, profile): @@ -545,11 +626,11 @@ @param extra(dict, None): extra data @return list(dict): shared files """ - iq_elt = client.IQ('get') - iq_elt['to'] = target_jid.full() - query_elt = iq_elt.addElement((NS_FIS, 'query')) + iq_elt = client.IQ("get") + iq_elt["to"] = target_jid.full() + query_elt = iq_elt.addElement((NS_FIS, "query")) if path: - query_elt['node'] = path + query_elt["node"] = path d = iq_elt.send() d.addCallback(self._parseResult) return d @@ -563,7 +644,7 @@ def _sharePath(self, name, path, access, profile): client = self.host.getClient(profile) - access= json.loads(access) + access = json.loads(access) return self.sharePath(client, name or None, path, access) def sharePath(self, client, name, path, access): @@ -571,31 +652,36 @@ raise exceptions.ClientTypeError if not os.path.exists(path): raise ValueError(_(u"This path doesn't exist!")) - if not path or not path.strip(u' /'): + if not path or not path.strip(u" /"): raise ValueError(_(u"A path need to be specified")) if not isinstance(access, dict): - raise ValueError(_(u'access must be a dict')) + raise ValueError(_(u"access must be a dict")) node = client._XEP_0329_root_node node_type = TYPE_PATH if os.path.isfile(path): - # we have a single file, the workflow is diferrent as we store all single files in the same dir + # we have a single file, the workflow is diferrent as we store all single files in the same dir node = node.getOrCreate(SINGLE_FILES_DIR) if not name: - name = os.path.basename(path.rstrip(u' /')) + name = os.path.basename(path.rstrip(u" /")) if not name: raise exceptions.InternalError(_(u"Can't find a proper name")) if name in node or name == SINGLE_FILES_DIR: idx = 1 - new_name = name + '_' + unicode(idx) + new_name = name + "_" + unicode(idx) while new_name in node: idx += 1 - new_name = name + '_' + unicode(idx) + new_name = name + "_" + unicode(idx) name = new_name - log.info(_(u"A directory with this name is already shared, renamed to {new_name} [{profile}]".format( - new_name=new_name, profile=client.profile))) + log.info( + _( + u"A directory with this name is already shared, renamed to {new_name} [{profile}]".format( + new_name=new_name, profile=client.profile + ) + ) + ) ShareNode(name=name, parent=node, type_=node_type, access=access, path=path) self.host.bridge.FISSharedPathNew(path, name, client.profile) @@ -621,12 +707,16 @@ def connectionInitialized(self): if self.parent.is_component: - self.xmlstream.addObserver(IQ_FIS_REQUEST, self.plugin_parent.onComponentRequest, client=self.parent) + self.xmlstream.addObserver( + IQ_FIS_REQUEST, self.plugin_parent.onComponentRequest, client=self.parent + ) else: - self.xmlstream.addObserver(IQ_FIS_REQUEST, self.plugin_parent.onRequest, client=self.parent) + self.xmlstream.addObserver( + IQ_FIS_REQUEST, self.plugin_parent.onRequest, client=self.parent + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_FIS)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0334.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0334.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,12 +20,14 @@ from sat.core.i18n import _, D_ from sat.core.log import getLogger + log = getLogger(__name__) from sat.core.constants import Const as C from sat.tools.common import data_format from wokkel import disco, iwokkel + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: @@ -42,21 +44,24 @@ C.PI_MAIN: "XEP_0334", C.PI_HANDLER: u"yes", C.PI_DESCRIPTION: D_(u"""Implementation of Message Processing Hints"""), - C.PI_USAGE: dedent(D_(u"""\ + C.PI_USAGE: dedent( + D_( + u"""\ Frontends can use HINT_* constants in mess_data['extra'] in a serialized 'hints' dict. Internal plugins can use directly addHint([HINT_* constant]). - Will set mess_data['extra']['history'] to 'skipped' when no store is requested and message is not saved in history.""")) - + Will set mess_data['extra']['history'] to 'skipped' when no store is requested and message is not saved in history.""" + ) + ), } -NS_HINTS = u'urn:xmpp:hints' +NS_HINTS = u"urn:xmpp:hints" class XEP_0334(object): - HINT_NO_PERMANENT_STORE = u'no-permanent-store' - HINT_NO_STORE = u'no-store' - HINT_NO_COPY = u'no-copy' - HINT_STORE = u'store' + HINT_NO_PERMANENT_STORE = u"no-permanent-store" + HINT_NO_STORE = u"no-store" + HINT_NO_COPY = u"no-copy" + HINT_STORE = u"store" HINTS = (HINT_NO_PERMANENT_STORE, HINT_NO_STORE, HINT_NO_COPY, HINT_STORE) def __init__(self, host): @@ -69,38 +74,45 @@ return XEP_0334_handler() def addHint(self, mess_data, hint): - if hint == self.HINT_NO_COPY and not mess_data['to'].resource: - log.error(u"{hint} can only be used with full jids! Ignoring it.".format(hint=hint)) + if hint == self.HINT_NO_COPY and not mess_data["to"].resource: + log.error( + u"{hint} can only be used with full jids! Ignoring it.".format(hint=hint) + ) return - hints = mess_data.setdefault('hints', set()) + hints = mess_data.setdefault("hints", set()) if hint in self.HINTS: hints.add(hint) else: log.error(u"Unknown hint: {}".format(hint)) def _sendPostXmlTreatment(self, mess_data): - if 'hints' in mess_data: - for hint in mess_data['hints']: - mess_data[u'xml'].addElement((NS_HINTS, hint)) + if "hints" in mess_data: + for hint in mess_data["hints"]: + mess_data[u"xml"].addElement((NS_HINTS, hint)) return mess_data - def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments): + def sendMessageTrigger( + self, client, mess_data, pre_xml_treatments, post_xml_treatments + ): """Add the hints element to the message to be sent""" - if u'hints' in mess_data[u'extra']: - for hint in data_format.dict2iter(u'hints', mess_data[u'extra'], pop=True): + if u"hints" in mess_data[u"extra"]: + for hint in data_format.dict2iter(u"hints", mess_data[u"extra"], pop=True): self.addHint(hint) post_xml_treatments.addCallback(self._sendPostXmlTreatment) return True def _receivedSkipHistory(self, mess_data): - mess_data[u'history'] = C.HISTORY_SKIP + mess_data[u"history"] = C.HISTORY_SKIP return mess_data def messageReceivedTrigger(self, client, message_elt, post_treat): """Check for hints in the received message""" for elt in message_elt.elements(): - if elt.uri == NS_HINTS and elt.name in (self.HINT_NO_PERMANENT_STORE, self.HINT_NO_STORE): + if elt.uri == NS_HINTS and elt.name in ( + self.HINT_NO_PERMANENT_STORE, + self.HINT_NO_STORE, + ): log.debug(u"history will be skipped for this message, as requested") post_treat.addCallback(self._receivedSkipHistory) break @@ -110,8 +122,8 @@ class XEP_0334_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_HINTS)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/plugins/plugin_xep_0363.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/plugins/plugin_xep_0363.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from wokkel import disco, iwokkel @@ -49,18 +50,17 @@ C.PI_DEPENDENCIES: ["FILE", "UPLOAD"], C.PI_MAIN: "XEP_0363", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of HTTP File Upload""") + C.PI_DESCRIPTION: _("""Implementation of HTTP File Upload"""), } -NS_HTTP_UPLOAD = 'urn:xmpp:http:upload' +NS_HTTP_UPLOAD = "urn:xmpp:http:upload" -Slot = namedtuple('Slot', ['put', 'get']) +Slot = namedtuple("Slot", ["put", "get"]) @implementer(IOpenSSLClientConnectionCreator) class NoCheckConnectionCreator(object): - def __init__(self, hostname, ctx): self._ctx = ctx @@ -80,53 +80,91 @@ """ def creatorForNetloc(self, hostname, port): - log.warning(u"TLS check disabled for {host} on port {port}".format(host=hostname, port=port)) + log.warning( + u"TLS check disabled for {host} on port {port}".format( + host=hostname, port=port + ) + ) certificateOptions = ssl.CertificateOptions(trustRoot=None) return NoCheckConnectionCreator(hostname, certificateOptions.getContext()) class XEP_0363(object): - def __init__(self, host): log.info(_("plugin HTTP File Upload initialization")) self.host = host - host.bridge.addMethod("fileHTTPUpload", ".plugin", in_sign='sssbs', out_sign='', method=self._fileHTTPUpload) - host.bridge.addMethod("fileHTTPUploadGetSlot", ".plugin", in_sign='sisss', out_sign='(ss)', method=self._getSlot, async=True) - host.plugins['UPLOAD'].register(u"HTTP Upload", self.getHTTPUploadEntity, self.fileHTTPUpload) + host.bridge.addMethod( + "fileHTTPUpload", + ".plugin", + in_sign="sssbs", + out_sign="", + method=self._fileHTTPUpload, + ) + host.bridge.addMethod( + "fileHTTPUploadGetSlot", + ".plugin", + in_sign="sisss", + out_sign="(ss)", + method=self._getSlot, + async=True, + ) + host.plugins["UPLOAD"].register( + u"HTTP Upload", self.getHTTPUploadEntity, self.fileHTTPUpload + ) def getHandler(self, client): return XEP_0363_handler() @defer.inlineCallbacks def getHTTPUploadEntity(self, upload_jid=None, profile=C.PROF_KEY_NONE): - """Get HTTP upload capable entity + """Get HTTP upload capable entity upload_jid is checked, then its components @param upload_jid(None, jid.JID): entity to check @return(D(jid.JID)): first HTTP upload capable entity @raise exceptions.NotFound: no entity found """ - client = self.host.getClient(profile) - try: - entity = client.http_upload_service - except AttributeError: - found_entities = yield self.host.findFeaturesSet(client, (NS_HTTP_UPLOAD,)) - try: - entity = client.http_upload_service = iter(found_entities).next() - except StopIteration: - entity = client.http_upload_service = None + client = self.host.getClient(profile) + try: + entity = client.http_upload_service + except AttributeError: + found_entities = yield self.host.findFeaturesSet(client, (NS_HTTP_UPLOAD,)) + try: + entity = client.http_upload_service = iter(found_entities).next() + except StopIteration: + entity = client.http_upload_service = None + + if entity is None: + raise failure.Failure(exceptions.NotFound(u"No HTTP upload entity found")) + + defer.returnValue(entity) - if entity is None: - raise failure.Failure(exceptions.NotFound(u'No HTTP upload entity found')) - - defer.returnValue(entity) - - def _fileHTTPUpload(self, filepath, filename='', upload_jid='', ignore_tls_errors=False, profile=C.PROF_KEY_NONE): + def _fileHTTPUpload( + self, + filepath, + filename="", + upload_jid="", + ignore_tls_errors=False, + profile=C.PROF_KEY_NONE, + ): assert os.path.isabs(filepath) and os.path.isfile(filepath) - progress_id_d, dummy = self.fileHTTPUpload(filepath, filename or None, jid.JID(upload_jid) if upload_jid else None, {'ignore_tls_errors': ignore_tls_errors}, profile) + progress_id_d, dummy = self.fileHTTPUpload( + filepath, + filename or None, + jid.JID(upload_jid) if upload_jid else None, + {"ignore_tls_errors": ignore_tls_errors}, + profile, + ) return progress_id_d - def fileHTTPUpload(self, filepath, filename=None, upload_jid=None, options=None, profile=C.PROF_KEY_NONE): + def fileHTTPUpload( + self, + filepath, + filename=None, + upload_jid=None, + options=None, + profile=C.PROF_KEY_NONE, + ): """upload a file through HTTP @param filepath(str): absolute path of the file @@ -141,14 +179,20 @@ """ if options is None: options = {} - ignore_tls_errors = options.get('ignore_tls_errors', False) + ignore_tls_errors = options.get("ignore_tls_errors", False) client = self.host.getClient(profile) filename = filename or os.path.basename(filepath) size = os.path.getsize(filepath) progress_id_d = defer.Deferred() download_d = defer.Deferred() d = self.getSlot(client, filename, size, upload_jid=upload_jid) - d.addCallbacks(self._getSlotCb, self._getSlotEb, (client, progress_id_d, download_d, filepath, size, ignore_tls_errors), None, (client, progress_id_d, download_d)) + d.addCallbacks( + self._getSlotCb, + self._getSlotEb, + (client, progress_id_d, download_d, filepath, size, ignore_tls_errors), + None, + (client, progress_id_d, download_d), + ) return progress_id_d, download_d def _getSlotEb(self, fail, client, progress_id_d, download_d): @@ -157,7 +201,9 @@ progress_id_d.errback(fail) download_d.errback(fail) - def _getSlotCb(self, slot, client, progress_id_d, download_d, path, size, ignore_tls_errors=False): + def _getSlotCb( + self, slot, client, progress_id_d, download_d, path, size, ignore_tls_errors=False + ): """Called when slot is received, try to do the upload @param slot(Slot): slot instance with the get and put urls @@ -169,15 +215,28 @@ @return (tuple """ log.debug(u"Got upload slot: {}".format(slot)) - sat_file = self.host.plugins['FILE'].File(self.host, client, path, size=size, auto_end_signals=False) + sat_file = self.host.plugins["FILE"].File( + self.host, client, path, size=size, auto_end_signals=False + ) progress_id_d.callback(sat_file.uid) file_producer = http_client.FileBodyProducer(sat_file) if ignore_tls_errors: agent = http_client.Agent(reactor, NoCheckContextFactory()) else: agent = http_client.Agent(reactor) - d = agent.request('PUT', slot.put.encode('utf-8'), http_headers.Headers({'User-Agent': [C.APP_NAME.encode('utf-8')]}), file_producer) - d.addCallbacks(self._uploadCb, self._uploadEb, (sat_file, slot, download_d), None, (sat_file, download_d)) + d = agent.request( + "PUT", + slot.put.encode("utf-8"), + http_headers.Headers({"User-Agent": [C.APP_NAME.encode("utf-8")]}), + file_producer, + ) + d.addCallbacks( + self._uploadCb, + self._uploadEb, + (sat_file, slot, download_d), + None, + (sat_file, download_d), + ) return d def _uploadCb(self, dummy, sat_file, slot, download_d): @@ -188,7 +247,7 @@ @param slot(Slot): put/get urls """ log.info(u"HTTP upload finished") - sat_file.progressFinished({'url': slot.get}) + sat_file.progressFinished({"url": slot.get}) download_d.callback(slot.get) def _uploadEb(self, fail, sat_file, download_d): @@ -216,15 +275,17 @@ @param iq_elt(domish.Element): <IQ/> result as specified in XEP-0363 """ try: - slot_elt = iq_elt.elements(NS_HTTP_UPLOAD, 'slot').next() - put_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, 'put').next()) - get_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, 'get').next()) + slot_elt = iq_elt.elements(NS_HTTP_UPLOAD, "slot").next() + put_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, "put").next()) + get_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, "get").next()) except StopIteration: raise exceptions.DataError(u"Incorrect stanza received from server") slot = Slot(put=put_url, get=get_url) return slot - def _getSlot(self, filename, size, content_type, upload_jid, profile_key=C.PROF_KEY_NONE): + def _getSlot( + self, filename, size, content_type, upload_jid, profile_key=C.PROF_KEY_NONE + ): """Get a upload slot This method can be used when uploading is done by the frontend @@ -234,9 +295,11 @@ @param content_type(unicode, None): MIME type of the content empty string or None to guess automatically """ - filename = filename.replace('/', '_') + filename = filename.replace("/", "_") client = self.host.getClient(profile_key) - return self.getSlot(client, filename, size, content_type or None, upload_jid or None) + return self.getSlot( + client, filename, size, content_type or None, upload_jid or None + ) def getSlot(self, client, filename, size, content_type=None, upload_jid=None): """Get a slot (i.e. download/upload links) @@ -261,19 +324,25 @@ upload_jid = client.http_upload_service except AttributeError: d = self.getHTTPUploadEntity(profile=client.profile) - d.addCallback(lambda found_entity: self.getSlot(client, filename, size, content_type, found_entity)) + d.addCallback( + lambda found_entity: self.getSlot( + client, filename, size, content_type, found_entity + ) + ) return d else: if upload_jid is None: - raise failure.Failure(exceptions.NotFound(u'No HTTP upload entity found')) + raise failure.Failure( + exceptions.NotFound(u"No HTTP upload entity found") + ) - iq_elt = client.IQ('get') - iq_elt['to'] = upload_jid.full() - request_elt = iq_elt.addElement((NS_HTTP_UPLOAD, 'request')) - request_elt.addElement('filename', content=filename) - request_elt.addElement('size', content=unicode(size)) + iq_elt = client.IQ("get") + iq_elt["to"] = upload_jid.full() + request_elt = iq_elt.addElement((NS_HTTP_UPLOAD, "request")) + request_elt.addElement("filename", content=filename) + request_elt.addElement("size", content=unicode(size)) if content_type is not None: - request_elt.addElement('content-type', content=content_type) + request_elt.addElement("content-type", content=content_type) d = iq_elt.send() d.addCallback(self._gotSlot, client) @@ -284,8 +353,8 @@ class XEP_0363_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_HTTP_UPLOAD)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []
--- a/sat/stdui/ui_contact_list.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/stdui/ui_contact_list.py Wed Jun 27 20:14:46 2018 +0200 @@ -31,17 +31,34 @@ self.host = host self.__add_id = host.registerCallback(self._addContact, with_data=True) self.__update_id = host.registerCallback(self._updateContact, with_data=True) - self.__confirm_delete_id = host.registerCallback(self._getConfirmRemoveXMLUI, with_data=True) + self.__confirm_delete_id = host.registerCallback( + self._getConfirmRemoveXMLUI, with_data=True + ) - host.importMenu((D_("Contacts"), D_("Add contact")), self._getAddDialogXMLUI, security_limit=2, help_string=D_("Add contact")) - host.importMenu((D_("Contacts"), D_("Update contact")), self._getUpdateDialogXMLUI, security_limit=2, help_string=D_("Update contact")) - host.importMenu((D_("Contacts"), D_("Remove contact")), self._getRemoveDialogXMLUI, security_limit=2, help_string=D_("Remove contact")) + host.importMenu( + (D_("Contacts"), D_("Add contact")), + self._getAddDialogXMLUI, + security_limit=2, + help_string=D_("Add contact"), + ) + host.importMenu( + (D_("Contacts"), D_("Update contact")), + self._getUpdateDialogXMLUI, + security_limit=2, + help_string=D_("Update contact"), + ) + host.importMenu( + (D_("Contacts"), D_("Remove contact")), + self._getRemoveDialogXMLUI, + security_limit=2, + help_string=D_("Remove contact"), + ) # FIXME: a plugin should not be used here, and current profile's jid host would be better than installation wise host - if 'MISC-ACCOUNT' in self.host.plugins: - self.default_host = self.host.plugins['MISC-ACCOUNT'].getNewAccountDomain() + if "MISC-ACCOUNT" in self.host.plugins: + self.default_host = self.host.plugins["MISC-ACCOUNT"].getNewAccountDomain() else: - self.default_host = 'example.net' + self.default_host = "example.net" def getContacts(self, profile): """Return a sorted list of the contacts for that profile @@ -96,11 +113,11 @@ """ elts = [] for key in data: - key_elt = Element('jid') - key_elt.setAttribute('name', key) + key_elt = Element("jid") + key_elt.setAttribute("name", key) for value in data[key]: - value_elt = Element('group') - value_elt.setAttribute('name', value) + value_elt = Element("group") + value_elt.setAttribute("name", value) key_elt.childNodes.append(value_elt) elts.append(key_elt) return elts @@ -116,44 +133,50 @@ @param profile: %(doc_profile)s @return dict """ - form_ui = xml_tools.XMLUI("form", title=options['title'], submit_id=options['id']) - if 'message' in data: - form_ui.addText(data['message']) - form_ui.addDivider('dash') + form_ui = xml_tools.XMLUI("form", title=options["title"], submit_id=options["id"]) + if "message" in data: + form_ui.addText(data["message"]) + form_ui.addDivider("dash") - form_ui.addText(options['contact_text']) - if options['id'] == self.__add_id: - contact = data.get(xml_tools.formEscape('contact_jid'), '@%s' % self.default_host) - form_ui.addString('contact_jid', value=contact) - elif options['id'] == self.__update_id: + form_ui.addText(options["contact_text"]) + if options["id"] == self.__add_id: + contact = data.get( + xml_tools.formEscape("contact_jid"), "@%s" % self.default_host + ) + form_ui.addString("contact_jid", value=contact) + elif options["id"] == self.__update_id: contacts = self.getContacts(profile) - list_ = form_ui.addList('contact_jid', options=contacts, selected=contacts[0]) + list_ = form_ui.addList("contact_jid", options=contacts, selected=contacts[0]) elts = self._data2elts(self.getGroupsOfAllContacts(profile)) - list_.setInternalCallback('groups_of_contact', fields=['contact_jid', 'groups_list'], data_elts=elts) + list_.setInternalCallback( + "groups_of_contact", fields=["contact_jid", "groups_list"], data_elts=elts + ) - form_ui.addDivider('blank') + form_ui.addDivider("blank") form_ui.addText(_("Select in which groups your contact is:")) selected_groups = [] - if 'selected_groups' in data: - selected_groups = data['selected_groups'] - elif options['id'] == self.__update_id: + if "selected_groups" in data: + selected_groups = data["selected_groups"] + elif options["id"] == self.__update_id: try: selected_groups = self.getGroupsOfContact(contacts[0], profile) except IndexError: pass groups = self.getGroups(selected_groups, profile) - form_ui.addList('groups_list', options=groups, selected=selected_groups, styles=['multi']) + form_ui.addList( + "groups_list", options=groups, selected=selected_groups, styles=["multi"] + ) - adv_list = form_ui.changeContainer("advanced_list", columns=3, selectable='no') + adv_list = form_ui.changeContainer("advanced_list", columns=3, selectable="no") form_ui.addLabel(D_("Add group")) form_ui.addString("add_group") - button = form_ui.addButton('', value=D_('Add')) - button.setInternalCallback('move', fields=['add_group', 'groups_list']) + button = form_ui.addButton("", value=D_("Add")) + button.setInternalCallback("move", fields=["add_group", "groups_list"]) adv_list.end() - form_ui.addDivider('blank') - return {'xmlui': form_ui.toXml()} + form_ui.addDivider("blank") + return {"xmlui": form_ui.toXml()} def _getAddDialogXMLUI(self, data, profile): """Get the dialog for adding contact @@ -162,10 +185,11 @@ @param profile: %(doc_profile)s @return dict """ - options = {'id': self.__add_id, - 'title': D_('Add contact'), - 'contact_text': D_("New contact identifier (JID):"), - } + options = { + "id": self.__add_id, + "title": D_("Add contact"), + "contact_text": D_("New contact identifier (JID):"), + } return self.getDialogXMLUI(options, {}, profile) def _getUpdateDialogXMLUI(self, data, profile): @@ -176,14 +200,15 @@ @return dict """ if not self.getContacts(profile): - _dialog = xml_tools.XMLUI('popup', title=D_('Nothing to update')) - _dialog.addText(_('Your contact list is empty.')) - return {'xmlui': _dialog.toXml()} + _dialog = xml_tools.XMLUI("popup", title=D_("Nothing to update")) + _dialog.addText(_("Your contact list is empty.")) + return {"xmlui": _dialog.toXml()} - options = {'id': self.__update_id, - 'title': D_('Update contact'), - 'contact_text': D_("Which contact do you want to update?"), - } + options = { + "id": self.__update_id, + "title": D_("Update contact"), + "contact_text": D_("Which contact do you want to update?"), + } return self.getDialogXMLUI(options, {}, profile) def _getRemoveDialogXMLUI(self, data, profile): @@ -194,13 +219,17 @@ @return dict """ if not self.getContacts(profile): - _dialog = xml_tools.XMLUI('popup', title=D_('Nothing to delete')) - _dialog.addText(_('Your contact list is empty.')) - return {'xmlui': _dialog.toXml()} + _dialog = xml_tools.XMLUI("popup", title=D_("Nothing to delete")) + _dialog.addText(_("Your contact list is empty.")) + return {"xmlui": _dialog.toXml()} - form_ui = xml_tools.XMLUI("form", title=D_('Who do you want to remove from your contacts?'), submit_id=self.__confirm_delete_id) - form_ui.addList('contact_jid', options=self.getContacts(profile)) - return {'xmlui': form_ui.toXml()} + form_ui = xml_tools.XMLUI( + "form", + title=D_("Who do you want to remove from your contacts?"), + submit_id=self.__confirm_delete_id, + ) + form_ui.addList("contact_jid", options=self.getContacts(profile)) + return {"xmlui": form_ui.toXml()} def _getConfirmRemoveXMLUI(self, data, profile): """Get the confirmation dialog for removing contact @@ -209,17 +238,21 @@ @param profile: %(doc_profile)s @return dict """ - if C.bool(data.get('cancelled', 'false')): + if C.bool(data.get("cancelled", "false")): return {} - contact = data[xml_tools.formEscape('contact_jid')] + contact = data[xml_tools.formEscape("contact_jid")] + def delete_cb(data, profile): - if not C.bool(data.get('cancelled', 'false')): + if not C.bool(data.get("cancelled", "false")): self._deleteContact(jid.JID(contact), profile) return {} + delete_id = self.host.registerCallback(delete_cb, with_data=True, one_shot=True) form_ui = xml_tools.XMLUI("form", title=D_("Delete contact"), submit_id=delete_id) - form_ui.addText(D_("Are you sure you want to remove %s from your contact list?") % contact) - return {'xmlui': form_ui.toXml()} + form_ui.addText( + D_("Are you sure you want to remove %s from your contact list?") % contact + ) + return {"xmlui": form_ui.toXml()} def _addContact(self, data, profile): """Add the selected contact @@ -228,18 +261,22 @@ @param profile: %(doc_profile)s @return dict """ - if C.bool(data.get('cancelled', 'false')): + if C.bool(data.get("cancelled", "false")): return {} - contact_jid_s = data[xml_tools.formEscape('contact_jid')] + contact_jid_s = data[xml_tools.formEscape("contact_jid")] try: contact_jid = jid.JID(contact_jid_s) except (RuntimeError, jid.InvalidFormat, AttributeError): # TODO: replace '\t' by a constant (see tools.xmlui.XMLUI.onFormSubmitted) - data['selected_groups'] = data[xml_tools.formEscape('groups_list')].split('\t') - options = {'id': self.__add_id, - 'title': D_('Add contact'), - 'contact_text': D_('Please enter a valid JID (like "contact@%s"):') % self.default_host, - } + data["selected_groups"] = data[xml_tools.formEscape("groups_list")].split( + "\t" + ) + options = { + "id": self.__add_id, + "title": D_("Add contact"), + "contact_text": D_('Please enter a valid JID (like "contact@%s"):') + % self.default_host, + } return self.getDialogXMLUI(options, data, profile) self.host.addContact(contact_jid, profile_key=profile) return self._updateContact(data, profile) # after adding, updating @@ -251,12 +288,12 @@ @param profile: %(doc_profile)s @return dict """ - if C.bool(data.get('cancelled', 'false')): + if C.bool(data.get("cancelled", "false")): return {} - contact_jid = jid.JID(data[xml_tools.formEscape('contact_jid')]) + contact_jid = jid.JID(data[xml_tools.formEscape("contact_jid")]) # TODO: replace '\t' by a constant (see tools.xmlui.XMLUI.onFormSubmitted) - groups = data[xml_tools.formEscape('groups_list')].split('\t') - self.host.updateContact(contact_jid, name='', groups=groups, profile_key=profile) + groups = data[xml_tools.formEscape("groups_list")].split("\t") + self.host.updateContact(contact_jid, name="", groups=groups, profile_key=profile) return {} def _deleteContact(self, contact_jid, profile):
--- a/sat/stdui/ui_profile_manager.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/stdui/ui_profile_manager.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core.i18n import D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from sat.tools import xml_tools @@ -35,9 +36,15 @@ self.host = host self.profile_ciphers = {} self._sessions = ProfileSessions() - host.registerCallback(self._authenticateProfile, force_id=C.AUTHENTICATE_PROFILE_ID, with_data=True) - host.registerCallback(self._changeXMPPPassword, force_id=C.CHANGE_XMPP_PASSWD_ID, with_data=True) - self.__new_xmpp_passwd_id = host.registerCallback(self._changeXMPPPasswordCb, with_data=True) + host.registerCallback( + self._authenticateProfile, force_id=C.AUTHENTICATE_PROFILE_ID, with_data=True + ) + host.registerCallback( + self._changeXMPPPassword, force_id=C.CHANGE_XMPP_PASSWD_ID, with_data=True + ) + self.__new_xmpp_passwd_id = host.registerCallback( + self._changeXMPPPasswordCb, with_data=True + ) def _startSessionEb(self, fail, first, profile): """Errback method for startSession during profile authentication @@ -49,66 +56,93 @@ """ if first: # first call, we ask for the password - form_ui = xml_tools.XMLUI("form", title=D_('Profile password for {}').format(profile), submit_id='') - form_ui.addPassword('profile_password', value='') + form_ui = xml_tools.XMLUI( + "form", title=D_("Profile password for {}").format(profile), submit_id="" + ) + form_ui.addPassword("profile_password", value="") d = xml_tools.deferredUI(self.host, form_ui, chained=True) d.addCallback(self._authenticateProfile, profile) - return {'xmlui': form_ui.toXml()} + return {"xmlui": form_ui.toXml()} assert profile is None if fail.check(exceptions.PasswordError): - dialog = xml_tools.XMLUI('popup', title=D_('Connection error')) + dialog = xml_tools.XMLUI("popup", title=D_("Connection error")) dialog.addText(D_("The provided profile password doesn't match.")) else: log.error(u"Unexpected exceptions: {}".format(fail)) - dialog = xml_tools.XMLUI('popup', title=D_('Internal error')) + dialog = xml_tools.XMLUI("popup", title=D_("Internal error")) dialog.addText(D_(u"Internal error: {}".format(fail))) - return {'xmlui': dialog.toXml(), 'validated': C.BOOL_FALSE} + return {"xmlui": dialog.toXml(), "validated": C.BOOL_FALSE} def _authenticateProfile(self, data, profile): - if C.bool(data.get('cancelled', 'false')): + if C.bool(data.get("cancelled", "false")): return {} if self.host.memory.isSessionStarted(profile): - return {'validated': C.BOOL_TRUE} + return {"validated": C.BOOL_TRUE} try: - password = data[xml_tools.formEscape('profile_password')] + password = data[xml_tools.formEscape("profile_password")] except KeyError: # first request, we try empty password - password = '' + password = "" first = True eb_profile = profile else: first = False eb_profile = None d = self.host.memory.startSession(password, profile) - d.addCallback(lambda dummy: {'validated': C.BOOL_TRUE}) + d.addCallback(lambda dummy: {"validated": C.BOOL_TRUE}) d.addErrback(self._startSessionEb, first, eb_profile) return d def _changeXMPPPassword(self, data, profile): session_data = self._sessions.profileGetUnique(profile) if not session_data: - server = self.host.memory.getParamA(C.FORCE_SERVER_PARAM, "Connection", profile_key=profile) + server = self.host.memory.getParamA( + C.FORCE_SERVER_PARAM, "Connection", profile_key=profile + ) if not server: - server = jid.parse(self.host.memory.getParamA('JabberID', "Connection", profile_key=profile))[1] - session_id, session_data = self._sessions.newSession({'count': 0, 'server': server}, profile=profile) - if session_data['count'] > 2: # 3 attempts with a new password after the initial try + server = jid.parse( + self.host.memory.getParamA( + "JabberID", "Connection", profile_key=profile + ) + )[1] + session_id, session_data = self._sessions.newSession( + {"count": 0, "server": server}, profile=profile + ) + if ( + session_data["count"] > 2 + ): # 3 attempts with a new password after the initial try self._sessions.profileDelUnique(profile) - _dialog = xml_tools.XMLUI('popup', title=D_('Connection error')) - _dialog.addText(D_("Can't connect to %s. Please check your connection details.") % session_data['server']) - return {'xmlui': _dialog.toXml()} - session_data['count'] += 1 - counter = ' (%d)' % session_data['count'] if session_data['count'] > 1 else '' - title = D_('XMPP password for %(profile)s%(counter)s') % {'profile': profile, 'counter': counter} - form_ui = xml_tools.XMLUI("form", title=title, submit_id=self.__new_xmpp_passwd_id) - form_ui.addText(D_("Can't connect to %s. Please check your connection details or try with another password.") % session_data['server']) - form_ui.addPassword('xmpp_password', value='') - return {'xmlui': form_ui.toXml()} + _dialog = xml_tools.XMLUI("popup", title=D_("Connection error")) + _dialog.addText( + D_("Can't connect to %s. Please check your connection details.") + % session_data["server"] + ) + return {"xmlui": _dialog.toXml()} + session_data["count"] += 1 + counter = " (%d)" % session_data["count"] if session_data["count"] > 1 else "" + title = D_("XMPP password for %(profile)s%(counter)s") % { + "profile": profile, + "counter": counter, + } + form_ui = xml_tools.XMLUI( + "form", title=title, submit_id=self.__new_xmpp_passwd_id + ) + form_ui.addText( + D_( + "Can't connect to %s. Please check your connection details or try with another password." + ) + % session_data["server"] + ) + form_ui.addPassword("xmpp_password", value="") + return {"xmlui": form_ui.toXml()} def _changeXMPPPasswordCb(self, data, profile): - xmpp_password = data[xml_tools.formEscape('xmpp_password')] - d = self.host.memory.setParam("Password", xmpp_password, "Connection", profile_key=profile) + xmpp_password = data[xml_tools.formEscape("xmpp_password")] + d = self.host.memory.setParam( + "Password", xmpp_password, "Connection", profile_key=profile + ) d.addCallback(lambda dummy: self.host.connect(profile)) d.addCallback(lambda dummy: {}) d.addErrback(lambda dummy: self._changeXMPPPassword({}, profile))
--- a/sat/test/constants.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/test/constants.py Wed Jun 27 20:14:46 2018 +0200 @@ -23,10 +23,22 @@ class Const(object): - PROF_KEY_NONE = '@NONE@' + PROF_KEY_NONE = "@NONE@" - PROFILE = ['test_profile', 'test_profile2', 'test_profile3', 'test_profile4', 'test_profile5'] - JID_STR = [u"test@example.org/SàT", u"sender@example.net/house", u"sender@example.net/work", u"sender@server.net/res", u"xxx@server.net/res"] + PROFILE = [ + "test_profile", + "test_profile2", + "test_profile3", + "test_profile4", + "test_profile5", + ] + JID_STR = [ + u"test@example.org/SàT", + u"sender@example.net/house", + u"sender@example.net/work", + u"sender@server.net/res", + u"xxx@server.net/res", + ] JID = [jid.JID(jid_s) for jid_s in JID_STR] PROFILE_DICT = {}
--- a/sat/test/helpers_plugins.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/test/helpers_plugins.py Wed Jun 27 20:14:46 2018 +0200 @@ -57,8 +57,12 @@ if other_profile == profile: continue try: - other_room = self.plugin_parent.clients[other_profile].joined_rooms[room_jid] - roster.setdefault(other_room.nick, User(other_room.nick, C.PROFILE_DICT[other_profile])) + other_room = self.plugin_parent.clients[other_profile].joined_rooms[ + room_jid + ] + roster.setdefault( + other_room.nick, User(other_room.nick, C.PROFILE_DICT[other_profile]) + ) for other_nick in other_room.roster: roster.setdefault(other_nick, other_room.roster[other_nick]) except (AttributeError, KeyError): @@ -80,8 +84,12 @@ if other_profile == profile: continue try: - other_room = self.plugin_parent.clients[other_profile].joined_rooms[room_jid] - other_room.roster.setdefault(room.nick, User(room.nick, C.PROFILE_DICT[profile])) + other_room = self.plugin_parent.clients[other_profile].joined_rooms[ + room_jid + ] + other_room.roster.setdefault( + room.nick, User(room.nick, C.PROFILE_DICT[profile]) + ) except (AttributeError, KeyError): pass @@ -101,7 +109,9 @@ if other_profile == profile: continue try: - other_room = self.plugin_parent.clients[other_profile].joined_rooms[roomJID] + other_room = self.plugin_parent.clients[other_profile].joined_rooms[ + roomJID + ] del other_room.roster[room.nick] except (AttributeError, KeyError): pass @@ -110,14 +120,13 @@ class FakeXEP_0045(plugin_xep_0045.XEP_0045): - def __init__(self, host): self.host = host self.clients = {} for profile in C.PROFILE: self.clients[profile] = FakeMUCClient(self) - def join(self, room_jid, nick, options={}, profile_key='@DEFAULT@'): + def join(self, room_jid, nick, options={}, profile_key="@DEFAULT@"): """ @param roomJID: the room JID @param nick: nick to be used in the room @@ -140,7 +149,7 @@ self.join(muc_jid, nick, profile_key=profile) return self.getNick(muc_index, user_index) - def leave(self, room_jid, profile_key='@DEFAULT@'): + def leave(self, room_jid, profile_key="@DEFAULT@"): """ @param roomJID: the room JID @param profile_key: the profile of the user leaving the room @@ -174,7 +183,7 @@ try: return self.getRoomNick(C.MUC[muc_index], C.PROFILE[user_index]) except (KeyError, AttributeError): - return '' + return "" def getNickOfUser(self, muc_index, user_index, profile_index, secure=True): try: @@ -185,11 +194,10 @@ class FakeXEP_0249(object): - def __init__(self, host): self.host = host - def invite(self, target, room, options={}, profile_key='@DEFAULT@'): + def invite(self, target, room, options={}, profile_key="@DEFAULT@"): """ Invite a user to a room. To accept the invitation from a test, just call FakeXEP_0045.joinRoom (no need to have a dedicated method). @@ -202,15 +210,13 @@ class FakeSatPubSubClient(object): - def __init__(self, host, parent_plugin): self.host = host self.parent_plugin = parent_plugin self.__items = OrderedDict() self.__rsm_responses = {} - def createNode(self, service, nodeIdentifier=None, options=None, - sender=None): + def createNode(self, service, nodeIdentifier=None, options=None, sender=None): return defer.succeed(None) def deleteNode(self, service, nodeIdentifier, sender=None): @@ -220,12 +226,17 @@ pass return defer.succeed(None) - def subscribe(self, service, nodeIdentifier, subscriber, - options=None, sender=None): + def subscribe(self, service, nodeIdentifier, subscriber, options=None, sender=None): return defer.succeed(None) - def unsubscribe(self, service, nodeIdentifier, subscriber, - subscriptionIdentifier=None, sender=None): + def unsubscribe( + self, + service, + nodeIdentifier, + subscriber, + subscriptionIdentifier=None, + sender=None, + ): return defer.succeed(None) def publish(self, service, nodeIdentifier, items=None, sender=None): @@ -234,7 +245,7 @@ def replace(item_obj): index = 0 for current in node: - if current['id'] == item_obj['id']: + if current["id"] == item_obj["id"]: node[index] = item_obj return True index += 1 @@ -246,22 +257,30 @@ node.append(item_obj) return defer.succeed(None) - def items(self, service, nodeIdentifier, maxItems=None, itemIdentifiers=None, - subscriptionIdentifier=None, sender=None, ext_data=None): + def items( + self, + service, + nodeIdentifier, + maxItems=None, + itemIdentifiers=None, + subscriptionIdentifier=None, + sender=None, + ext_data=None, + ): try: items = self.__items[nodeIdentifier] except KeyError: items = [] if ext_data: - assert('id' in ext_data) - if 'rsm' in ext_data: - args = (0, items[0]['id'], items[-1]['id']) if items else () - self.__rsm_responses[ext_data['id']] = RSMResponse(len(items), *args) + assert "id" in ext_data + if "rsm" in ext_data: + args = (0, items[0]["id"], items[-1]["id"]) if items else () + self.__rsm_responses[ext_data["id"]] = RSMResponse(len(items), *args) return defer.succeed(items) def retractItems(self, service, nodeIdentifier, itemIdentifiers, sender=None): node = self.__items[nodeIdentifier] - for item in [item for item in node if item['id'] in itemIdentifiers]: + for item in [item for item in node if item["id"] in itemIdentifiers]: node.remove(item) return defer.succeed(None)
--- a/sat/test/test_helpers_plugins.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/test/test_helpers_plugins.py Wed Jun 27 20:14:46 2018 +0200 @@ -25,93 +25,100 @@ class FakeXEP_0045Test(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() self.plugin = helpers_plugins.FakeXEP_0045(self.host) def test_joinRoom(self): self.plugin.joinRoom(0, 0) - self.assertEqual('test', self.plugin.getNick(0, 0)) + self.assertEqual("test", self.plugin.getNick(0, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 0)) - self.assertEqual('', self.plugin.getNick(0, 1)) + self.assertEqual("", self.plugin.getNick(0, 1)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 1)) self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 1)) self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 1)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 1)) - self.assertEqual('', self.plugin.getNick(0, 2)) + self.assertEqual("", self.plugin.getNick(0, 2)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 2)) self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 2)) self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 2)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 2)) - self.assertEqual('', self.plugin.getNick(0, 3)) + self.assertEqual("", self.plugin.getNick(0, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 3)) self.plugin.joinRoom(0, 1) - self.assertEqual('test', self.plugin.getNick(0, 0)) + self.assertEqual("test", self.plugin.getNick(0, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 0)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 0)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 0)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 0)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 0)) - self.assertEqual('sender', self.plugin.getNick(0, 1)) - self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 1)) + self.assertEqual("sender", self.plugin.getNick(0, 1)) + self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 1)) self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 1)) self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 1)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 1)) - self.assertEqual('', self.plugin.getNick(0, 2)) + self.assertEqual("", self.plugin.getNick(0, 2)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 2)) self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 2)) self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 2)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 2)) - self.assertEqual('', self.plugin.getNick(0, 3)) + self.assertEqual("", self.plugin.getNick(0, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 3)) self.plugin.joinRoom(0, 2) - self.assertEqual('test', self.plugin.getNick(0, 0)) + self.assertEqual("test", self.plugin.getNick(0, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 0)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 0)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 0)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 0)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 0)) - self.assertEqual('sender', self.plugin.getNick(0, 1)) - self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 1)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 1)) # Const.JID[2] is in the roster for Const.PROFILE[1] - self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 1)) + self.assertEqual("sender", self.plugin.getNick(0, 1)) + self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 1)) + self.assertEqual( + "sender", self.plugin.getNickOfUser(0, 1, 1) + ) # Const.JID[2] is in the roster for Const.PROFILE[1] + self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 1)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 1)) - self.assertEqual('sender', self.plugin.getNick(0, 2)) - self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 2)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 2)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 2)) # Const.JID[1] is in the roster for Const.PROFILE[2] + self.assertEqual("sender", self.plugin.getNick(0, 2)) + self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 2)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 2)) + self.assertEqual( + "sender", self.plugin.getNickOfUser(0, 2, 2) + ) # Const.JID[1] is in the roster for Const.PROFILE[2] self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 2)) - self.assertEqual('', self.plugin.getNick(0, 3)) + self.assertEqual("", self.plugin.getNick(0, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 3)) self.plugin.joinRoom(0, 3) - self.assertEqual('test', self.plugin.getNick(0, 0)) + self.assertEqual("test", self.plugin.getNick(0, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 0)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 0)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 0)) - self.assertEqual('sender_', self.plugin.getNickOfUser(0, 3, 0)) - self.assertEqual('sender', self.plugin.getNick(0, 1)) - self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 1)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 1)) # Const.JID[2] is in the roster for Const.PROFILE[1] - self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 1)) - self.assertEqual('sender_', self.plugin.getNickOfUser(0, 3, 1)) - self.assertEqual('sender', self.plugin.getNick(0, 2)) - self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 2)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 2)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 2)) # Const.JID[1] is in the roster for Const.PROFILE[2] - self.assertEqual('sender_', self.plugin.getNickOfUser(0, 3, 2)) - self.assertEqual('sender_', self.plugin.getNick(0, 3)) - self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 3)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 3)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 3)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 0)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 0)) + self.assertEqual("sender_", self.plugin.getNickOfUser(0, 3, 0)) + self.assertEqual("sender", self.plugin.getNick(0, 1)) + self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 1)) + self.assertEqual( + "sender", self.plugin.getNickOfUser(0, 1, 1) + ) # Const.JID[2] is in the roster for Const.PROFILE[1] + self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 1)) + self.assertEqual("sender_", self.plugin.getNickOfUser(0, 3, 1)) + self.assertEqual("sender", self.plugin.getNick(0, 2)) + self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 2)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 2)) + self.assertEqual( + "sender", self.plugin.getNickOfUser(0, 2, 2) + ) # Const.JID[1] is in the roster for Const.PROFILE[2] + self.assertEqual("sender_", self.plugin.getNickOfUser(0, 3, 2)) + self.assertEqual("sender_", self.plugin.getNick(0, 3)) + self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 3)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 3)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 3))
--- a/sat/test/test_memory.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/test/test_memory.py Wed Jun 27 20:14:46 2018 +0200 @@ -26,7 +26,6 @@ class MemoryTest(unittest.TestCase): - def setUp(self): self.host = helpers.FakeSAT() @@ -37,14 +36,19 @@ @param security_level: security level of the parameters @return (str) """ + def getParam(name): return """ <param name="%(param_name)s" label="%(param_label)s" value="true" type="bool" %(security)s/> - """ % {'param_name': name, - 'param_label': _(name), - 'security': '' if security_level is None else ('security="%d"' % security_level) - } - params = '' + """ % { + "param_name": name, + "param_label": _(name), + "security": "" + if security_level is None + else ('security="%d"' % security_level), + } + + params = "" if "1" in param: params += getParam(Const.ENABLE_UNIBOX_PARAM) if "2" in param: @@ -60,9 +64,9 @@ </individual> </params> """ % { - 'category_name': Const.COMPOSITION_KEY, - 'category_label': _(Const.COMPOSITION_KEY), - 'params': params + "category_name": Const.COMPOSITION_KEY, + "category_label": _(Const.COMPOSITION_KEY), + "params": params, } def _paramExists(self, param="1", src=None): @@ -86,7 +90,10 @@ if type_node.nodeName not in ("individual", "general", "params"): continue for cat_node in type_node.childNodes: - if cat_node.nodeName != "category" or cat_node.getAttribute("name") != category: + if ( + cat_node.nodeName != "category" + or cat_node.getAttribute("name") != category + ): continue for param in cat_node.childNodes: if param.nodeName == "param" and param.getAttribute("name") == name: @@ -100,7 +107,11 @@ @param exists (boolean): True to assert the param exists, False to assert it doesn't @param deferred (boolean): True if this method is called from a Deferred callback """ - msg = "Expected parameter not found!\n" if exists else "Unexpected parameter found!\n" + msg = ( + "Expected parameter not found!\n" + if exists + else "Unexpected parameter found!\n" + ) if deferred: # in this stack we can see the line where the error came from, # if limit=5, 6 is not enough you can increase the value @@ -116,21 +127,25 @@ def assertParamExists_async(self, src, param="1"): """@param src: a deferred result from Memory.getParams""" - self.assertParam_generic(param, minidom.parseString(src.encode("utf-8")), True, True) + self.assertParam_generic( + param, minidom.parseString(src.encode("utf-8")), True, True + ) def assertParamNotExists_async(self, src, param="1"): """@param src: a deferred result from Memory.getParams""" - self.assertParam_generic(param, minidom.parseString(src.encode("utf-8")), False, True) + self.assertParam_generic( + param, minidom.parseString(src.encode("utf-8")), False, True + ) - def _getParams(self, security_limit, app='', profile_key='@NONE@'): + def _getParams(self, security_limit, app="", profile_key="@NONE@"): """Get the parameters accessible with the given security limit and application name. @param security_limit (int): the security limit @param app (str): empty string or "libervia" @param profile_key """ - if profile_key == '@NONE@': - profile_key = '@DEFAULT@' + if profile_key == "@NONE@": + profile_key = "@DEFAULT@" return self.host.memory.params.getParams(security_limit, app, profile_key) def test_updateParams(self): @@ -141,14 +156,17 @@ previous = self.host.memory.params.dom.cloneNode(True) # now check if it is really updated and not duplicated self.host.memory.updateParams(self._getParamXML()) - self.assertEqual(previous.toxml().encode("utf-8"), self.host.memory.params.dom.toxml().encode("utf-8")) + self.assertEqual( + previous.toxml().encode("utf-8"), + self.host.memory.params.dom.toxml().encode("utf-8"), + ) self.host.memory.reinit() # check successive updates (without intersection) - self.host.memory.updateParams(self._getParamXML('1')) + self.host.memory.updateParams(self._getParamXML("1")) self.assertParamExists("1") self.assertParamNotExists("2") - self.host.memory.updateParams(self._getParamXML('2')) + self.host.memory.updateParams(self._getParamXML("2")) self.assertParamExists("1") self.assertParamExists("2") @@ -156,19 +174,22 @@ self.host.memory.reinit() # check successive updates (with intersection) - self.host.memory.updateParams(self._getParamXML('1')) + self.host.memory.updateParams(self._getParamXML("1")) self.assertParamExists("1") self.assertParamNotExists("2") - self.host.memory.updateParams(self._getParamXML('12')) + self.host.memory.updateParams(self._getParamXML("12")) self.assertParamExists("1") self.assertParamExists("2") # successive updates with or without intersection should have the same result - self.assertEqual(previous.toxml().encode("utf-8"), self.host.memory.params.dom.toxml().encode("utf-8")) + self.assertEqual( + previous.toxml().encode("utf-8"), + self.host.memory.params.dom.toxml().encode("utf-8"), + ) self.host.memory.reinit() # one update with two params in a new category - self.host.memory.updateParams(self._getParamXML('12')) + self.host.memory.updateParams(self._getParamXML("12")) self.assertParamExists("1") self.assertParamExists("2") @@ -196,7 +217,6 @@ return self._getParams(1).addCallback(self.assertParamExists_async) def test_paramsRegisterApp(self): - def register(xml, security_limit, app): """ @param xml: XML definition of the parameters to be added @@ -271,17 +291,23 @@ self.host.memory.reinit() params = self._getParamXML(security_level=1) self.host.memory.paramsRegisterApp(params, 1, Const.APP_NAME) - self._getParams(1, '').addCallback(self.assertParamExists_async) + self._getParams(1, "").addCallback(self.assertParamExists_async) self._getParams(1, Const.APP_NAME).addCallback(self.assertParamExists_async) - self._getParams(1, 'another_dummy_frontend').addCallback(self.assertParamNotExists_async) + self._getParams(1, "another_dummy_frontend").addCallback( + self.assertParamNotExists_async + ) # the same with several parameters registered at the same time self.host.memory.reinit() - params = self._getParamXML('12', security_level=0) + params = self._getParamXML("12", security_level=0) self.host.memory.paramsRegisterApp(params, 5, Const.APP_NAME) - self._getParams(5, '').addCallback(self.assertParamExists_async) - self._getParams(5, '').addCallback(self.assertParamExists_async, "2") + self._getParams(5, "").addCallback(self.assertParamExists_async) + self._getParams(5, "").addCallback(self.assertParamExists_async, "2") self._getParams(5, Const.APP_NAME).addCallback(self.assertParamExists_async) self._getParams(5, Const.APP_NAME).addCallback(self.assertParamExists_async, "2") - self._getParams(5, 'another_dummy_frontend').addCallback(self.assertParamNotExists_async) - return self._getParams(5, 'another_dummy_frontend').addCallback(self.assertParamNotExists_async, "2") + self._getParams(5, "another_dummy_frontend").addCallback( + self.assertParamNotExists_async + ) + return self._getParams(5, "another_dummy_frontend").addCallback( + self.assertParamNotExists_async, "2" + )
--- a/sat/test/test_memory_crypto.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/test/test_memory_crypto.py Wed Jun 27 20:14:46 2018 +0200 @@ -30,11 +30,10 @@ def getRandomUnicode(len): """Return a random unicode string""" - return u''.join(random.choice(string.letters + u"éáúóâêûôßüöä") for i in xrange(len)) + return u"".join(random.choice(string.letters + u"éáúóâêûôßüöä") for i in xrange(len)) class CryptoTest(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT()
--- a/sat/test/test_plugin_misc_groupblog.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/test/test_plugin_misc_groupblog.py Wed Jun 27 20:14:46 2018 +0200 @@ -31,27 +31,32 @@ from twisted.words.protocols.jabber import jid -NS_PUBSUB = 'http://jabber.org/protocol/pubsub' +NS_PUBSUB = "http://jabber.org/protocol/pubsub" DO_NOT_COUNT_COMMENTS = -1 -SERVICE = u'pubsub.example.com' -PUBLISHER = u'test@example.org' -OTHER_PUBLISHER = u'other@xmpp.net' -NODE_ID = u'urn:xmpp:groupblog:{publisher}'.format(publisher=PUBLISHER) -OTHER_NODE_ID = u'urn:xmpp:groupblog:{publisher}'.format(publisher=OTHER_PUBLISHER) -ITEM_ID_1 = u'c745a688-9b02-11e3-a1a3-c0143dd4fe51' -COMMENT_ID_1 = u'd745a688-9b02-11e3-a1a3-c0143dd4fe52' -COMMENT_ID_2 = u'e745a688-9b02-11e3-a1a3-c0143dd4fe53' +SERVICE = u"pubsub.example.com" +PUBLISHER = u"test@example.org" +OTHER_PUBLISHER = u"other@xmpp.net" +NODE_ID = u"urn:xmpp:groupblog:{publisher}".format(publisher=PUBLISHER) +OTHER_NODE_ID = u"urn:xmpp:groupblog:{publisher}".format(publisher=OTHER_PUBLISHER) +ITEM_ID_1 = u"c745a688-9b02-11e3-a1a3-c0143dd4fe51" +COMMENT_ID_1 = u"d745a688-9b02-11e3-a1a3-c0143dd4fe52" +COMMENT_ID_2 = u"e745a688-9b02-11e3-a1a3-c0143dd4fe53" def COMMENTS_NODE_ID(publisher=PUBLISHER): - return u'urn:xmpp:comments:_{id}__urn:xmpp:groupblog:{publisher}'.format(id=ITEM_ID_1, publisher=publisher) + return u"urn:xmpp:comments:_{id}__urn:xmpp:groupblog:{publisher}".format( + id=ITEM_ID_1, publisher=publisher + ) def COMMENTS_NODE_URL(publisher=PUBLISHER): - return u'xmpp:{service}?node={node}'.format(service=SERVICE, id=ITEM_ID_1, - node=COMMENTS_NODE_ID(publisher).replace(':', '%3A').replace('@', '%40')) + return u"xmpp:{service}?node={node}".format( + service=SERVICE, + id=ITEM_ID_1, + node=COMMENTS_NODE_ID(publisher).replace(":", "%3A").replace("@", "%40"), + ) def ITEM(publisher=PUBLISHER): @@ -68,7 +73,12 @@ </author> </entry> </item> - """.format(ns=NS_PUBSUB, id=ITEM_ID_1, publisher=publisher, comments_node_url=COMMENTS_NODE_URL(publisher)) + """.format( + ns=NS_PUBSUB, + id=ITEM_ID_1, + publisher=publisher, + comments_node_url=COMMENTS_NODE_URL(publisher), + ) def COMMENT(id_=COMMENT_ID_1): @@ -84,35 +94,41 @@ </author> </entry> </item> - """.format(ns=NS_PUBSUB, id=id_, publisher=PUBLISHER) + """.format( + ns=NS_PUBSUB, id=id_, publisher=PUBLISHER + ) def ITEM_DATA(id_=ITEM_ID_1, count=0): - res = {'id': ITEM_ID_1, - 'type': 'main_item', - 'content': 'The Uses of This World', - 'author': PUBLISHER, - 'updated': '1071251243.0', - 'published': '1071251243.0', - 'service': SERVICE, - 'comments': COMMENTS_NODE_URL_1, - 'comments_service': SERVICE, - 'comments_node': COMMENTS_NODE_ID_1} + res = { + "id": ITEM_ID_1, + "type": "main_item", + "content": "The Uses of This World", + "author": PUBLISHER, + "updated": "1071251243.0", + "published": "1071251243.0", + "service": SERVICE, + "comments": COMMENTS_NODE_URL_1, + "comments_service": SERVICE, + "comments_node": COMMENTS_NODE_ID_1, + } if count != DO_NOT_COUNT_COMMENTS: - res.update({'comments_count': unicode(count)}) + res.update({"comments_count": unicode(count)}) return res def COMMENT_DATA(id_=COMMENT_ID_1): - return {'id': id_, - 'type': 'comment', - 'content': 'The Uses of This World', - 'author': PUBLISHER, - 'updated': '1071251243.0', - 'published': '1071251243.0', - 'service': SERVICE, - 'node': COMMENTS_NODE_ID_1, - 'verified_publisher': 'false'} + return { + "id": id_, + "type": "comment", + "content": "The Uses of This World", + "author": PUBLISHER, + "updated": "1071251243.0", + "published": "1071251243.0", + "service": SERVICE, + "node": COMMENTS_NODE_ID_1, + "verified_publisher": "false", + } COMMENTS_NODE_ID_1 = COMMENTS_NODE_ID() @@ -128,19 +144,21 @@ def ITEM_DATA_1(count=0): return ITEM_DATA(count=count) + COMMENT_DATA_1 = COMMENT_DATA() COMMENT_DATA_2 = COMMENT_DATA(COMMENT_ID_2) class XEP_groupblogTest(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() - self.host.plugins['XEP-0060'] = plugin_xep_0060.XEP_0060(self.host) - self.host.plugins['XEP-0163'] = plugin_xep_0163.XEP_0163(self.host) + self.host.plugins["XEP-0060"] = plugin_xep_0060.XEP_0060(self.host) + self.host.plugins["XEP-0163"] = plugin_xep_0163.XEP_0163(self.host) reload(plugin_misc_text_syntaxes) # reload the plugin to avoid conflict error - self.host.plugins['TEXT-SYNTAXES'] = plugin_misc_text_syntaxes.TextSyntaxes(self.host) - self.host.plugins['XEP-0277'] = plugin_xep_0277.XEP_0277(self.host) + self.host.plugins["TEXT-SYNTAXES"] = plugin_misc_text_syntaxes.TextSyntaxes( + self.host + ) + self.host.plugins["XEP-0277"] = plugin_xep_0277.XEP_0277(self.host) self.plugin = plugin_misc_groupblog.GroupBlog(self.host) self.plugin._initialise = self._initialise self.__initialised = False @@ -151,8 +169,10 @@ client = self.host.getClient(profile) if not self.__initialised: client.item_access_pubsub = jid.JID(SERVICE) - xep_0060 = self.host.plugins['XEP-0060'] - client.pubsub_client = helpers_plugins.FakeSatPubSubClient(self.host, xep_0060) + xep_0060 = self.host.plugins["XEP-0060"] + client.pubsub_client = helpers_plugins.FakeSatPubSubClient( + self.host, xep_0060 + ) client.pubsub_client.parent = client self.psclient = client.pubsub_client helpers.FakeSAT.getDiscoItems = self.psclient.service_getDiscoItems @@ -167,19 +187,30 @@ self._initialise(C.PROFILE[0]) d = self.psclient.items(SERVICE, NODE_ID) d.addCallback(lambda items: self.assertEqual(len(items), 0)) - d.addCallback(lambda dummy: self.plugin.sendGroupBlog('PUBLIC', [], 'test', {}, C.PROFILE[0])) + d.addCallback( + lambda dummy: self.plugin.sendGroupBlog( + "PUBLIC", [], "test", {}, C.PROFILE[0] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID)) return d.addCallback(lambda items: self.assertEqual(len(items), 1)) def test_deleteGroupBlog(self): pub_data = (SERVICE, NODE_ID, ITEM_ID_1) - self.host.bridge.expectCall('personalEvent', C.JID_STR[0], - "MICROBLOG_DELETE", - {'type': 'main_item', 'id': ITEM_ID_1}, - C.PROFILE[0]) + self.host.bridge.expectCall( + "personalEvent", + C.JID_STR[0], + "MICROBLOG_DELETE", + {"type": "main_item", "id": ITEM_ID_1}, + C.PROFILE[0], + ) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.plugin.deleteGroupBlog(pub_data, COMMENTS_NODE_URL_1, profile_key=C.PROFILE[0])) + d.addCallback( + lambda dummy: self.plugin.deleteGroupBlog( + pub_data, COMMENTS_NODE_URL_1, profile_key=C.PROFILE[0] + ) + ) return d.addCallback(self.assertEqual, None) def test_updateGroupBlog(self): @@ -188,71 +219,155 @@ self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.plugin.updateGroupBlog(pub_data, COMMENTS_NODE_URL_1, new_text, {}, profile_key=C.PROFILE[0])) + d.addCallback( + lambda dummy: self.plugin.updateGroupBlog( + pub_data, COMMENTS_NODE_URL_1, new_text, {}, profile_key=C.PROFILE[0] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID)) - return d.addCallback(lambda items: self.assertEqual(''.join(items[0].entry.title.children), new_text)) + return d.addCallback( + lambda items: self.assertEqual( + "".join(items[0].entry.title.children), new_text + ) + ) def test_sendGroupBlogComment(self): self._initialise(C.PROFILE[0]) d = self.psclient.items(SERVICE, NODE_ID) d.addCallback(lambda items: self.assertEqual(len(items), 0)) - d.addCallback(lambda dummy: self.plugin.sendGroupBlogComment(COMMENTS_NODE_URL_1, 'test', {}, profile_key=C.PROFILE[0])) + d.addCallback( + lambda dummy: self.plugin.sendGroupBlogComment( + COMMENTS_NODE_URL_1, "test", {}, profile_key=C.PROFILE[0] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_1)) return d.addCallback(lambda items: self.assertEqual(len(items), 1)) def test_getGroupBlogs(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.plugin.getGroupBlogs(PUBLISHER, profile_key=C.PROFILE[0])) - result = ([ITEM_DATA_1()], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1}) + d.addCallback( + lambda dummy: self.plugin.getGroupBlogs(PUBLISHER, profile_key=C.PROFILE[0]) + ) + result = ( + [ITEM_DATA_1()], + {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1}, + ) return d.addCallback(self.assertEqual, result) def test_getGroupBlogsNoCount(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.plugin.getGroupBlogs(PUBLISHER, count_comments=False, profile_key=C.PROFILE[0])) - result = ([ITEM_DATA_1(DO_NOT_COUNT_COMMENTS)], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1}) + d.addCallback( + lambda dummy: self.plugin.getGroupBlogs( + PUBLISHER, count_comments=False, profile_key=C.PROFILE[0] + ) + ) + result = ( + [ITEM_DATA_1(DO_NOT_COUNT_COMMENTS)], + {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1}, + ) return d.addCallback(self.assertEqual, result) def test_getGroupBlogsWithIDs(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.plugin.getGroupBlogs(PUBLISHER, [ITEM_ID_1], profile_key=C.PROFILE[0])) - result = ([ITEM_DATA_1()], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1}) + d.addCallback( + lambda dummy: self.plugin.getGroupBlogs( + PUBLISHER, [ITEM_ID_1], profile_key=C.PROFILE[0] + ) + ) + result = ( + [ITEM_DATA_1()], + {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1}, + ) return d.addCallback(self.assertEqual, result) def test_getGroupBlogsWithRSM(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.plugin.getGroupBlogs(PUBLISHER, rsm_data={'max_': 1}, profile_key=C.PROFILE[0])) - result = ([ITEM_DATA_1()], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1}) + d.addCallback( + lambda dummy: self.plugin.getGroupBlogs( + PUBLISHER, rsm_data={"max_": 1}, profile_key=C.PROFILE[0] + ) + ) + result = ( + [ITEM_DATA_1()], + {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1}, + ) return d.addCallback(self.assertEqual, result) def test_getGroupBlogsWithComments(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1])) - d.addCallback(lambda dummy: self.plugin.getGroupBlogsWithComments(PUBLISHER, [], profile_key=C.PROFILE[0])) - result = ([(ITEM_DATA_1(1), ([COMMENT_DATA_1], - {'count': '1', 'index': '0', 'first': COMMENT_ID_1, 'last': COMMENT_ID_1}))], - {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1}) + d.addCallback( + lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1]) + ) + d.addCallback( + lambda dummy: self.plugin.getGroupBlogsWithComments( + PUBLISHER, [], profile_key=C.PROFILE[0] + ) + ) + result = ( + [ + ( + ITEM_DATA_1(1), + ( + [COMMENT_DATA_1], + { + "count": "1", + "index": "0", + "first": COMMENT_ID_1, + "last": COMMENT_ID_1, + }, + ), + ) + ], + {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1}, + ) return d.addCallback(self.assertEqual, result) def test_getGroupBlogsWithComments2(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2])) - d.addCallback(lambda dummy: self.plugin.getGroupBlogsWithComments(PUBLISHER, [], profile_key=C.PROFILE[0])) - result = ([(ITEM_DATA_1(2), ([COMMENT_DATA_1, COMMENT_DATA_2], - {'count': '2', 'index': '0', 'first': COMMENT_ID_1, 'last': COMMENT_ID_2}))], - {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1}) + d.addCallback( + lambda dummy: self.psclient.publish( + SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2] + ) + ) + d.addCallback( + lambda dummy: self.plugin.getGroupBlogsWithComments( + PUBLISHER, [], profile_key=C.PROFILE[0] + ) + ) + result = ( + [ + ( + ITEM_DATA_1(2), + ( + [COMMENT_DATA_1, COMMENT_DATA_2], + { + "count": "2", + "index": "0", + "first": COMMENT_ID_1, + "last": COMMENT_ID_2, + }, + ), + ) + ], + {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1}, + ) return d.addCallback(self.assertEqual, result) def test_getGroupBlogsAtom(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.plugin.getGroupBlogsAtom(PUBLISHER, {'max_': 1}, profile_key=C.PROFILE[0])) + d.addCallback( + lambda dummy: self.plugin.getGroupBlogsAtom( + PUBLISHER, {"max_": 1}, profile_key=C.PROFILE[0] + ) + ) def cb(atom): self.assertIsInstance(atom, unicode) @@ -263,11 +378,22 @@ def test_getMassiveGroupBlogs(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.plugin.getMassiveGroupBlogs('JID', [jid.JID(PUBLISHER)], {'max_': 1}, profile_key=C.PROFILE[0])) - result = {PUBLISHER: ([ITEM_DATA_1()], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1})} + d.addCallback( + lambda dummy: self.plugin.getMassiveGroupBlogs( + "JID", [jid.JID(PUBLISHER)], {"max_": 1}, profile_key=C.PROFILE[0] + ) + ) + result = { + PUBLISHER: ( + [ITEM_DATA_1()], + {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1}, + ) + } def clean(res): - del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE] + del self.host.plugins["XEP-0060"].node_cache[ + C.PROFILE[0] + "@found@" + SERVICE + ] return res d.addCallback(clean) @@ -276,12 +402,27 @@ def test_getMassiveGroupBlogsWithComments(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2])) - d.addCallback(lambda dummy: self.plugin.getMassiveGroupBlogs('JID', [jid.JID(PUBLISHER)], {'max_': 1}, profile_key=C.PROFILE[0])) - result = {PUBLISHER: ([ITEM_DATA_1(2)], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1})} + d.addCallback( + lambda dummy: self.psclient.publish( + SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2] + ) + ) + d.addCallback( + lambda dummy: self.plugin.getMassiveGroupBlogs( + "JID", [jid.JID(PUBLISHER)], {"max_": 1}, profile_key=C.PROFILE[0] + ) + ) + result = { + PUBLISHER: ( + [ITEM_DATA_1(2)], + {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1}, + ) + } def clean(res): - del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE] + del self.host.plugins["XEP-0060"].node_cache[ + C.PROFILE[0] + "@found@" + SERVICE + ] return res d.addCallback(clean) @@ -290,9 +431,18 @@ def test_getGroupBlogComments(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1])) - d.addCallback(lambda dummy: self.plugin.getGroupBlogComments(SERVICE, COMMENTS_NODE_ID_1, {'max_': 1}, profile_key=C.PROFILE[0])) - result = ([COMMENT_DATA_1], {'count': '1', 'index': '0', 'first': COMMENT_ID_1, 'last': COMMENT_ID_1}) + d.addCallback( + lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1]) + ) + d.addCallback( + lambda dummy: self.plugin.getGroupBlogComments( + SERVICE, COMMENTS_NODE_ID_1, {"max_": 1}, profile_key=C.PROFILE[0] + ) + ) + result = ( + [COMMENT_DATA_1], + {"count": "1", "index": "0", "first": COMMENT_ID_1, "last": COMMENT_ID_1}, + ) return d.addCallback(self.assertEqual, result) def test_subscribeGroupBlog(self): @@ -302,11 +452,17 @@ def test_massiveSubscribeGroupBlogs(self): self._initialise(C.PROFILE[0]) - d = self.plugin.massiveSubscribeGroupBlogs('JID', [jid.JID(PUBLISHER)], profile_key=C.PROFILE[0]) + d = self.plugin.massiveSubscribeGroupBlogs( + "JID", [jid.JID(PUBLISHER)], profile_key=C.PROFILE[0] + ) def clean(res): - del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE] - del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@subscriptions@' + SERVICE] + del self.host.plugins["XEP-0060"].node_cache[ + C.PROFILE[0] + "@found@" + SERVICE + ] + del self.host.plugins["XEP-0060"].node_cache[ + C.PROFILE[0] + "@subscriptions@" + SERVICE + ] return res d.addCallback(clean) @@ -317,21 +473,33 @@ self._initialise(C.PROFILE[0]) self.host.profiles[C.PROFILE[0]].roster.addItem(jid.JID(OTHER_PUBLISHER)) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2])) + d.addCallback( + lambda dummy: self.psclient.publish( + SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID)) d.addCallback(lambda items: self.assertEqual(len(items), 1)) d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_1)) d.addCallback(lambda items: self.assertEqual(len(items), 2)) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2])) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2])) + d.addCallback( + lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2]) + ) + d.addCallback( + lambda dummy: self.psclient.publish( + SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, OTHER_NODE_ID)) d.addCallback(lambda items: self.assertEqual(len(items), 1)) d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_2)) d.addCallback(lambda items: self.assertEqual(len(items), 2)) def clean(res): - del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE] + del self.host.plugins["XEP-0060"].node_cache[ + C.PROFILE[0] + "@found@" + SERVICE + ] return res d.addCallback(lambda dummy: self.plugin.deleteAllGroupBlogs(C.PROFILE[0])) @@ -353,21 +521,33 @@ self._initialise(C.PROFILE[0]) self.host.profiles[C.PROFILE[0]].roster.addItem(jid.JID(OTHER_PUBLISHER)) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2])) + d.addCallback( + lambda dummy: self.psclient.publish( + SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID)) d.addCallback(lambda items: self.assertEqual(len(items), 1)) d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_1)) d.addCallback(lambda items: self.assertEqual(len(items), 2)) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2])) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2])) + d.addCallback( + lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2]) + ) + d.addCallback( + lambda dummy: self.psclient.publish( + SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, OTHER_NODE_ID)) d.addCallback(lambda items: self.assertEqual(len(items), 1)) d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_2)) d.addCallback(lambda items: self.assertEqual(len(items), 2)) def clean(res): - del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE] + del self.host.plugins["XEP-0060"].node_cache[ + C.PROFILE[0] + "@found@" + SERVICE + ] return res d.addCallback(lambda dummy: self.plugin.deleteAllGroupBlogsComments(C.PROFILE[0])) @@ -388,24 +568,38 @@ self._initialise(C.PROFILE[0]) self.host.profiles[C.PROFILE[0]].roster.addItem(jid.JID(OTHER_PUBLISHER)) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2])) + d.addCallback( + lambda dummy: self.psclient.publish( + SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID)) d.addCallback(lambda items: self.assertEqual(len(items), 1)) d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_1)) d.addCallback(lambda items: self.assertEqual(len(items), 2)) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2])) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2])) + d.addCallback( + lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2]) + ) + d.addCallback( + lambda dummy: self.psclient.publish( + SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, OTHER_NODE_ID)) d.addCallback(lambda items: self.assertEqual(len(items), 1)) d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_2)) d.addCallback(lambda items: self.assertEqual(len(items), 2)) def clean(res): - del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE] + del self.host.plugins["XEP-0060"].node_cache[ + C.PROFILE[0] + "@found@" + SERVICE + ] return res - d.addCallback(lambda dummy: self.plugin.deleteAllGroupBlogsAndComments(C.PROFILE[0])) + d.addCallback( + lambda dummy: self.plugin.deleteAllGroupBlogsAndComments(C.PROFILE[0]) + ) d.addCallback(clean) d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID))
--- a/sat/test/test_plugin_misc_radiocol.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/test/test_plugin_misc_radiocol.py Wed Jun 27 20:14:46 2018 +0200 @@ -39,7 +39,9 @@ from mutagen.easyid3 import EasyID3 from mutagen.id3 import ID3NoHeaderError except ImportError: - raise exceptions.MissingModule(u"Missing module Mutagen, please download/install from https://bitbucket.org/lazka/mutagen") + raise exceptions.MissingModule( + u"Missing module Mutagen, please download/install from https://bitbucket.org/lazka/mutagen" + ) import uuid import os @@ -49,35 +51,38 @@ ROOM_JID = JID(Const.MUC_STR[0]) PROFILE = Const.PROFILE[0] -REFEREE_FULL = JID(ROOM_JID.userhost() + '/' + Const.JID[0].user) +REFEREE_FULL = JID(ROOM_JID.userhost() + "/" + Const.JID[0].user) PLAYERS_INDICES = [0, 1, 3] # referee included OTHER_PROFILES = [Const.PROFILE[1], Const.PROFILE[3]] OTHER_PLAYERS = [Const.JID[1], Const.JID[3]] class RadiocolTest(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() def reinit(self): self.host.reinit() - self.host.plugins['ROOM-GAME'] = plugin_room_game.RoomGame(self.host) + self.host.plugins["ROOM-GAME"] = plugin_room_game.RoomGame(self.host) self.plugin = plugin.Radiocol(self.host) # must be init after ROOM-GAME self.plugin.testing = True - self.plugin_0045 = self.host.plugins['XEP-0045'] = helpers_plugins.FakeXEP_0045(self.host) - self.plugin_0249 = self.host.plugins['XEP-0249'] = helpers_plugins.FakeXEP_0249(self.host) + self.plugin_0045 = self.host.plugins["XEP-0045"] = helpers_plugins.FakeXEP_0045( + self.host + ) + self.plugin_0249 = self.host.plugins["XEP-0249"] = helpers_plugins.FakeXEP_0249( + self.host + ) for profile in Const.PROFILE: self.host.getClient(profile) # init self.host.profiles[profile] self.songs = [] self.playlist = [] - self.sound_dir = self.host.memory.getConfig('', 'media_dir') + '/test/sound/' + self.sound_dir = self.host.memory.getConfig("", "media_dir") + "/test/sound/" try: for filename in os.listdir(self.sound_dir): - if filename.endswith('.ogg') or filename.endswith('.mp3'): + if filename.endswith(".ogg") or filename.endswith(".mp3"): self.songs.append(filename) except OSError: - raise SkipTest('The sound samples in sat_media/test/sound were not found') + raise SkipTest("The sound samples in sat_media/test/sound were not found") def _buildPlayers(self, players=[]): """@return: the "started" content built with the given players""" @@ -101,10 +106,17 @@ if isinstance(content, list): new_content = copy.deepcopy(content) for element in new_content: - if not element.hasAttribute('xmlns'): - element['xmlns'] = '' + if not element.hasAttribute("xmlns"): + element["xmlns"] = "" content = "".join([element.toXml() for element in new_content]) - return "<message to='%s' type='%s'><%s xmlns='%s'>%s</%s></message>" % (to_jid.full(), type_, plugin.RADIOC_TAG, plugin.NC_RADIOCOL, content, plugin.RADIOC_TAG) + return "<message to='%s' type='%s'><%s xmlns='%s'>%s</%s></message>" % ( + to_jid.full(), + type_, + plugin.RADIOC_TAG, + plugin.NC_RADIOCOL, + content, + plugin.RADIOC_TAG, + ) def _rejectSongCb(self, profile_index): """Check if the message "song_rejected" has been sent by the referee @@ -112,24 +124,41 @@ @param profile_index: uploader's profile""" sent = self.host.getSentMessage(0) content = "<song_rejected xmlns='' reason='Too many songs in queue'/>" - self.assertEqualXML(sent.toXml(), self._expectedMessage(JID(ROOM_JID.userhost() + '/' + self.plugin_0045.getNick(0, profile_index), 'normal', content))) - self._roomGameCmd(sent, ['radiocolSongRejected', ROOM_JID.full(), 'Too many songs in queue']) + self.assertEqualXML( + sent.toXml(), + self._expectedMessage( + JID( + ROOM_JID.userhost() + + "/" + + self.plugin_0045.getNick(0, profile_index), + "normal", + content, + ) + ), + ) + self._roomGameCmd( + sent, ["radiocolSongRejected", ROOM_JID.full(), "Too many songs in queue"] + ) def _noUploadCb(self): """Check if the message "no_upload" has been sent by the referee and process the command with the profiles of each room users""" sent = self.host.getSentMessage(0) content = "<no_upload xmlns=''/>" - self.assertEqualXML(sent.toXml(), self._expectedMessage(ROOM_JID, 'groupchat', content)) - self._roomGameCmd(sent, ['radiocolNoUpload', ROOM_JID.full()]) + self.assertEqualXML( + sent.toXml(), self._expectedMessage(ROOM_JID, "groupchat", content) + ) + self._roomGameCmd(sent, ["radiocolNoUpload", ROOM_JID.full()]) def _uploadOkCb(self): """Check if the message "upload_ok" has been sent by the referee and process the command with the profiles of each room users""" sent = self.host.getSentMessage(0) content = "<upload_ok xmlns=''/>" - self.assertEqualXML(sent.toXml(), self._expectedMessage(ROOM_JID, 'groupchat', content)) - self._roomGameCmd(sent, ['radiocolUploadOk', ROOM_JID.full()]) + self.assertEqualXML( + sent.toXml(), self._expectedMessage(ROOM_JID, "groupchat", content) + ) + self._roomGameCmd(sent, ["radiocolUploadOk", ROOM_JID.full()]) def _preloadCb(self, attrs, profile_index): """Check if the message "preload" has been sent by the referee @@ -138,15 +167,33 @@ @param profile_index: profile index of the uploader """ sent = self.host.getSentMessage(0) - attrs['sender'] = self.plugin_0045.getNick(0, profile_index) - radiocol_elt = domish.generateElementsNamed(sent.elements(), 'radiocol').next() - preload_elt = domish.generateElementsNamed(radiocol_elt.elements(), 'preload').next() - attrs['timestamp'] = preload_elt['timestamp'] # we could not guess it... - content = "<preload xmlns='' %s/>" % " ".join(["%s='%s'" % (attr, attrs[attr]) for attr in attrs]) - if sent.hasAttribute('from'): - del sent['from'] - self.assertEqualXML(sent.toXml(), self._expectedMessage(ROOM_JID, 'groupchat', content)) - self._roomGameCmd(sent, ['radiocolPreload', ROOM_JID.full(), attrs['timestamp'], attrs['filename'], attrs['title'], attrs['artist'], attrs['album'], attrs['sender']]) + attrs["sender"] = self.plugin_0045.getNick(0, profile_index) + radiocol_elt = domish.generateElementsNamed(sent.elements(), "radiocol").next() + preload_elt = domish.generateElementsNamed( + radiocol_elt.elements(), "preload" + ).next() + attrs["timestamp"] = preload_elt["timestamp"] # we could not guess it... + content = "<preload xmlns='' %s/>" % " ".join( + ["%s='%s'" % (attr, attrs[attr]) for attr in attrs] + ) + if sent.hasAttribute("from"): + del sent["from"] + self.assertEqualXML( + sent.toXml(), self._expectedMessage(ROOM_JID, "groupchat", content) + ) + self._roomGameCmd( + sent, + [ + "radiocolPreload", + ROOM_JID.full(), + attrs["timestamp"], + attrs["filename"], + attrs["title"], + attrs["artist"], + attrs["album"], + attrs["sender"], + ], + ) def _playNextSongCb(self): """Check if the message "play" has been sent by the referee @@ -154,11 +201,13 @@ sent = self.host.getSentMessage(0) filename = self.playlist.pop(0) content = "<play xmlns='' filename='%s' />" % filename - self.assertEqualXML(sent.toXml(), self._expectedMessage(ROOM_JID, 'groupchat', content)) - self._roomGameCmd(sent, ['radiocolPlay', ROOM_JID.full(), filename]) + self.assertEqualXML( + sent.toXml(), self._expectedMessage(ROOM_JID, "groupchat", content) + ) + self._roomGameCmd(sent, ["radiocolPlay", ROOM_JID.full(), filename]) game_data = self.plugin.games[ROOM_JID] - if len(game_data['queue']) == plugin.QUEUE_LIMIT - 1: + if len(game_data["queue"]) == plugin.QUEUE_LIMIT - 1: self._uploadOkCb() def _addSongCb(self, d, filepath, profile_index): @@ -174,7 +223,7 @@ game_data = self.plugin.games[ROOM_JID] # this is copied from the plugin - if filepath.lower().endswith('.mp3'): + if filepath.lower().endswith(".mp3"): actual_song = MP3(filepath) try: song = EasyID3(filepath) @@ -182,27 +231,36 @@ class Info(object): def __init__(self, length): self.length = length + song.info = Info(actual_song.info.length) except ID3NoHeaderError: song = actual_song else: song = OggVorbis(filepath) - attrs = {'filename': os.path.basename(filepath), - 'title': song.get("title", ["Unknown"])[0], - 'artist': song.get("artist", ["Unknown"])[0], - 'album': song.get("album", ["Unknown"])[0], - 'length': str(song.info.length) - } - self.assertEqual(game_data['to_delete'][attrs['filename']], filepath) + attrs = { + "filename": os.path.basename(filepath), + "title": song.get("title", ["Unknown"])[0], + "artist": song.get("artist", ["Unknown"])[0], + "album": song.get("album", ["Unknown"])[0], + "length": str(song.info.length), + } + self.assertEqual(game_data["to_delete"][attrs["filename"]], filepath) - content = "<song_added xmlns='' %s/>" % " ".join(["%s='%s'" % (attr, attrs[attr]) for attr in attrs]) + content = "<song_added xmlns='' %s/>" % " ".join( + ["%s='%s'" % (attr, attrs[attr]) for attr in attrs] + ) sent = self.host.getSentMessage(profile_index) - self.assertEqualXML(sent.toXml(), self._expectedMessage(REFEREE_FULL, 'normal', content)) + self.assertEqualXML( + sent.toXml(), self._expectedMessage(REFEREE_FULL, "normal", content) + ) - reject_song = len(game_data['queue']) >= plugin.QUEUE_LIMIT - no_upload = len(game_data['queue']) + 1 >= plugin.QUEUE_LIMIT - play_next = not game_data['playing'] and len(game_data['queue']) + 1 == plugin.QUEUE_TO_START + reject_song = len(game_data["queue"]) >= plugin.QUEUE_LIMIT + no_upload = len(game_data["queue"]) + 1 >= plugin.QUEUE_LIMIT + play_next = ( + not game_data["playing"] + and len(game_data["queue"]) + 1 == plugin.QUEUE_TO_START + ) self._roomGameCmd(sent, profile_index) # queue unchanged or +1 if reject_song: @@ -211,7 +269,7 @@ if no_upload: self._noUploadCb() self._preloadCb(attrs, profile_index) - self.playlist.append(attrs['filename']) + self.playlist.append(attrs["filename"]) if play_next: self._playNextSongCb() # queue -1 @@ -229,21 +287,28 @@ call = from_index from_index = 0 - sent['from'] = ROOM_JID.full() + '/' + self.plugin_0045.getNick(0, from_index) - recipient = JID(sent['to']).resource + sent["from"] = ROOM_JID.full() + "/" + self.plugin_0045.getNick(0, from_index) + recipient = JID(sent["to"]).resource # The message could have been sent to a room user (room_jid + '/' + nick), # but when it is received, the 'to' attribute of the message has been # changed to the recipient own JID. We need to simulate that here. if recipient: room = self.plugin_0045.getRoom(0, 0) - sent['to'] = Const.JID_STR[0] if recipient == room.nick else room.roster[recipient].entity.full() + sent["to"] = ( + Const.JID_STR[0] + if recipient == room.nick + else room.roster[recipient].entity.full() + ) for index in xrange(0, len(Const.PROFILE)): nick = self.plugin_0045.getNick(0, index) if nick: if not recipient or nick == recipient: - if call and (self.plugin.isPlayer(ROOM_JID, nick) or call[0] == 'radiocolStarted'): + if call and ( + self.plugin.isPlayer(ROOM_JID, nick) + or call[0] == "radiocolStarted" + ): args = copy.deepcopy(call) args.append(Const.PROFILE[index]) self.host.bridge.expectCall(*args) @@ -255,16 +320,35 @@ @param profile_index: index of the profile to be synchronized """ for nick in sync_data: - expected = self._expectedMessage(JID(ROOM_JID.userhost() + '/' + nick), 'normal', sync_data[nick]) + expected = self._expectedMessage( + JID(ROOM_JID.userhost() + "/" + nick), "normal", sync_data[nick] + ) sent = self.host.getSentMessage(0) self.assertEqualXML(sent.toXml(), expected) for elt in sync_data[nick]: - if elt.name == 'preload': - self.host.bridge.expectCall('radiocolPreload', ROOM_JID.full(), elt['timestamp'], elt['filename'], elt['title'], elt['artist'], elt['album'], elt['sender'], Const.PROFILE[profile_index]) - elif elt.name == 'play': - self.host.bridge.expectCall('radiocolPlay', ROOM_JID.full(), elt['filename'], Const.PROFILE[profile_index]) - elif elt.name == 'no_upload': - self.host.bridge.expectCall('radiocolNoUpload', ROOM_JID.full(), Const.PROFILE[profile_index]) + if elt.name == "preload": + self.host.bridge.expectCall( + "radiocolPreload", + ROOM_JID.full(), + elt["timestamp"], + elt["filename"], + elt["title"], + elt["artist"], + elt["album"], + elt["sender"], + Const.PROFILE[profile_index], + ) + elif elt.name == "play": + self.host.bridge.expectCall( + "radiocolPlay", + ROOM_JID.full(), + elt["filename"], + Const.PROFILE[profile_index], + ) + elif elt.name == "no_upload": + self.host.bridge.expectCall( + "radiocolNoUpload", ROOM_JID.full(), Const.PROFILE[profile_index] + ) sync_data[nick] self._roomGameCmd(sent, []) @@ -280,12 +364,12 @@ if player_index not in PLAYERS_INDICES: # this user is actually not a player self.assertFalse(self.plugin.isPlayer(ROOM_JID, user_nick)) - to_jid, type_ = (JID(ROOM_JID.userhost() + '/' + user_nick), 'normal') + to_jid, type_ = (JID(ROOM_JID.userhost() + "/" + user_nick), "normal") else: # this user is a player self.assertTrue(self.plugin.isPlayer(ROOM_JID, user_nick)) nicks.append(user_nick) - to_jid, type_ = (ROOM_JID, 'groupchat') + to_jid, type_ = (ROOM_JID, "groupchat") # Check that the message "players" has been sent by the referee expected = self._expectedMessage(to_jid, type_, self._buildPlayers(nicks)) @@ -293,7 +377,16 @@ self.assertEqualXML(sent.toXml(), expected) # Process the command with the profiles of each room users - self._roomGameCmd(sent, ['radiocolStarted', ROOM_JID.full(), REFEREE_FULL.full(), nicks, [plugin.QUEUE_TO_START, plugin.QUEUE_LIMIT]]) + self._roomGameCmd( + sent, + [ + "radiocolStarted", + ROOM_JID.full(), + REFEREE_FULL.full(), + nicks, + [plugin.QUEUE_TO_START, plugin.QUEUE_LIMIT], + ], + ) if sync: self._syncCb(self.plugin._getSyncData(ROOM_JID, [user_nick]), player_index) @@ -322,12 +415,14 @@ else: song_index = song_index % len(self.songs) src_filename = self.songs[song_index] - dst_filepath = '/tmp/%s%s' % (uuid.uuid1(), os.path.splitext(src_filename)[1]) + dst_filepath = "/tmp/%s%s" % (uuid.uuid1(), os.path.splitext(src_filename)[1]) shutil.copy(self.sound_dir + src_filename, dst_filepath) expect_io_error = False try: - d = self.plugin.radiocolSongAdded(REFEREE_FULL, dst_filepath, Const.PROFILE[profile_index]) + d = self.plugin.radiocolSongAdded( + REFEREE_FULL, dst_filepath, Const.PROFILE[profile_index] + ) except IOError: self.assertTrue(expect_io_error) return @@ -340,7 +435,7 @@ self.fail("Adding a song which is not OGG nor MP3 should fail!") self.assertEqual(failure.value.__class__, exceptions.DataError) - if src_filename.endswith('.ogg') or src_filename.endswith('.mp3'): + if src_filename.endswith(".ogg") or src_filename.endswith(".mp3"): d.addCallbacks(cb, cb) else: d.addCallbacks(eb, eb) @@ -362,14 +457,28 @@ nicks = [self.plugin_0045.getNick(0, 0)] sent = self.host.getSentMessage(0) - self.assertEqualXML(sent.toXml(), self._expectedMessage(ROOM_JID, 'groupchat', self._buildPlayers(nicks))) - self._roomGameCmd(sent, ['radiocolStarted', ROOM_JID.full(), REFEREE_FULL.full(), nicks, [plugin.QUEUE_TO_START, plugin.QUEUE_LIMIT]]) + self.assertEqualXML( + sent.toXml(), + self._expectedMessage(ROOM_JID, "groupchat", self._buildPlayers(nicks)), + ) + self._roomGameCmd( + sent, + [ + "radiocolStarted", + ROOM_JID.full(), + REFEREE_FULL.full(), + nicks, + [plugin.QUEUE_TO_START, plugin.QUEUE_LIMIT], + ], + ) self._joinRoom(room, nicks, 1) # player joins self._joinRoom(room, nicks, 4) # user not playing joins song_index = 0 - self._uploadSong(song_index, 0) # ogg or mp3 file should exist in sat_media/test/song + self._uploadSong( + song_index, 0 + ) # ogg or mp3 file should exist in sat_media/test/song self._uploadSong(None, 0) # non existing file # another songs are added by Const.JID[1] until the radio starts + 1 to fill the queue @@ -379,7 +488,9 @@ self.plugin.playNext(Const.MUC[0], PROFILE) # simulate the end of the first song self._playNextSongCb() - self._uploadSong(song_index, 1) # now the song is accepted and the queue is full again + self._uploadSong( + song_index, 1 + ) # now the song is accepted and the queue is full again self._joinRoom(room, nicks, 3) # new player joins @@ -396,7 +507,7 @@ self._playNextSongCb() for filename in self.playlist: - self.plugin.deleteFile('/tmp/' + filename) + self.plugin.deleteFile("/tmp/" + filename) return defer.succeed(None)
--- a/sat/test/test_plugin_misc_room_game.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/test/test_plugin_misc_room_game.py Wed Jun 27 20:14:46 2018 +0200 @@ -30,8 +30,8 @@ from logging import WARNING # Data used for test initialization -NAMESERVICE = 'http://www.goffi.org/protocol/dummy' -TAG = 'dummy' +NAMESERVICE = "http://www.goffi.org/protocol/dummy" +TAG = "dummy" PLUGIN_INFO = { "name": "Dummy plugin", "import_name": "DUMMY", @@ -40,7 +40,7 @@ "dependencies": [], "main": "Dummy", "handler": "no", # handler MUST be "no" (dynamic inheritance) - "description": _("""Dummy plugin to test room game""") + "description": _("""Dummy plugin to test room game"""), } ROOM_JID = JID(Const.MUC_STR[0]) @@ -49,16 +49,21 @@ class RoomGameTest(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() def reinit(self, game_init={}, player_init={}): self.host.reinit() self.plugin = plugin.RoomGame(self.host) - self.plugin._init_(self.host, PLUGIN_INFO, (NAMESERVICE, TAG), game_init, player_init) - self.plugin_0045 = self.host.plugins['XEP-0045'] = helpers_plugins.FakeXEP_0045(self.host) - self.plugin_0249 = self.host.plugins['XEP-0249'] = helpers_plugins.FakeXEP_0249(self.host) + self.plugin._init_( + self.host, PLUGIN_INFO, (NAMESERVICE, TAG), game_init, player_init + ) + self.plugin_0045 = self.host.plugins["XEP-0045"] = helpers_plugins.FakeXEP_0045( + self.host + ) + self.plugin_0249 = self.host.plugins["XEP-0249"] = helpers_plugins.FakeXEP_0249( + self.host + ) for profile in Const.PROFILE: self.host.getClient(profile) # init self.host.profiles[profile] @@ -75,7 +80,13 @@ for i in xrange(0, len(players)): content += "<player index='%s'>%s</player>" % (i, players[i]) content += "</%s>" % tag - return "<message to='%s' type='%s'><%s xmlns='%s'>%s</dummy></message>" % (to.full(), type_, TAG, NAMESERVICE, content) + return "<message to='%s' type='%s'><%s xmlns='%s'>%s</dummy></message>" % ( + to.full(), + type_, + TAG, + NAMESERVICE, + content, + ) def test_createOrInvite_solo(self): self.reinit() @@ -87,14 +98,18 @@ self.reinit() self.plugin_0045.joinRoom(0, 0) other_players = [Const.JID[1], Const.JID[2]] - self.plugin._createOrInvite(self.plugin_0045.getRoom(0, 0), other_players, Const.PROFILE[0]) + self.plugin._createOrInvite( + self.plugin_0045.getRoom(0, 0), other_players, Const.PROFILE[0] + ) self.assertTrue(self.plugin._gameExists(ROOM_JID, True)) def test_createOrInvite_multi_waiting(self): - self.reinit(player_init={'score': 0}) + self.reinit(player_init={"score": 0}) self.plugin_0045.joinRoom(0, 0) other_players = [Const.JID[1], Const.JID[2]] - self.plugin._createOrInvite(self.plugin_0045.getRoom(0, 0), other_players, Const.PROFILE[0]) + self.plugin._createOrInvite( + self.plugin_0045.getRoom(0, 0), other_players, Const.PROFILE[0] + ) self.assertTrue(self.plugin._gameExists(ROOM_JID, False)) self.assertFalse(self.plugin._gameExists(ROOM_JID, True)) @@ -102,11 +117,13 @@ self.reinit() self.initGame(0, 0) self.assertTrue(self.plugin.isReferee(ROOM_JID, Const.JID[0].user)) - self.assertEqual([], self.plugin.games[ROOM_JID]['players']) + self.assertEqual([], self.plugin.games[ROOM_JID]["players"]) def test_checkJoinAuth(self): self.reinit() - check = lambda value: getattr(self, "assert%s" % value)(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[0], Const.JID[0].user)) + check = lambda value: getattr(self, "assert%s" % value)( + self.plugin._checkJoinAuth(ROOM_JID, Const.JID[0], Const.JID[0].user) + ) check(False) # to test the "invited" mode, the referee must be different than the user to test self.initGame(0, 1) @@ -118,41 +135,66 @@ check(True) self.plugin.join_mode = self.plugin.NONE check(False) - self.plugin.games[ROOM_JID]['players'].append(Const.JID[0].user) + self.plugin.games[ROOM_JID]["players"].append(Const.JID[0].user) check(True) def test_updatePlayers(self): self.reinit() self.initGame(0, 0) - self.assertEqual(self.plugin.games[ROOM_JID]['players'], []) + self.assertEqual(self.plugin.games[ROOM_JID]["players"], []) self.plugin._updatePlayers(ROOM_JID, [], True, Const.PROFILE[0]) - self.assertEqual(self.plugin.games[ROOM_JID]['players'], []) + self.assertEqual(self.plugin.games[ROOM_JID]["players"], []) self.plugin._updatePlayers(ROOM_JID, ["user1"], True, Const.PROFILE[0]) - self.assertEqual(self.plugin.games[ROOM_JID]['players'], ["user1"]) + self.assertEqual(self.plugin.games[ROOM_JID]["players"], ["user1"]) self.plugin._updatePlayers(ROOM_JID, ["user2", "user3"], True, Const.PROFILE[0]) - self.assertEqual(self.plugin.games[ROOM_JID]['players'], ["user1", "user2", "user3"]) - self.plugin._updatePlayers(ROOM_JID, ["user2", "user3"], True, Const.PROFILE[0]) # should not be stored twice - self.assertEqual(self.plugin.games[ROOM_JID]['players'], ["user1", "user2", "user3"]) + self.assertEqual( + self.plugin.games[ROOM_JID]["players"], ["user1", "user2", "user3"] + ) + self.plugin._updatePlayers( + ROOM_JID, ["user2", "user3"], True, Const.PROFILE[0] + ) # should not be stored twice + self.assertEqual( + self.plugin.games[ROOM_JID]["players"], ["user1", "user2", "user3"] + ) def test_synchronizeRoom(self): self.reinit() self.initGame(0, 0) self.plugin._synchronizeRoom(ROOM_JID, [Const.MUC[0]], Const.PROFILE[0]) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "players", [])) - self.plugin.games[ROOM_JID]['players'].append("test1") + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "players", []), + ) + self.plugin.games[ROOM_JID]["players"].append("test1") self.plugin._synchronizeRoom(ROOM_JID, [Const.MUC[0]], Const.PROFILE[0]) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "players", ["test1"])) - self.plugin.games[ROOM_JID]['started'] = True - self.plugin.games[ROOM_JID]['players'].append("test2") + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "players", ["test1"]), + ) + self.plugin.games[ROOM_JID]["started"] = True + self.plugin.games[ROOM_JID]["players"].append("test2") self.plugin._synchronizeRoom(ROOM_JID, [Const.MUC[0]], Const.PROFILE[0]) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "started", ["test1", "test2"])) - self.plugin.games[ROOM_JID]['players'].append("test3") - self.plugin.games[ROOM_JID]['players'].append("test4") + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "started", ["test1", "test2"]), + ) + self.plugin.games[ROOM_JID]["players"].append("test3") + self.plugin.games[ROOM_JID]["players"].append("test4") user1 = JID(ROOM_JID.userhost() + "/" + Const.JID[0].user) user2 = JID(ROOM_JID.userhost() + "/" + Const.JID[1].user) self.plugin._synchronizeRoom(ROOM_JID, [user1, user2], Const.PROFILE[0]) - self.assertEqualXML(self.host.getSentMessageXml(0), self._expectedMessage(user1, "normal", "started", ["test1", "test2", "test3", "test4"])) - self.assertEqualXML(self.host.getSentMessageXml(0), self._expectedMessage(user2, "normal", "started", ["test1", "test2", "test3", "test4"])) + self.assertEqualXML( + self.host.getSentMessageXml(0), + self._expectedMessage( + user1, "normal", "started", ["test1", "test2", "test3", "test4"] + ), + ) + self.assertEqualXML( + self.host.getSentMessageXml(0), + self._expectedMessage( + user2, "normal", "started", ["test1", "test2", "test3", "test4"] + ), + ) def test_invitePlayers(self): self.reinit() @@ -160,25 +202,41 @@ self.plugin_0045.joinRoom(0, 1) self.assertEqual(self.plugin.invitations[ROOM_JID], []) room = self.plugin_0045.getRoom(0, 0) - nicks = self.plugin._invitePlayers(room, [Const.JID[1], Const.JID[2]], Const.JID[0].user, Const.PROFILE[0]) - self.assertEqual(self.plugin.invitations[ROOM_JID][0][1], [Const.JID[1].userhostJID(), Const.JID[2].userhostJID()]) + nicks = self.plugin._invitePlayers( + room, [Const.JID[1], Const.JID[2]], Const.JID[0].user, Const.PROFILE[0] + ) + self.assertEqual( + self.plugin.invitations[ROOM_JID][0][1], + [Const.JID[1].userhostJID(), Const.JID[2].userhostJID()], + ) # the following assertion is True because Const.JID[1] and Const.JID[2] have the same userhost self.assertEqual(nicks, [Const.JID[1].user, Const.JID[2].user]) - nicks = self.plugin._invitePlayers(room, [Const.JID[1], Const.JID[3]], Const.JID[0].user, Const.PROFILE[0]) - self.assertEqual(self.plugin.invitations[ROOM_JID][1][1], [Const.JID[1].userhostJID(), Const.JID[3].userhostJID()]) + nicks = self.plugin._invitePlayers( + room, [Const.JID[1], Const.JID[3]], Const.JID[0].user, Const.PROFILE[0] + ) + self.assertEqual( + self.plugin.invitations[ROOM_JID][1][1], + [Const.JID[1].userhostJID(), Const.JID[3].userhostJID()], + ) # this time Const.JID[1] and Const.JID[3] have the same user but the host differs self.assertEqual(nicks, [Const.JID[1].user]) def test_checkInviteAuth(self): - def check(value, index): nick = self.plugin_0045.getNick(0, index) - getattr(self, "assert%s" % value)(self.plugin._checkInviteAuth(ROOM_JID, nick)) + getattr(self, "assert%s" % value)( + self.plugin._checkInviteAuth(ROOM_JID, nick) + ) self.reinit() - for mode in [self.plugin.FROM_ALL, self.plugin.FROM_NONE, self.plugin.FROM_REFEREE, self.plugin.FROM_PLAYERS]: + for mode in [ + self.plugin.FROM_ALL, + self.plugin.FROM_NONE, + self.plugin.FROM_REFEREE, + self.plugin.FROM_PLAYERS, + ]: self.plugin.invite_mode = mode check(True, 0) @@ -193,7 +251,7 @@ check(True, 0) check(False, 1) user_nick = self.plugin_0045.joinRoom(0, 1) - self.plugin.games[ROOM_JID]['players'].append(user_nick) + self.plugin.games[ROOM_JID]["players"].append(user_nick) self.plugin.invite_mode = self.plugin.FROM_PLAYERS check(True, 0) check(True, 1) @@ -210,22 +268,25 @@ self.initGame(0, 0) self.assertTrue(self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNick(0, 0))) user_nick = self.plugin_0045.joinRoom(0, 1) - self.plugin.games[ROOM_JID]['players'].append(user_nick) + self.plugin.games[ROOM_JID]["players"].append(user_nick) self.assertTrue(self.plugin.isPlayer(ROOM_JID, user_nick)) self.assertFalse(self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNick(0, 2))) def test_checkWaitAuth(self): - def check(value, other_players, confirmed, rest): room = self.plugin_0045.getRoom(0, 0) - self.assertEqual((value, confirmed, rest), self.plugin._checkWaitAuth(room, other_players)) + self.assertEqual( + (value, confirmed, rest), self.plugin._checkWaitAuth(room, other_players) + ) self.reinit() self.initGame(0, 0) other_players = [Const.JID[1], Const.JID[3]] self.plugin.wait_mode = self.plugin.FOR_NONE check(True, [], [], []) - check(True, [Const.JID[0]], [], [Const.JID[0]]) # getRoomNickOfUser checks for the other users only + check( + True, [Const.JID[0]], [], [Const.JID[0]] + ) # getRoomNickOfUser checks for the other users only check(True, other_players, [], other_players) self.plugin.wait_mode = self.plugin.FOR_ALL check(True, [], [], []) @@ -234,28 +295,51 @@ self.plugin_0045.joinRoom(0, 1) check(False, other_players, [], other_players) self.plugin_0045.joinRoom(0, 4) - check(False, other_players, [self.plugin_0045.getNickOfUser(0, 1, 0)], [Const.JID[3]]) + check( + False, + other_players, + [self.plugin_0045.getNickOfUser(0, 1, 0)], + [Const.JID[3]], + ) self.plugin_0045.joinRoom(0, 3) - check(True, other_players, [self.plugin_0045.getNickOfUser(0, 1, 0), - self.plugin_0045.getNickOfUser(0, 3, 0)], []) + check( + True, + other_players, + [ + self.plugin_0045.getNickOfUser(0, 1, 0), + self.plugin_0045.getNickOfUser(0, 3, 0), + ], + [], + ) other_players = [Const.JID[1], Const.JID[3], Const.JID[2]] # the following assertion is True because Const.JID[1] and Const.JID[2] have the same userhost - check(True, other_players, [self.plugin_0045.getNickOfUser(0, 1, 0), - self.plugin_0045.getNickOfUser(0, 3, 0), - self.plugin_0045.getNickOfUser(0, 2, 0)], []) + check( + True, + other_players, + [ + self.plugin_0045.getNickOfUser(0, 1, 0), + self.plugin_0045.getNickOfUser(0, 3, 0), + self.plugin_0045.getNickOfUser(0, 2, 0), + ], + [], + ) def test_prepareRoom_trivial(self): self.reinit() other_players = [] self.plugin.prepareRoom(other_players, ROOM_JID, PROFILE) self.assertTrue(self.plugin._gameExists(ROOM_JID, True)) - self.assertTrue(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[0], Const.JID[0].user)) + self.assertTrue( + self.plugin._checkJoinAuth(ROOM_JID, Const.JID[0], Const.JID[0].user) + ) self.assertTrue(self.plugin._checkInviteAuth(ROOM_JID, Const.JID[0].user)) self.assertEqual((True, [], []), self.plugin._checkWaitAuth(ROOM_JID, [])) self.assertTrue(self.plugin.isReferee(ROOM_JID, Const.JID[0].user)) self.assertTrue(self.plugin.isPlayer(ROOM_JID, Const.JID[0].user)) - self.assertEqual((False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)) + self.assertEqual( + (False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE) + ) def test_prepareRoom_invite(self): self.reinit() @@ -264,10 +348,16 @@ room = self.plugin_0045.getRoom(0, 0) self.assertTrue(self.plugin._gameExists(ROOM_JID, True)) - self.assertTrue(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[1], Const.JID[1].user)) - self.assertFalse(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[3], Const.JID[3].user)) + self.assertTrue( + self.plugin._checkJoinAuth(ROOM_JID, Const.JID[1], Const.JID[1].user) + ) + self.assertFalse( + self.plugin._checkJoinAuth(ROOM_JID, Const.JID[3], Const.JID[3].user) + ) self.assertFalse(self.plugin._checkInviteAuth(ROOM_JID, Const.JID[1].user)) - self.assertEqual((True, [], other_players), self.plugin._checkWaitAuth(room, other_players)) + self.assertEqual( + (True, [], other_players), self.plugin._checkWaitAuth(room, other_players) + ) player2_nick = self.plugin_0045.joinRoom(0, 1) self.plugin.userJoinedTrigger(room, room.roster[player2_nick], PROFILE) @@ -275,21 +365,32 @@ self.assertTrue(self.plugin._checkInviteAuth(ROOM_JID, player2_nick)) self.assertFalse(self.plugin.isReferee(ROOM_JID, player2_nick)) self.assertTrue(self.plugin.isPlayer(ROOM_JID, player2_nick)) - self.assertTrue(self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNickOfUser(0, 2, 0))) + self.assertTrue( + self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNickOfUser(0, 2, 0)) + ) self.assertFalse(self.plugin.isPlayer(ROOM_JID, "xxx")) - self.assertEqual((False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, Const.PROFILE[1])) + self.assertEqual( + (False, False), + self.plugin._checkCreateGameAndInit(ROOM_JID, Const.PROFILE[1]), + ) def test_prepareRoom_score1(self): - self.reinit(player_init={'score': 0}) + self.reinit(player_init={"score": 0}) other_players = [Const.JID[1], Const.JID[2]] self.plugin.prepareRoom(other_players, ROOM_JID, PROFILE) room = self.plugin_0045.getRoom(0, 0) self.assertFalse(self.plugin._gameExists(ROOM_JID, True)) - self.assertTrue(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[1], Const.JID[1].user)) - self.assertFalse(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[3], Const.JID[3].user)) + self.assertTrue( + self.plugin._checkJoinAuth(ROOM_JID, Const.JID[1], Const.JID[1].user) + ) + self.assertFalse( + self.plugin._checkJoinAuth(ROOM_JID, Const.JID[3], Const.JID[3].user) + ) self.assertFalse(self.plugin._checkInviteAuth(ROOM_JID, Const.JID[1].user)) - self.assertEqual((False, [], other_players), self.plugin._checkWaitAuth(room, other_players)) + self.assertEqual( + (False, [], other_players), self.plugin._checkWaitAuth(room, other_players) + ) user_nick = self.plugin_0045.joinRoom(0, 1) self.plugin.userJoinedTrigger(room, room.roster[user_nick], PROFILE) @@ -298,25 +399,33 @@ self.assertFalse(self.plugin.isReferee(ROOM_JID, user_nick)) self.assertTrue(self.plugin.isPlayer(ROOM_JID, user_nick)) # the following assertion is True because Const.JID[1] and Const.JID[2] have the same userhost - self.assertTrue(self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNickOfUser(0, 2, 0))) + self.assertTrue( + self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNickOfUser(0, 2, 0)) + ) # the following assertion is True because Const.JID[1] nick in the room is equal to Const.JID[3].user self.assertTrue(self.plugin.isPlayer(ROOM_JID, Const.JID[3].user)) # but Const.JID[3] is actually not in the room self.assertEqual(self.plugin_0045.getNickOfUser(0, 3, 0), None) - self.assertEqual((True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, Const.PROFILE[0])) + self.assertEqual( + (True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, Const.PROFILE[0]) + ) def test_prepareRoom_score2(self): - self.reinit(player_init={'score': 0}) + self.reinit(player_init={"score": 0}) other_players = [Const.JID[1], Const.JID[4]] self.plugin.prepareRoom(other_players, ROOM_JID, PROFILE) room = self.plugin_0045.getRoom(0, 0) user_nick = self.plugin_0045.joinRoom(0, 1) self.plugin.userJoinedTrigger(room, room.roster[user_nick], PROFILE) - self.assertEqual((True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)) + self.assertEqual( + (True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE) + ) user_nick = self.plugin_0045.joinRoom(0, 4) self.plugin.userJoinedTrigger(room, room.roster[user_nick], PROFILE) - self.assertEqual((False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)) + self.assertEqual( + (False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE) + ) def test_userJoinedTrigger(self): self.reinit(player_init={"xxx": "xyz"}) @@ -324,34 +433,50 @@ self.plugin.prepareRoom(other_players, ROOM_JID, PROFILE) nicks = [self.plugin_0045.getNick(0, 0)] - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "players", nicks)) + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "players", nicks), + ) self.assertTrue(len(self.plugin.invitations[ROOM_JID]) == 1) # wrong profile user_nick = self.plugin_0045.joinRoom(0, 1) room = self.plugin_0045.getRoom(0, 1) self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[1]), OTHER_PROFILE) - self.assertEqual(self.host.getSentMessage(0), None) # no new message has been sent + self.assertEqual( + self.host.getSentMessage(0), None + ) # no new message has been sent self.assertFalse(self.plugin._gameExists(ROOM_JID, True)) # game not started # referee profile, user is allowed, wait for one more room = self.plugin_0045.getRoom(0, 0) self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[1]), PROFILE) nicks.append(user_nick) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "players", nicks)) + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "players", nicks), + ) self.assertFalse(self.plugin._gameExists(ROOM_JID, True)) # game not started # referee profile, user is not allowed user_nick = self.plugin_0045.joinRoom(0, 4) self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[4]), PROFILE) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(JID(ROOM_JID.userhost() + '/' + user_nick), "normal", "players", nicks)) + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage( + JID(ROOM_JID.userhost() + "/" + user_nick), "normal", "players", nicks + ), + ) self.assertFalse(self.plugin._gameExists(ROOM_JID, True)) # game not started # referee profile, user is allowed, everybody here user_nick = self.plugin_0045.joinRoom(0, 3) self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[3]), PROFILE) nicks.append(user_nick) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "started", nicks)) + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "started", nicks), + ) self.assertTrue(self.plugin._gameExists(ROOM_JID, True)) # game started self.assertTrue(len(self.plugin.invitations[ROOM_JID]) == 0) @@ -364,7 +489,10 @@ user_nick = self.plugin_0045.joinRoom(0, 3) self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[3]), PROFILE) nicks.append(user_nick) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "started", nicks)) + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "started", nicks), + ) self.assertTrue(self.plugin._gameExists(ROOM_JID, True)) def test_userLeftTrigger(self): @@ -373,7 +501,14 @@ self.plugin.prepareRoom(other_players, ROOM_JID, PROFILE) room = self.plugin_0045.getRoom(0, 0) nicks = [self.plugin_0045.getNick(0, 0)] - self.assertEqual(self.plugin.invitations[ROOM_JID][0][1], [Const.JID[1].userhostJID(), Const.JID[3].userhostJID(), Const.JID[4].userhostJID()]) + self.assertEqual( + self.plugin.invitations[ROOM_JID][0][1], + [ + Const.JID[1].userhostJID(), + Const.JID[3].userhostJID(), + Const.JID[4].userhostJID(), + ], + ) # one user joins user_nick = self.plugin_0045.joinRoom(0, 1) @@ -381,16 +516,20 @@ nicks.append(user_nick) # the user leaves - self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks) + self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks) room = self.plugin_0045.getRoom(0, 1) # to not call self.plugin_0045.leaveRoom(0, 1) here, we are testing the trigger with a wrong profile - self.plugin.userLeftTrigger(room, User(user_nick, Const.JID[1]), Const.PROFILE[1]) # not the referee - self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks) + self.plugin.userLeftTrigger( + room, User(user_nick, Const.JID[1]), Const.PROFILE[1] + ) # not the referee + self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks) room = self.plugin_0045.getRoom(0, 0) user_nick = self.plugin_0045.leaveRoom(0, 1) - self.plugin.userLeftTrigger(room, User(user_nick, Const.JID[1]), PROFILE) # referee + self.plugin.userLeftTrigger( + room, User(user_nick, Const.JID[1]), PROFILE + ) # referee nicks.pop() - self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks) + self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks) # all the users join user_nick = self.plugin_0045.joinRoom(0, 1) @@ -402,20 +541,25 @@ user_nick = self.plugin_0045.joinRoom(0, 4) self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[4]), PROFILE) nicks.append(user_nick) - self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks) + self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks) self.assertTrue(len(self.plugin.invitations[ROOM_JID]) == 0) # one user leaves user_nick = self.plugin_0045.leaveRoom(0, 4) self.plugin.userLeftTrigger(room, User(user_nick, Const.JID[4]), PROFILE) nicks.pop() - self.assertEqual(self.plugin.invitations[ROOM_JID][0][1], [Const.JID[4].userhostJID()]) + self.assertEqual( + self.plugin.invitations[ROOM_JID][0][1], [Const.JID[4].userhostJID()] + ) # another leaves user_nick = self.plugin_0045.leaveRoom(0, 3) self.plugin.userLeftTrigger(room, User(user_nick, Const.JID[3]), PROFILE) nicks.pop() - self.assertEqual(self.plugin.invitations[ROOM_JID][0][1], [Const.JID[4].userhostJID(), Const.JID[3].userhostJID()]) + self.assertEqual( + self.plugin.invitations[ROOM_JID][0][1], + [Const.JID[4].userhostJID(), Const.JID[3].userhostJID()], + ) # they can join again user_nick = self.plugin_0045.joinRoom(0, 3) @@ -424,31 +568,43 @@ user_nick = self.plugin_0045.joinRoom(0, 4) self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[4]), PROFILE) nicks.append(user_nick) - self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks) + self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks) self.assertTrue(len(self.plugin.invitations[ROOM_JID]) == 0) def test__checkCreateGameAndInit(self): self.reinit() helpers.muteLogging() - self.assertEqual((False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)) + self.assertEqual( + (False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE) + ) helpers.unmuteLogging() nick = self.plugin_0045.joinRoom(0, 0) - self.assertEqual((True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)) + self.assertEqual( + (True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE) + ) self.assertTrue(self.plugin._gameExists(ROOM_JID, False)) self.assertFalse(self.plugin._gameExists(ROOM_JID, True)) self.assertTrue(self.plugin.isReferee(ROOM_JID, nick)) helpers.muteLogging() - self.assertEqual((False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE)) + self.assertEqual( + (False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE) + ) helpers.unmuteLogging() self.plugin_0045.joinRoom(0, 1) - self.assertEqual((False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE)) + self.assertEqual( + (False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE) + ) self.plugin.createGame(ROOM_JID, [Const.JID[1]], PROFILE) - self.assertEqual((False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)) - self.assertEqual((False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE)) + self.assertEqual( + (False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE) + ) + self.assertEqual( + (False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE) + ) def test_createGame(self): @@ -460,27 +616,39 @@ # game not exists self.plugin.createGame(ROOM_JID, nicks, PROFILE) self.assertTrue(self.plugin._gameExists(ROOM_JID, True)) - self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "started", nicks)) + self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks) + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "started", nicks), + ) for nick in nicks: - self.assertEqual('init', self.plugin.games[ROOM_JID]['status'][nick]) - self.assertEqual(self.plugin.player_init, self.plugin.games[ROOM_JID]['players_data'][nick]) - self.plugin.games[ROOM_JID]['players_data'][nick]["xxx"] = nick + self.assertEqual("init", self.plugin.games[ROOM_JID]["status"][nick]) + self.assertEqual( + self.plugin.player_init, self.plugin.games[ROOM_JID]["players_data"][nick] + ) + self.plugin.games[ROOM_JID]["players_data"][nick]["xxx"] = nick for nick in nicks: # checks that a copy of self.player_init has been done and not a reference - self.assertEqual(nick, self.plugin.games[ROOM_JID]['players_data'][nick]['xxx']) + self.assertEqual( + nick, self.plugin.games[ROOM_JID]["players_data"][nick]["xxx"] + ) # game exists, current profile is referee self.reinit(player_init={"xxx": "xyz"}) self.initGame(0, 0) - self.plugin.games[ROOM_JID]['started'] = True + self.plugin.games[ROOM_JID]["started"] = True self.plugin.createGame(ROOM_JID, nicks, PROFILE) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "started", nicks)) + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "started", nicks), + ) # game exists, current profile is not referee self.reinit(player_init={"xxx": "xyz"}) self.initGame(0, 0) - self.plugin.games[ROOM_JID]['started'] = True + self.plugin.games[ROOM_JID]["started"] = True self.plugin_0045.joinRoom(0, 1) self.plugin.createGame(ROOM_JID, nicks, OTHER_PROFILE) - self.assertEqual(self.host.getSentMessage(0), None) # no sync message has been sent by other_profile + self.assertEqual( + self.host.getSentMessage(0), None + ) # no sync message has been sent by other_profile
--- a/sat/test/test_plugin_misc_text_syntaxes.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/test/test_plugin_misc_text_syntaxes.py Wed Jun 27 20:14:46 2018 +0200 @@ -91,10 +91,14 @@ def test_html2text(self): """Check that html2text is not inserting \n in the middle of that link. By default lines are truncated after the 79th characters.""" - source = "<img src=\"http://sat.goffi.org/static/images/screenshots/libervia/libervia_discussions.png\" alt=\"sat\"/>" + source = '<img src="http://sat.goffi.org/static/images/screenshots/libervia/libervia_discussions.png" alt="sat"/>' expected = "![sat](http://sat.goffi.org/static/images/screenshots/libervia/libervia_discussions.png)" try: - d = self.text_syntaxes.convert(source, self.text_syntaxes.SYNTAX_XHTML, self.text_syntaxes.SYNTAX_MARKDOWN) + d = self.text_syntaxes.convert( + source, + self.text_syntaxes.SYNTAX_XHTML, + self.text_syntaxes.SYNTAX_MARKDOWN, + ) except plugin_misc_text_syntaxes.UnknownSyntax: raise SkipTest("Markdown syntax is not available.") d.addCallback(self.assertEqual, expected) @@ -108,4 +112,3 @@ expected = u"""test retest toto""" result = self.text_syntaxes._removeMarkups(self.EVIL_HTML2) self.assertEqual(re.sub(r"\s+", " ", result).rstrip(), expected.rstrip()) -
--- a/sat/test/test_plugin_xep_0033.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/test/test_plugin_xep_0033.py Wed Jun 27 20:14:46 2018 +0200 @@ -36,11 +36,10 @@ JID_STR_X_CC = Const.JID_STR[1] JID_STR_X_BCC = Const.JID_STR[2] -ADDRS = ('to', JID_STR_X_TO, 'cc', JID_STR_X_CC, 'bcc', JID_STR_X_BCC) +ADDRS = ("to", JID_STR_X_TO, "cc", JID_STR_X_CC, "bcc", JID_STR_X_BCC) class XEP_0033Test(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() self.plugin = plugin.XEP_0033(self.host) @@ -56,48 +55,67 @@ <address type='bcc' jid='%s'/> </addresses> </message> - """ % (JID_STR_FROM, JID_STR_TO, JID_STR_X_TO, JID_STR_X_CC, JID_STR_X_BCC) + """ % ( + JID_STR_FROM, + JID_STR_TO, + JID_STR_X_TO, + JID_STR_X_CC, + JID_STR_X_BCC, + ) stanza = parseXml(xml.encode("utf-8")) treatments = defer.Deferred() - self.plugin.messageReceivedTrigger(self.host.getClient(PROFILE), stanza, treatments) - data = {'extra': {}} + self.plugin.messageReceivedTrigger( + self.host.getClient(PROFILE), stanza, treatments + ) + data = {"extra": {}} def cb(data): - expected = ('to', JID_STR_X_TO, 'cc', JID_STR_X_CC, 'bcc', JID_STR_X_BCC) - msg = 'Expected: %s\nGot: %s' % (expected, data['extra']['addresses']) - self.assertEqual(data['extra']['addresses'], '%s:%s\n%s:%s\n%s:%s\n' % expected, msg) + expected = ("to", JID_STR_X_TO, "cc", JID_STR_X_CC, "bcc", JID_STR_X_BCC) + msg = "Expected: %s\nGot: %s" % (expected, data["extra"]["addresses"]) + self.assertEqual( + data["extra"]["addresses"], "%s:%s\n%s:%s\n%s:%s\n" % expected, msg + ) treatments.addCallback(cb) return treatments.callback(data) def _get_mess_data(self): - mess_data = {"to": JID(JID_STR_TO), - "type": "chat", - "message": "content", - "extra": {} - } - mess_data["extra"]["address"] = '%s:%s\n%s:%s\n%s:%s\n' % ADDRS + mess_data = { + "to": JID(JID_STR_TO), + "type": "chat", + "message": "content", + "extra": {}, + } + mess_data["extra"]["address"] = "%s:%s\n%s:%s\n%s:%s\n" % ADDRS original_stanza = u""" <message type="chat" from="%s" to="%s" id="test_1"> <body>content</body> </message> - """ % (JID_STR_FROM, JID_STR_TO) - mess_data['xml'] = parseXml(original_stanza.encode("utf-8")) + """ % ( + JID_STR_FROM, + JID_STR_TO, + ) + mess_data["xml"] = parseXml(original_stanza.encode("utf-8")) return mess_data def _assertAddresses(self, mess_data): """The mess_data that we got here has been modified by self.plugin.messageSendTrigger, check that the addresses element has been added to the stanza.""" - expected = self._get_mess_data()['xml'] - addresses_extra = """ + expected = self._get_mess_data()["xml"] + addresses_extra = ( + """ <addresses xmlns='http://jabber.org/protocol/address'> <address type='%s' jid='%s'/> <address type='%s' jid='%s'/> <address type='%s' jid='%s'/> - </addresses>""" % ADDRS - addresses_element = parseXml(addresses_extra.encode('utf-8')) + </addresses>""" + % ADDRS + ) + addresses_element = parseXml(addresses_extra.encode("utf-8")) expected.addChild(addresses_element) - self.assertEqualXML(mess_data['xml'].toXml().encode("utf-8"), expected.toXml().encode("utf-8")) + self.assertEqualXML( + mess_data["xml"].toXml().encode("utf-8"), expected.toXml().encode("utf-8") + ) def _checkSentAndStored(self): """Check that all the recipients got their messages and that the history has been filled. @@ -108,7 +126,9 @@ def cb(entities, to_jid): if host in entities: - if host not in sent: # send the message to the entity offering the feature + if ( + host not in sent + ): # send the message to the entity offering the feature sent.append(host) stored.append(host) stored.append(to_jid) # store in history for each recipient @@ -127,7 +147,9 @@ def cb_list(dummy): msg = "/!\ see the comments in XEP_0033.sendAndStoreMessage" - sent_recipients = [JID(elt['to']) for elt in self.host.getSentMessages(PROFILE_INDEX)] + sent_recipients = [ + JID(elt["to"]) for elt in self.host.getSentMessages(PROFILE_INDEX) + ] self.assertEqualUnsortedList(sent_recipients, sent, msg) self.assertEqualUnsortedList(self.host.stored_messages, stored, msg) @@ -143,10 +165,14 @@ pre_treatments = defer.Deferred() post_treatments = defer.Deferred() helpers.muteLogging() - self.plugin.messageSendTrigger(self.host.getClient[PROFILE], data, pre_treatments, post_treatments) + self.plugin.messageSendTrigger( + self.host.getClient[PROFILE], data, pre_treatments, post_treatments + ) post_treatments.callback(data) helpers.unmuteLogging() - post_treatments.addCallbacks(self._assertAddresses, lambda failure: failure.trap(CancelError)) + post_treatments.addCallbacks( + self._assertAddresses, lambda failure: failure.trap(CancelError) + ) return post_treatments def test_messageSendTriggerFeatureNotSupported(self):
--- a/sat/test/test_plugin_xep_0085.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/test/test_plugin_xep_0085.py Wed Jun 27 20:14:46 2018 +0200 @@ -30,11 +30,16 @@ class XEP_0085Test(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() self.plugin = plugin.XEP_0085(self.host) - self.host.memory.setParam(plugin.PARAM_NAME, True, plugin.PARAM_KEY, C.NO_SECURITY_LIMIT, Const.PROFILE[0]) + self.host.memory.setParam( + plugin.PARAM_NAME, + True, + plugin.PARAM_KEY, + C.NO_SECURITY_LIMIT, + Const.PROFILE[0], + ) def test_messageReceived(self): for state in plugin.CHAT_STATES: @@ -43,37 +48,51 @@ %s <%s xmlns='%s'/> </message> - """ % (Const.JID_STR[1], - Const.JID_STR[0], - "<body>test</body>" if state == "active" else "", - state, plugin.NS_CHAT_STATES) + """ % ( + Const.JID_STR[1], + Const.JID_STR[0], + "<body>test</body>" if state == "active" else "", + state, + plugin.NS_CHAT_STATES, + ) stanza = parseXml(xml.encode("utf-8")) - self.host.bridge.expectCall("chatStateReceived", Const.JID_STR[1], state, Const.PROFILE[0]) - self.plugin.messageReceivedTrigger(self.host.getClient(Const.PROFILE[0]), stanza, None) + self.host.bridge.expectCall( + "chatStateReceived", Const.JID_STR[1], state, Const.PROFILE[0] + ) + self.plugin.messageReceivedTrigger( + self.host.getClient(Const.PROFILE[0]), stanza, None + ) def test_messageSendTrigger(self): def cb(data): - xml = data['xml'].toXml().encode("utf-8") + xml = data["xml"].toXml().encode("utf-8") self.assertEqualXML(xml, expected.toXml().encode("utf-8")) d_list = [] for state in plugin.CHAT_STATES: - mess_data = {"to": Const.JID[0], - "type": "chat", - "message": "content", - "extra": {} if state == "active" else {"chat_state": state}} + mess_data = { + "to": Const.JID[0], + "type": "chat", + "message": "content", + "extra": {} if state == "active" else {"chat_state": state}, + } stanza = u""" <message type="chat" from="%s" to="%s" id="test_1"> %s </message> - """ % (Const.JID_STR[1], Const.JID_STR[0], - ("<body>%s</body>" % mess_data['message']) if state == "active" else "") - mess_data['xml'] = parseXml(stanza.encode("utf-8")) - expected = deepcopy(mess_data['xml']) + """ % ( + Const.JID_STR[1], + Const.JID_STR[0], + ("<body>%s</body>" % mess_data["message"]) if state == "active" else "", + ) + mess_data["xml"] = parseXml(stanza.encode("utf-8")) + expected = deepcopy(mess_data["xml"]) expected.addElement(state, plugin.NS_CHAT_STATES) post_treatments = defer.Deferred() - self.plugin.messageSendTrigger(self.host.getClient(Const.PROFILE[0]), mess_data, None, post_treatments) + self.plugin.messageSendTrigger( + self.host.getClient(Const.PROFILE[0]), mess_data, None, post_treatments + ) post_treatments.addCallback(cb) post_treatments.callback(mess_data)
--- a/sat/test/test_plugin_xep_0203.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/test/test_plugin_xep_0203.py Wed Jun 27 20:14:46 2018 +0200 @@ -27,11 +27,10 @@ from dateutil.tz import tzutc import datetime -NS_PUBSUB = 'http://jabber.org/protocol/pubsub' +NS_PUBSUB = "http://jabber.org/protocol/pubsub" class XEP_0203Test(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() self.plugin = XEP_0203(self.host) @@ -44,7 +43,8 @@ Offline Storage </delay> """ - message_xml = """ + message_xml = ( + """ <message from='romeo@montague.net/orchard' to='juliet@capulet.com' @@ -52,14 +52,16 @@ <body>text</body> %s </message> - """ % delay_xml + """ + % delay_xml + ) - parent = domish.Element((None, 'message')) - parent['from'] = 'romeo@montague.net/orchard' - parent['to'] = 'juliet@capulet.com' - parent['type'] = 'chat' - parent.addElement('body', None, 'text') + parent = domish.Element((None, "message")) + parent["from"] = "romeo@montague.net/orchard" + parent["to"] = "juliet@capulet.com" + parent["type"] = "chat" + parent.addElement("body", None, "text") stamp = datetime.datetime(2002, 9, 10, 23, 8, 25, tzinfo=tzutc()) - elt = self.plugin.delay(stamp, JID('capulet.com'), 'Offline Storage', parent) + elt = self.plugin.delay(stamp, JID("capulet.com"), "Offline Storage", parent) self.assertEqualXML(elt.toXml(), delay_xml, True) self.assertEqualXML(parent.toXml(), message_xml, True)
--- a/sat/test/test_plugin_xep_0277.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/test/test_plugin_xep_0277.py Wed Jun 27 20:14:46 2018 +0200 @@ -29,7 +29,8 @@ class XEP_0277Test(helpers.SatTestCase): - PUBSUB_ENTRY_1 = u""" + PUBSUB_ENTRY_1 = ( + u""" <item id="c745a688-9b02-11e3-a1a3-c0143dd4fe51"> <entry xmlns="%s"> <title type="text"><span>titre</span></title> @@ -43,9 +44,12 @@ </author> </entry> </item> - """ % plugin_xep_0277.NS_ATOM + """ + % plugin_xep_0277.NS_ATOM + ) - PUBSUB_ENTRY_2 = u""" + PUBSUB_ENTRY_2 = ( + u""" <item id="c745a688-9b02-11e3-a1a3-c0143dd4fe51"> <entry xmlns='%s'> <title type="text"><div>titre</div></title> @@ -61,7 +65,9 @@ </author> </entry> </item> - """ % plugin_xep_0277.NS_ATOM + """ + % plugin_xep_0277.NS_ATOM + ) def setUp(self): self.host = helpers.FakeSAT() @@ -72,39 +78,48 @@ def addPEPEvent(self, *args): pass + self.host.plugins["XEP-0060"] = plugin_xep_0060.XEP_0060(self.host) self.host.plugins["XEP-0163"] = XEP_0163(self.host) reload(plugin_misc_text_syntaxes) # reload the plugin to avoid conflict error - self.host.plugins["TEXT-SYNTAXES"] = plugin_misc_text_syntaxes.TextSyntaxes(self.host) + self.host.plugins["TEXT-SYNTAXES"] = plugin_misc_text_syntaxes.TextSyntaxes( + self.host + ) self.plugin = plugin_xep_0277.XEP_0277(self.host) def test_item2mbdata_1(self): - expected = {u'id': u'c745a688-9b02-11e3-a1a3-c0143dd4fe51', - u'atom_id': u'c745a688-9b02-11e3-a1a3-c0143dd4fe51', - u'title': u'<span>titre</span>', - u'updated': u'1392992199.0', - u'published': u'1392992198.0', - u'content': u'<p>contenu</p>texte sans balise<p>autre contenu</p>', - u'content_xhtml': u'<div><p>contenu</p>texte sans balise<p>autre contenu</p></div>', - u'author': u'test1@souliane.org' - } - item_elt = ElementParser()(self.PUBSUB_ENTRY_1, namespace=NS_PUBSUB).elements().next() + expected = { + u"id": u"c745a688-9b02-11e3-a1a3-c0143dd4fe51", + u"atom_id": u"c745a688-9b02-11e3-a1a3-c0143dd4fe51", + u"title": u"<span>titre</span>", + u"updated": u"1392992199.0", + u"published": u"1392992198.0", + u"content": u"<p>contenu</p>texte sans balise<p>autre contenu</p>", + u"content_xhtml": u"<div><p>contenu</p>texte sans balise<p>autre contenu</p></div>", + u"author": u"test1@souliane.org", + } + item_elt = ( + ElementParser()(self.PUBSUB_ENTRY_1, namespace=NS_PUBSUB).elements().next() + ) d = self.plugin.item2mbdata(item_elt) d.addCallback(self.assertEqual, expected) return d def test_item2mbdata_2(self): - expected = {u'id': u'c745a688-9b02-11e3-a1a3-c0143dd4fe51', - u'atom_id': u'c745a688-9b02-11e3-a1a3-c0143dd4fe51', - u'title': u'<div>titre</div>', - u'title_xhtml': u'<div><div style="">titre</div></div>', - u'updated': u'1392992199.0', - u'published': u'1392992198.0', - u'content': u'<div><p>contenu</p>texte dans balise<p>autre contenu</p></div>', - u'content_xhtml': u'<div><p>contenu</p>texte dans balise<p>autre contenu</p></div>', - u'author': u'test1@souliane.org' - } - item_elt = ElementParser()(self.PUBSUB_ENTRY_2, namespace=NS_PUBSUB).elements().next() + expected = { + u"id": u"c745a688-9b02-11e3-a1a3-c0143dd4fe51", + u"atom_id": u"c745a688-9b02-11e3-a1a3-c0143dd4fe51", + u"title": u"<div>titre</div>", + u"title_xhtml": u'<div><div style="">titre</div></div>', + u"updated": u"1392992199.0", + u"published": u"1392992198.0", + u"content": u"<div><p>contenu</p>texte dans balise<p>autre contenu</p></div>", + u"content_xhtml": u"<div><p>contenu</p>texte dans balise<p>autre contenu</p></div>", + u"author": u"test1@souliane.org", + } + item_elt = ( + ElementParser()(self.PUBSUB_ENTRY_2, namespace=NS_PUBSUB).elements().next() + ) d = self.plugin.item2mbdata(item_elt) d.addCallback(self.assertEqual, expected) return d
--- a/sat/test/test_plugin_xep_0297.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/test/test_plugin_xep_0297.py Wed Jun 27 20:14:46 2018 +0200 @@ -30,18 +30,18 @@ from wokkel.generic import parseXml -NS_PUBSUB = 'http://jabber.org/protocol/pubsub' +NS_PUBSUB = "http://jabber.org/protocol/pubsub" class XEP_0297Test(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() self.plugin = XEP_0297(self.host) - self.host.plugins['XEP-0203'] = XEP_0203(self.host) + self.host.plugins["XEP-0203"] = XEP_0203(self.host) def test_delay(self): - stanza = parseXml(""" + stanza = parseXml( + """ <message from='juliet@capulet.lit/orchard' id='0202197' to='romeo@montague.lit' @@ -51,7 +51,10 @@ <amorous/> </mood> </message> - """.encode('utf-8')) + """.encode( + "utf-8" + ) + ) output = """ <message to='mercutio@verona.lit' type='chat'> <body>A most courteous exposition!</body> @@ -71,8 +74,16 @@ </message> """ stamp = datetime.datetime(2010, 7, 10, 23, 8, 25, tzinfo=tzutc()) - d = self.plugin.forward(stanza, JID('mercutio@verona.lit'), stamp, - body='A most courteous exposition!', - profile_key=C.PROFILE[0]) - d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), output, True)) + d = self.plugin.forward( + stanza, + JID("mercutio@verona.lit"), + stamp, + body="A most courteous exposition!", + profile_key=C.PROFILE[0], + ) + d.addCallback( + lambda dummy: self.assertEqualXML( + self.host.getSentMessageXml(0), output, True + ) + ) return d
--- a/sat/test/test_plugin_xep_0313.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/test/test_plugin_xep_0313.py Wed Jun 27 20:14:46 2018 +0200 @@ -33,13 +33,12 @@ from sat_tmp.wokkel.rsm import RSMRequest from sat_tmp.wokkel.mam import buildForm, MAMRequest -NS_PUBSUB = 'http://jabber.org/protocol/pubsub' -SERVICE = 'sat-pubsub.tazar.int' +NS_PUBSUB = "http://jabber.org/protocol/pubsub" +SERVICE = "sat-pubsub.tazar.int" SERVICE_JID = JID(SERVICE) class XEP_0313Test(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() self.plugin = XEP_0313(self.host) @@ -52,9 +51,14 @@ <iq type='set' id='%s' to='%s'> <query xmlns='urn:xmpp:mam:1'/> </iq> - """ % (("H_%d" % domish.Element._idCounter), SERVICE) + """ % ( + ("H_%d" % domish.Element._idCounter), + SERVICE, + ) d = self.plugin.queryArchive(self.client, MAMRequest(), SERVICE_JID) - d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)) + d.addCallback( + lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True) + ) return d def test_queryArchivePubsub(self): @@ -62,9 +66,16 @@ <iq type='set' id='%s' to='%s'> <query xmlns='urn:xmpp:mam:1' node='fdp/submitted/capulet.lit/sonnets' /> </iq> - """ % (("H_%d" % domish.Element._idCounter), SERVICE) - d = self.plugin.queryArchive(self.client, MAMRequest(node="fdp/submitted/capulet.lit/sonnets"), SERVICE_JID) - d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)) + """ % ( + ("H_%d" % domish.Element._idCounter), + SERVICE, + ) + d = self.plugin.queryArchive( + self.client, MAMRequest(node="fdp/submitted/capulet.lit/sonnets"), SERVICE_JID + ) + d.addCallback( + lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True) + ) return d def test_queryArchiveWith(self): @@ -81,10 +92,15 @@ </x> </query> </iq> - """ % (("H_%d" % domish.Element._idCounter), SERVICE) - form = buildForm(with_jid=JID('juliet@capulet.lit')) + """ % ( + ("H_%d" % domish.Element._idCounter), + SERVICE, + ) + form = buildForm(with_jid=JID("juliet@capulet.lit")) d = self.plugin.queryArchive(self.client, MAMRequest(form), SERVICE_JID) - d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)) + d.addCallback( + lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True) + ) return d def test_queryArchiveStartEnd(self): @@ -104,12 +120,17 @@ </x> </query> </iq> - """ % (("H_%d" % domish.Element._idCounter), SERVICE) + """ % ( + ("H_%d" % domish.Element._idCounter), + SERVICE, + ) start = datetime.datetime(2010, 6, 7, 0, 0, 0, tzinfo=tzutc()) end = datetime.datetime(2010, 7, 7, 13, 23, 54, tzinfo=tzutc()) form = buildForm(start=start, end=end) d = self.plugin.queryArchive(self.client, MAMRequest(form), SERVICE_JID) - d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)) + d.addCallback( + lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True) + ) return d def test_queryArchiveStart(self): @@ -126,11 +147,16 @@ </x> </query> </iq> - """ % (("H_%d" % domish.Element._idCounter), SERVICE) + """ % ( + ("H_%d" % domish.Element._idCounter), + SERVICE, + ) start = datetime.datetime(2010, 8, 7, 0, 0, 0, tzinfo=tzutc()) form = buildForm(start=start) d = self.plugin.queryArchive(self.client, MAMRequest(form), SERVICE_JID) - d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)) + d.addCallback( + lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True) + ) return d def test_queryArchiveRSM(self): @@ -150,12 +176,17 @@ </set> </query> </iq> - """ % (("H_%d" % domish.Element._idCounter), SERVICE) + """ % ( + ("H_%d" % domish.Element._idCounter), + SERVICE, + ) start = datetime.datetime(2010, 8, 7, 0, 0, 0, tzinfo=tzutc()) form = buildForm(start=start) rsm = RSMRequest(max_=10) d = self.plugin.queryArchive(self.client, MAMRequest(form, rsm), SERVICE_JID) - d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)) + d.addCallback( + lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True) + ) return d def test_queryArchiveRSMPaging(self): @@ -172,12 +203,17 @@ </set> </query> </iq> - """ % (("H_%d" % domish.Element._idCounter), SERVICE) + """ % ( + ("H_%d" % domish.Element._idCounter), + SERVICE, + ) start = datetime.datetime(2010, 8, 7, 0, 0, 0, tzinfo=tzutc()) form = buildForm(start=start) - rsm = RSMRequest(max_=10, after=u'09af3-cc343-b409f') + rsm = RSMRequest(max_=10, after=u"09af3-cc343-b409f") d = self.plugin.queryArchive(self.client, MAMRequest(form, rsm), SERVICE_JID) - d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)) + d.addCallback( + lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True) + ) return d def test_queryFields(self): @@ -185,9 +221,14 @@ <iq type='get' id="%s" to='%s'> <query xmlns='urn:xmpp:mam:1'/> </iq> - """ % (("H_%d" % domish.Element._idCounter), SERVICE) + """ % ( + ("H_%d" % domish.Element._idCounter), + SERVICE, + ) d = self.plugin.queryFields(self.client, SERVICE_JID) - d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)) + d.addCallback( + lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True) + ) return d def test_queryArchiveFields(self): @@ -207,13 +248,27 @@ </x> </query> </iq> - """ % (("H_%d" % domish.Element._idCounter), SERVICE) - extra_fields = [Field('text-single', 'urn:example:xmpp:free-text-search', 'Where arth thou, my Juliet?'), - Field('text-single', 'urn:example:xmpp:stanza-content', '{http://jabber.org/protocol/mood}mood/lonely') - ] + """ % ( + ("H_%d" % domish.Element._idCounter), + SERVICE, + ) + extra_fields = [ + Field( + "text-single", + "urn:example:xmpp:free-text-search", + "Where arth thou, my Juliet?", + ), + Field( + "text-single", + "urn:example:xmpp:stanza-content", + "{http://jabber.org/protocol/mood}mood/lonely", + ), + ] form = buildForm(extra_fields=extra_fields) d = self.plugin.queryArchive(self.client, MAMRequest(form), SERVICE_JID) - d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)) + d.addCallback( + lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True) + ) return d def test_queryPrefs(self): @@ -224,9 +279,14 @@ <never/> </prefs> </iq> - """ % (("H_%d" % domish.Element._idCounter), SERVICE) + """ % ( + ("H_%d" % domish.Element._idCounter), + SERVICE, + ) d = self.plugin.getPrefs(self.client, SERVICE_JID) - d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)) + d.addCallback( + lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True) + ) return d def test_setPrefs(self): @@ -241,9 +301,14 @@ </never> </prefs> </iq> - """ % (("H_%d" % domish.Element._idCounter), SERVICE) - always = [JID('romeo@montague.lit')] - never = [JID('montague@montague.lit')] + """ % ( + ("H_%d" % domish.Element._idCounter), + SERVICE, + ) + always = [JID("romeo@montague.lit")] + never = [JID("montague@montague.lit")] d = self.plugin.setPrefs(self.client, SERVICE_JID, always=always, never=never) - d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)) + d.addCallback( + lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True) + ) return d
--- a/sat/test/test_plugin_xep_0334.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/test/test_plugin_xep_0334.py Wed Jun 27 20:14:46 2018 +0200 @@ -27,11 +27,10 @@ from wokkel.generic import parseXml from sat.core import exceptions -HINTS = ('no-permanent-storage', 'no-storage', 'no-copy') +HINTS = ("no-permanent-storage", "no-storage", "no-copy") class XEP_0334Test(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() self.plugin = XEP_0334(self.host) @@ -46,20 +45,23 @@ %s </message> """ - original_xml = template_xml % '' + original_xml = template_xml % "" d_list = [] def cb(data, expected_xml): - result_xml = data['xml'].toXml().encode("utf-8") + result_xml = data["xml"].toXml().encode("utf-8") self.assertEqualXML(result_xml, expected_xml, True) - for key in (HINTS + ('', 'dummy_hint')): - mess_data = {'xml': parseXml(original_xml.encode("utf-8")), - 'extra': {key: True} - } + for key in HINTS + ("", "dummy_hint"): + mess_data = { + "xml": parseXml(original_xml.encode("utf-8")), + "extra": {key: True}, + } treatments = defer.Deferred() - self.plugin.messageSendTrigger(self.host.getClient(C.PROFILE[0]), mess_data, defer.Deferred(), treatments) + self.plugin.messageSendTrigger( + self.host.getClient(C.PROFILE[0]), mess_data, defer.Deferred(), treatments + ) if treatments.callbacks: # the trigger added a callback expected_xml = template_xml % ('<%s xmlns="urn:xmpp:hints"/>' % key) treatments.addCallback(cb, expected_xml) @@ -87,16 +89,18 @@ d_list = [] - for key in (HINTS + ('dummy_hint',)): + for key in HINTS + ("dummy_hint",): message = parseXml(template_xml % ('<%s xmlns="urn:xmpp:hints"/>' % key)) post_treat = defer.Deferred() - self.plugin.messageReceivedTrigger(self.host.getClient(C.PROFILE[0]), message, post_treat) + self.plugin.messageReceivedTrigger( + self.host.getClient(C.PROFILE[0]), message, post_treat + ) if post_treat.callbacks: - assert(key in ('no-permanent-storage', 'no-storage')) + assert key in ("no-permanent-storage", "no-storage") post_treat.addCallbacks(cb, eb) post_treat.callback(None) d_list.append(post_treat) else: - assert(key not in ('no-permanent-storage', 'no-storage')) + assert key not in ("no-permanent-storage", "no-storage") return defer.DeferredList(d_list)
--- a/sat/tools/common/ansi.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/tools/common/ansi.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,15 +19,18 @@ import sys + class ANSI(object): ## ANSI escape sequences ## - RESET = '\033[0m' - NORMAL_WEIGHT = '\033[22m' - FG_BLACK, FG_RED, FG_GREEN, FG_YELLOW, FG_BLUE, FG_MAGENTA, FG_CYAN, FG_WHITE = ('\033[3%dm' % nb for nb in xrange(8)) - BOLD = '\033[1m' - BLINK = '\033[5m' - BLINK_OFF = '\033[25m' + RESET = "\033[0m" + NORMAL_WEIGHT = "\033[22m" + FG_BLACK, FG_RED, FG_GREEN, FG_YELLOW, FG_BLUE, FG_MAGENTA, FG_CYAN, FG_WHITE = ( + "\033[3%dm" % nb for nb in xrange(8) + ) + BOLD = "\033[1m" + BLINK = "\033[5m" + BLINK_OFF = "\033[25m" @classmethod def color(cls, *args): @@ -39,16 +42,19 @@ if args[-1] != cls.RESET: args = list(args) args.append(cls.RESET) - return u''.join(args) + return u"".join(args) try: tty = sys.stdout.isatty() -except (AttributeError, TypeError): # FIXME: TypeError is here for Pyjamas, need to be removed +except ( + AttributeError, + TypeError, +): # FIXME: TypeError is here for Pyjamas, need to be removed tty = False if not tty: - # we don't want ANSI escape codes if we are not outputing to a tty! + # we don't want ANSI escape codes if we are not outputing to a tty! for attr in dir(ANSI): if isinstance(getattr(ANSI, attr), basestring): - setattr(ANSI, attr, u'') + setattr(ANSI, attr, u"") del tty
--- a/sat/tools/common/data_format.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/tools/common/data_format.py Wed Jun 27 20:14:46 2018 +0200 @@ -18,10 +18,11 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. """ tools common to backend and frontends """ -# FIXME: json may be more appropriate than manual serialising like done here +# FIXME: json may be more appropriate than manual serialising like done here from sat.core import exceptions + def dict2iter(name, dict_, pop=False): """iterate into a list serialised in a dict @@ -36,24 +37,25 @@ @return iter: iterate through the deserialised list """ if pop: - get=lambda d,k: d.pop(k) + get = lambda d, k: d.pop(k) else: - get=lambda d,k: d[k] + get = lambda d, k: d[k] try: - yield get(dict_,name) + yield get(dict_, name) except KeyError: return else: idx = 1 while True: try: - yield get(dict_,u'{}#{}'.format(name, idx)) + yield get(dict_, u"{}#{}".format(name, idx)) except KeyError: return else: idx += 1 + def dict2iterdict(name, dict_, extra_keys, pop=False): """like dict2iter but yield dictionaries @@ -61,17 +63,20 @@ e.g. dict2iterdict(comments, mb_data, ('node', 'service')) will yield dicts like: {u'comments': u'value1', u'node': u'value2', u'service': u'value3'} """ - # FIXME: this format seem overcomplicated, it may be more appropriate to use json here + # FIXME: this format seem overcomplicated, it may be more appropriate to use json here if pop: - get=lambda d,k: d.pop(k) + get = lambda d, k: d.pop(k) else: - get=lambda d,k: d[k] + get = lambda d, k: d[k] for idx, main_value in enumerate(dict2iter(name, dict_, pop=pop)): ret = {name: main_value} for k in extra_keys: - ret[k] = get(dict_, u'{}{}_{}'.format(name, (u'#' + unicode(idx)) if idx else u'', k)) + ret[k] = get( + dict_, u"{}{}_{}".format(name, (u"#" + unicode(idx)) if idx else u"", k) + ) yield ret + def iter2dict(name, iter_, dict_=None, check_conflict=True): """Fill a dict with values from an iterable @@ -92,13 +97,14 @@ if idx == 0: key = name else: - key = u'{}#{}'.format(name, idx) + key = u"{}#{}".format(name, idx) if check_conflict and key in dict_: raise exceptions.ConflictError dict_[key] = value return dict -def getSubDict(name, dict_, sep=u'_'): + +def getSubDict(name, dict_, sep=u"_"): """get a sub dictionary from a serialised dictionary look for keys starting with name, and create a dict with it @@ -109,7 +115,7 @@ @param sep(unicode): separator used between name and subkey @return iter: iterate through the deserialised items """ - for k,v in dict_.iteritems(): + for k, v in dict_.iteritems(): if k.startswith(name): if k == name: yield None, v @@ -117,4 +123,4 @@ if k[len(name)] != sep: continue else: - yield k[len(name)+1:], v + yield k[len(name) + 1 :], v
--- a/sat/tools/common/data_objects.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/tools/common/data_objects.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ """Objects handling bridge data, with jinja2 safe markup handling""" from sat.tools.common import data_format + try: from jinja2 import Markup as safe except ImportError: @@ -28,11 +29,10 @@ from sat.tools.common import uri as xmpp_uri import urllib -q = lambda value: urllib.quote(value.encode('utf-8'), safe='@') +q = lambda value: urllib.quote(value.encode("utf-8"), safe="@") class BlogItem(object): - def __init__(self, mb_data, parent): self.mb_data = mb_data self.parent = parent @@ -43,97 +43,97 @@ @property def id(self): - return self.mb_data.get(u'id') + return self.mb_data.get(u"id") @property def atom_id(self): - return self.mb_data.get(u'atom_id') + return self.mb_data.get(u"atom_id") @property def uri(self): node = self.parent.node service = self.parent.service - return xmpp_uri.buildXMPPUri(u'pubsub', - subtype=u'microblog', - path=service, - node=node, - item=self.id) + return xmpp_uri.buildXMPPUri( + u"pubsub", subtype=u"microblog", path=service, node=node, item=self.id + ) @property def published(self): - return self.mb_data.get(u'published') + return self.mb_data.get(u"published") @property def updated(self): - return self.mb_data.get(u'updated') + return self.mb_data.get(u"updated") @property def language(self): - return self.mb_data.get(u'language') + return self.mb_data.get(u"language") @property def author(self): - return self.mb_data.get(u'author') + return self.mb_data.get(u"author") @property def author_jid(self): - return self.mb_data.get(u'author_jid') + return self.mb_data.get(u"author_jid") @property def author_jid_verified(self): - return self.mb_data.get(u'author_jid_verified') + return self.mb_data.get(u"author_jid_verified") @property def author_email(self): - return self.mb_data.get(u'author_email') + return self.mb_data.get(u"author_email") @property def tags(self): if self._tags is None: - self._tags = list(data_format.dict2iter('tag', self.mb_data)) + self._tags = list(data_format.dict2iter("tag", self.mb_data)) return self._tags @property def groups(self): if self._groups is None: - self._groups = list(data_format.dict2iter('group', self.mb_data)) + self._groups = list(data_format.dict2iter("group", self.mb_data)) return self._groups @property def title(self): - return self.mb_data.get(u'title') + return self.mb_data.get(u"title") @property def title_xhtml(self): try: - return safe(self.mb_data[u'title_xhtml']) + return safe(self.mb_data[u"title_xhtml"]) except KeyError: return None @property def content(self): - return self.mb_data.get(u'content') + return self.mb_data.get(u"content") @property def content_xhtml(self): try: - return safe(self.mb_data[u'content_xhtml']) + return safe(self.mb_data[u"content_xhtml"]) except KeyError: return None @property def comments(self): if self._comments is None: - self._comments = data_format.dict2iterdict(u'comments', self.mb_data, (u'node', u'service')) + self._comments = data_format.dict2iterdict( + u"comments", self.mb_data, (u"node", u"service") + ) return self._comments @property def comments_service(self): - return self.mb_data.get(u'comments_service') + return self.mb_data.get(u"comments_service") @property def comments_node(self): - return self.mb_data.get(u'comments_node') + return self.mb_data.get(u"comments_node") @property def comments_items_list(self): @@ -147,22 +147,21 @@ class BlogItems(object): - def __init__(self, mb_data): self.items = [BlogItem(i, self) for i in mb_data[0]] self.metadata = mb_data[1] @property def service(self): - return self.metadata[u'service'] + return self.metadata[u"service"] @property def node(self): - return self.metadata[u'node'] + return self.metadata[u"node"] @property def uri(self): - return self.metadata[u'uri'] + return self.metadata[u"uri"] def __len__(self): return self.items.__len__() @@ -184,7 +183,6 @@ class Message(object): - def __init__(self, msg_data): self._uid = msg_data[0] self._timestamp = msg_data[1] @@ -194,7 +192,7 @@ self._subject_data = msg_data[5] self._type = msg_data[6] self._extra = msg_data[7] - self._html = dict(data_format.getSubDict('xhtml', self._extra)) + self._html = dict(data_format.getSubDict("xhtml", self._extra)) @property def id(self): @@ -211,14 +209,14 @@ @property def text(self): try: - return self._message_data[''] + return self._message_data[""] except KeyError: return next(self._message_data.itervalues()) @property def subject(self): try: - return self._subject_data[''] + return self._subject_data[""] except KeyError: return next(self._subject_data.itervalues()) @@ -228,36 +226,35 @@ @property def thread(self): - return self._extra.get('thread') + return self._extra.get("thread") @property def thread_parent(self): - return self._extra.get('thread_parent') + return self._extra.get("thread_parent") @property def received(self): - return self._extra.get('received_timestamp', self._timestamp) + return self._extra.get("received_timestamp", self._timestamp) @property def delay_sender(self): - return self._extra.get('delay_sender') + return self._extra.get("delay_sender") @property def info_type(self): - return self._extra.get('info_type') + return self._extra.get("info_type") @property def html(self): if not self._html: return None try: - return safe(self._html['']) + return safe(self._html[""]) except KeyError: return safe(next(self._html.itervalues())) class Messages(object): - def __init__(self, msgs_data): self.messages = [Message(m) for m in msgs_data] @@ -279,8 +276,8 @@ def __contains__(self, item): return self.messages.__contains__(item) + class Room(object): - def __init__(self, jid, name=None, url=None): self.jid = jid self.name = name or jid @@ -289,7 +286,6 @@ class Identity(object): - def __init__(self, jid_str, data=None): self.jid_str = jid_str self.data = data if data is not None else {} @@ -305,7 +301,6 @@ class Identities(object): - def __init__(self): self.identities = {} @@ -352,5 +347,6 @@ values will be quoted before being used """ - return self.url.format(*[q(a) for a in args], - **{k: ObjectQuoter(v) for k,v in kwargs.iteritems()}) + return self.url.format( + *[q(a) for a in args], **{k: ObjectQuoter(v) for k, v in kwargs.iteritems()} + )
--- a/sat/tools/common/date_utils.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/tools/common/date_utils.py Wed Jun 27 20:14:46 2018 +0200 @@ -36,7 +36,15 @@ return calendar.timegm(dateutil_parser.parse(unicode(value)).utctimetuple()) -def date_fmt(timestamp, fmt='short', date_only=False, auto_limit=7, auto_old_fmt='short', auto_new_fmt='relative', locale_str=C.DEFAULT_LOCALE): +def date_fmt( + timestamp, + fmt="short", + date_only=False, + auto_limit=7, + auto_old_fmt="short", + auto_new_fmt="relative", + locale_str=C.DEFAULT_LOCALE, +): """format date according to locale @param timestamp(basestring, int): unix time @@ -63,9 +71,9 @@ than limit """ - if fmt == 'auto_day': - fmt, auto_limit, auto_old_fmt, auto_new_fmt = 'auto', 0, 'short', 'HH:mm' - if fmt == 'auto': + if fmt == "auto_day": + fmt, auto_limit, auto_old_fmt, auto_new_fmt = "auto", 0, "short", "HH:mm" + if fmt == "auto": if auto_limit == 0: today = time.mktime(datetime.date.today().timetuple()) if int(timestamp) < today: @@ -79,15 +87,17 @@ else: fmt = auto_new_fmt - if fmt == 'relative': + if fmt == "relative": delta = int(timestamp) - time.time() - return dates.format_timedelta(delta, granularity="minute", add_direction=True, locale=locale_str) - elif fmt in ('short', 'long'): + return dates.format_timedelta( + delta, granularity="minute", add_direction=True, locale=locale_str + ) + elif fmt in ("short", "long"): formatter = dates.format_date if date_only else dates.format_datetime return formatter(int(timestamp), format=fmt, locale=locale_str) - elif fmt == 'iso': + elif fmt == "iso": if date_only: - fmt = 'yyyy-MM-dd' + fmt = "yyyy-MM-dd" else: fmt = "yyyy-MM-ddTHH:mm:ss'Z'" return dates.format_datetime(int(timestamp), format=fmt)
--- a/sat/tools/common/dynamic_import.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/tools/common/dynamic_import.py Wed Jun 27 20:14:46 2018 +0200 @@ -22,7 +22,7 @@ from importlib import import_module -def bridge(name, module_path='sat.bridge'): +def bridge(name, module_path="sat.bridge"): """Import bridge module @param module_path(str): path of the module to import @@ -30,10 +30,10 @@ @return (module, None): imported module or None if nothing is found """ try: - bridge_module = import_module(module_path + '.' + name) + bridge_module = import_module(module_path + "." + name) except ImportError: try: - bridge_module = import_module(module_path + '.' + name + '_bridge') + bridge_module = import_module(module_path + "." + name + "_bridge") except ImportError: bridge_module = None return bridge_module
--- a/sat/tools/common/files_utils.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/tools/common/files_utils.py Wed Jun 27 20:14:46 2018 +0200 @@ -30,6 +30,6 @@ ori_path = path idx = 1 while os.path.exists(path): - path = ori_path + u'_' + unicode(idx) + path = ori_path + u"_" + unicode(idx) idx += 1 return path
--- a/sat/tools/common/regex.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/tools/common/regex.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,16 +20,17 @@ """ regex tools common to backend and frontends """ import re -path_escape = {'%': '%25', '/': '%2F', '\\': '%5c'} -path_escape_rev = {re.escape(v):k for k, v in path_escape.iteritems()} -path_escape = {re.escape(k):v for k, v in path_escape.iteritems()} -# thanks to Martijn Pieters (https://stackoverflow.com/a/14693789) -RE_ANSI_REMOVE = re.compile(r'\x1b[^m]*m') + +path_escape = {"%": "%25", "/": "%2F", "\\": "%5c"} +path_escape_rev = {re.escape(v): k for k, v in path_escape.iteritems()} +path_escape = {re.escape(k): v for k, v in path_escape.iteritems()} +# thanks to Martijn Pieters (https://stackoverflow.com/a/14693789) +RE_ANSI_REMOVE = re.compile(r"\x1b[^m]*m") def reJoin(exps): """Join (OR) various regexes""" - return re.compile('|'.join(exps)) + return re.compile("|".join(exps)) def reSubDict(pattern, repl_dict, string): @@ -42,6 +43,7 @@ """ return pattern.sub(lambda m: repl_dict[re.escape(m.group(0))], string) + path_escape_re = reJoin(path_escape.keys()) path_escape_rev_re = reJoin(path_escape_rev.keys()) @@ -63,10 +65,11 @@ """ return reSubDict(path_escape_rev_re, path_escape_rev, string) + def ansiRemove(string): """Remove ANSI escape codes from string @param string(basestr): string to filter @return (str, unicode): string without ANSI escape codes """ - return RE_ANSI_REMOVE.sub('', string) + return RE_ANSI_REMOVE.sub("", string)
--- a/sat/tools/common/template.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/tools/common/template.py Wed Jun 27 20:14:46 2018 +0200 @@ -24,6 +24,7 @@ from sat.core import exceptions from sat.tools.common import date_utils from sat.core.log import getLogger + log = getLogger(__name__) import os.path from xml.sax.saxutils import quoteattr @@ -35,30 +36,34 @@ import pygments from pygments import lexers from pygments import formatters + try: import sat_templates except ImportError: - raise exceptions.MissingModule(u'sat_templates module is not available, please install it or check your path to use template engine') + raise exceptions.MissingModule( + u"sat_templates module is not available, please install it or check your path to use template engine" + ) else: sat_templates # to avoid pyflakes warning try: import jinja2 except: - raise exceptions.MissingModule(u'Missing module jinja2, please install it from http://jinja.pocoo.org or with pip install jinja2') + raise exceptions.MissingModule( + u"Missing module jinja2, please install it from http://jinja.pocoo.org or with pip install jinja2" + ) from jinja2 import Markup as safe from jinja2 import is_undefined from lxml import etree -HTML_EXT = ('html', 'xhtml') -RE_ATTR_ESCAPE = re.compile(r'[^a-z_-]') -# TODO: handle external path (an additional search path for templates should be settable by user +HTML_EXT = ("html", "xhtml") +RE_ATTR_ESCAPE = re.compile(r"[^a-z_-]") +# TODO: handle external path (an additional search path for templates should be settable by user # TODO: handle absolute URL (should be used for trusted use cases) only (e.g. jp) for security reason class TemplateLoader(jinja2.FileSystemLoader): - def __init__(self): searchpath = os.path.dirname(sat_templates.__file__) super(TemplateLoader, self).__init__(searchpath, followlinks=True) @@ -72,20 +77,20 @@ relative path is the path from search path with theme specified e.g. default/blog/articles.html """ - if template.startswith(u'('): + if template.startswith(u"("): try: - theme_end = template.index(u')') + theme_end = template.index(u")") except IndexError: raise ValueError(u"incorrect theme in template") theme = template[1:theme_end] - template = template[theme_end+1:] - if not template or template.startswith(u'/'): + template = template[theme_end + 1 :] + if not template or template.startswith(u"/"): raise ValueError(u"incorrect path after template name") template = os.path.join(theme, template) - elif template.startswith(u'/'): + elif template.startswith(u"/"): # absolute path means no template theme = None - raise NotImplementedError(u'absolute path is not implemented yet') + raise NotImplementedError(u"absolute path is not implemented yet") else: theme = C.TEMPLATE_THEME_DEFAULT template = os.path.join(theme, template) @@ -99,11 +104,11 @@ @return (unicode, None): default path or None if there is not """ ext = os.path.splitext(template_path)[1][1:] - path_elems = template_path.split(u'/') + path_elems = template_path.split(u"/") if ext in HTML_EXT: - if path_elems[1] == u'error': + if path_elems[1] == u"error": # if an inexisting error page is requested, we return base page - default_path = os.path.join(theme, u'error/base.html') + default_path = os.path.join(theme, u"error/base.html") return default_path if theme != C.TEMPLATE_THEME_DEFAULT: # if template doesn't exists for this theme, we try with default @@ -124,7 +129,9 @@ if theme is not None: default_path = self.get_default_template(theme, template_path) if default_path is not None: - return super(TemplateLoader, self).get_source(environment, default_path) + return super(TemplateLoader, self).get_source( + environment, default_path + ) # if no default template is found, we re-raise the error raise e @@ -147,15 +154,14 @@ class ScriptsHandler(object): - def __init__(self, renderer, template_path, template_root_dir, root_path): self.renderer = renderer self.template_root_dir = template_root_dir self.root_path = root_path - self.scripts = [] # we don't use a set because order may be important + self.scripts = [] # we don't use a set because order may be important dummy, self.theme, self.is_default_theme = renderer.getThemeData(template_path) - def include(self, library_name, attribute='defer'): + def include(self, library_name, attribute="defer"): """Mark that a script need to be imported. Must be used before base.html is extended, as <script> are generated there. @@ -163,13 +169,15 @@ @param library_name(unicode): name of the library to import @param loading: """ - if attribute not in ('defer', 'async', ''): - raise exceptions.DataError(_(u'Invalid attribute, please use one of "defer", "async" or ""')) - if library_name.endswith('.js'): + if attribute not in ("defer", "async", ""): + raise exceptions.DataError( + _(u'Invalid attribute, please use one of "defer", "async" or ""') + ) + if library_name.endswith(".js"): library_name = library_name[:-3] if library_name not in self.scripts: self.scripts.append((library_name, attribute)) - return u'' + return u"" def generate_scripts(self): """Generate the <script> elements @@ -177,68 +185,72 @@ @return (unicode): <scripts> HTML tags """ scripts = [] - tpl = u'<script src={src} {attribute}></script>' + tpl = u"<script src={src} {attribute}></script>" for library, attribute in self.scripts: - path = self.renderer.getStaticPath(library, self.template_root_dir, self.theme, self.is_default_theme, '.js') + path = self.renderer.getStaticPath( + library, self.template_root_dir, self.theme, self.is_default_theme, ".js" + ) if path is None: log.warning(_(u"Can't find {}.js javascript library").format(library)) continue path = os.path.join(self.root_path, path) - scripts.append(tpl.format( - src = quoteattr(path), - attribute = attribute, - )) - return safe(u'\n'.join(scripts)) + scripts.append(tpl.format(src=quoteattr(path), attribute=attribute)) + return safe(u"\n".join(scripts)) class Renderer(object): - def __init__(self, host): self.host = host - self.base_dir = os.path.dirname(sat_templates.__file__) # FIXME: should be modified if we handle use extra dirs + self.base_dir = os.path.dirname( + sat_templates.__file__ + ) # FIXME: should be modified if we handle use extra dirs self.env = jinja2.Environment( loader=TemplateLoader(), - autoescape=jinja2.select_autoescape(['html', 'xhtml', 'xml']), + autoescape=jinja2.select_autoescape(["html", "xhtml", "xml"]), trim_blocks=True, lstrip_blocks=True, - extensions=['jinja2.ext.i18n'], - ) + extensions=["jinja2.ext.i18n"], + ) self._locale_str = C.DEFAULT_LOCALE self._locale = Locale.parse(self._locale_str) self.installTranslations() # we want to have access to SàT constants in templates - self.env.globals[u'C'] = C + self.env.globals[u"C"] = C # custom filters - self.env.filters['next_gidx'] = self._next_gidx - self.env.filters['cur_gidx'] = self._cur_gidx - self.env.filters['date_fmt'] = self._date_fmt - self.env.filters['xmlui_class'] = self._xmlui_class - self.env.filters['attr_escape'] = self.attr_escape - self.env.filters['item_filter'] = self._item_filter - self.env.filters['adv_format'] = self._adv_format - self.env.filters['dict_ext'] = self._dict_ext - self.env.filters['highlight'] = self.highlight + self.env.filters["next_gidx"] = self._next_gidx + self.env.filters["cur_gidx"] = self._cur_gidx + self.env.filters["date_fmt"] = self._date_fmt + self.env.filters["xmlui_class"] = self._xmlui_class + self.env.filters["attr_escape"] = self.attr_escape + self.env.filters["item_filter"] = self._item_filter + self.env.filters["adv_format"] = self._adv_format + self.env.filters["dict_ext"] = self._dict_ext + self.env.filters["highlight"] = self.highlight # custom tests - self.env.tests['in_the_past'] = self._in_the_past - self.icons_path = os.path.join(host.media_dir, u'fonts/fontello/svg') + self.env.tests["in_the_past"] = self._in_the_past + self.icons_path = os.path.join(host.media_dir, u"fonts/fontello/svg") def installTranslations(self): - i18n_dir = os.path.join(self.base_dir, 'i18n') + i18n_dir = os.path.join(self.base_dir, "i18n") self.translations = {} for lang_dir in os.listdir(i18n_dir): lang_path = os.path.join(i18n_dir, lang_dir) if not os.path.isdir(lang_path): continue - po_path = os.path.join(lang_path, 'LC_MESSAGES/sat.mo') + po_path = os.path.join(lang_path, "LC_MESSAGES/sat.mo") try: - with open(po_path, 'rb') as f: - self.translations[Locale.parse(lang_dir)] = support.Translations(f, 'sat') + with open(po_path, "rb") as f: + self.translations[Locale.parse(lang_dir)] = support.Translations( + f, "sat" + ) except EnvironmentError: - log.error(_(u"Can't find template translation at {path}").format(path = po_path)) + log.error( + _(u"Can't find template translation at {path}").format(path=po_path) + ) except UnknownLocaleError as e: log.error(_(u"Invalid locale name: {msg}").format(msg=e)) else: - log.info(_(u'loaded {lang} templates translations').format(lang=lang_dir)) + log.info(_(u"loaded {lang} templates translations").format(lang=lang_dir)) self.env.install_null_translations(True) def setLocale(self, locale_str): @@ -248,10 +260,10 @@ """ if locale_str == self._locale_str: return - if locale_str == 'en': + if locale_str == "en": # we default to GB English when it's not specified # one of the main reason is to avoid the nonsense U.S. short date format - locale_str = 'en_GB' + locale_str = "en_GB" try: locale = Locale.parse(locale_str) except ValueError as e: @@ -269,7 +281,7 @@ locale = Locale.parse(self._locale_str) else: self.env.install_gettext_translations(translations, True) - log.debug(_(u'Switched to {lang}').format(lang=locale.english_name)) + log.debug(_(u"Switched to {lang}").format(lang=locale.english_name)) if locale_str == C.DEFAULT_LOCALE: self.env.install_null_translations(True) @@ -286,7 +298,7 @@ theme, dummy = self.env.loader.parse_template(template) return theme, os.path.join(self.base_dir, theme) - def getStaticPath(self, name, template_root_dir, theme, is_default, ext='.css'): + def getStaticPath(self, name, template_root_dir, theme, is_default, ext=".css"): """retrieve path of a static file if it exists with current theme or default File will be looked at [theme]/static/[name][ext], and then default @@ -302,7 +314,9 @@ if os.path.exists(os.path.join(template_root_dir, path)): file_ = path elif not is_default: - path = os.path.join(C.TEMPLATE_THEME_DEFAULT, C.TEMPLATE_STATIC_DIR, name + ext) + path = os.path.join( + C.TEMPLATE_THEME_DEFAULT, C.TEMPLATE_STATIC_DIR, name + ext + ) if os.path.exists(os.path.join(template_root_dir, path)): file_.append(path) return file_ @@ -315,7 +329,7 @@ theme: theme of the page is_default: True if the theme is the default theme """ - path_elems = [os.path.splitext(p)[0] for p in template_path.split(u'/')] + path_elems = [os.path.splitext(p)[0] for p in template_path.split(u"/")] theme = path_elems.pop(0) is_default = theme == C.TEMPLATE_THEME_DEFAULT return (path_elems, theme, is_default) @@ -336,39 +350,44 @@ # TODO: some caching would be nice css_files = [] path_elems, theme, is_default = self.getThemeData(template_path) - for css in (u'fonts', u'styles'): + for css in (u"fonts", u"styles"): css_path = self.getStaticPath(css, template_root_dir, theme, is_default) if css_path is not None: css_files.append(css_path) for idx, path in enumerate(path_elems): - css_path = self.getStaticPath(u'_'.join(path_elems[:idx+1]), template_root_dir, theme, is_default) + css_path = self.getStaticPath( + u"_".join(path_elems[: idx + 1]), template_root_dir, theme, is_default + ) if css_path is not None: css_files.append(css_path) return css_files - ## custom filters ## @jinja2.contextfilter def _next_gidx(self, ctx, value): """Use next current global index as suffix""" - next_ = ctx['gidx'].next(value) + next_ = ctx["gidx"].next(value) return value if next_ == 0 else u"{}_{}".format(value, next_) @jinja2.contextfilter def _cur_gidx(self, ctx, value): """Use current current global index as suffix""" - current = ctx['gidx'].current(value) + current = ctx["gidx"].current(value) return value if not current else u"{}_{}".format(value, current) - def _date_fmt(self, timestamp, fmt='short', date_only=False, auto_limit=None, auto_old_fmt=None): + def _date_fmt( + self, timestamp, fmt="short", date_only=False, auto_limit=None, auto_old_fmt=None + ): if is_undefined(fmt): - fmt = u'short' + fmt = u"short" try: - return date_utils.date_fmt(timestamp, fmt, date_only, auto_limit, auto_old_fmt) + return date_utils.date_fmt( + timestamp, fmt, date_only, auto_limit, auto_old_fmt + ) except Exception as e: log.warning(_(u"Can't parse date: {msg}").format(msg=e)) return timestamp @@ -378,7 +397,7 @@ remove spaces, and put in lower case """ - return RE_ATTR_ESCAPE.sub(u'_', text.strip().lower())[:50] + return RE_ATTR_ESCAPE.sub(u"_", text.strip().lower())[:50] def _xmlui_class(self, xmlui_item, fields): """return classes computed from XMLUI fields name @@ -394,11 +413,13 @@ escaped_name = self.attr_escape(name) try: for value in xmlui_item.widgets[name].values: - classes.append(escaped_name + '_' + self.attr_escape(value)) + classes.append(escaped_name + "_" + self.attr_escape(value)) except KeyError: - log.debug(_(u"ignoring field \"{name}\": it doesn't exists").format(name=name)) + log.debug( + _(u'ignoring field "{name}": it doesn\'t exists').format(name=name) + ) continue - return u' '.join(classes) or None + return u" ".join(classes) or None @jinja2.contextfilter def _item_filter(self, ctx, item, filters): @@ -420,8 +441,8 @@ if filter_ is None: return value elif isinstance(filter_, dict): - filters_args = filter_.get(u'filters_args') - for idx, f_name in enumerate(filter_.get(u'filters', [])): + filters_args = filter_.get(u"filters_args") + for idx, f_name in enumerate(filter_.get(u"filters", [])): kwargs = filters_args[idx] if filters_args is not None else {} filter_func = self.env.filters[f_name] try: @@ -433,7 +454,7 @@ value = filter_func(ctx.eval_ctx, value, **kwargs) else: value = filter_func(value, **kwargs) - template = filter_.get(u'template') + template = filter_.get(u"template") if template: # format will return a string, so we need to check first # if the value is safe or not, and re-mark it after formatting @@ -455,7 +476,7 @@ """ if template is None: return value - # jinja use string when no special char is used, so we have to convert to unicode + # jinja use string when no special char is used, so we have to convert to unicode return unicode(template).format(value=value, **kwargs) def _dict_ext(self, source_dict, extra_dict, key=None): @@ -511,29 +532,45 @@ def _icon_defs(self, *names): """Define svg icons which will be used in the template, and use their name as id""" - svg_elt = etree.Element('svg', nsmap={None: 'http://www.w3.org/2000/svg'}, - width='0', height='0', style='display: block' - ) - defs_elt = etree.SubElement(svg_elt, 'defs') + svg_elt = etree.Element( + "svg", + nsmap={None: "http://www.w3.org/2000/svg"}, + width="0", + height="0", + style="display: block", + ) + defs_elt = etree.SubElement(svg_elt, "defs") for name in names: - path = os.path.join(self.icons_path, name + u'.svg') + path = os.path.join(self.icons_path, name + u".svg") icon_svg_elt = etree.parse(path).getroot() # we use icon name as id, so we can retrieve them easily - icon_svg_elt.set('id', name) - if not icon_svg_elt.tag == '{http://www.w3.org/2000/svg}svg': - raise exceptions.DataError(u'invalid SVG element') + icon_svg_elt.set("id", name) + if not icon_svg_elt.tag == "{http://www.w3.org/2000/svg}svg": + raise exceptions.DataError(u"invalid SVG element") defs_elt.append(icon_svg_elt) - return safe(etree.tostring(svg_elt, encoding='unicode')) + return safe(etree.tostring(svg_elt, encoding="unicode")) - def _icon_use(self, name, cls=''): - return safe(u"""<svg class="svg-icon{cls}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> + def _icon_use(self, name, cls=""): + return safe( + u"""<svg class="svg-icon{cls}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> <use href="#{name}"/> </svg> """.format( - name=name, - cls=(' ' + cls) if cls else '')) + name=name, cls=(" " + cls) if cls else "" + ) + ) - def render(self, template, theme=None, locale=C.DEFAULT_LOCALE, root_path=u'', media_path=u'', css_files=None, css_inline=False, **kwargs): + def render( + self, + template, + theme=None, + locale=C.DEFAULT_LOCALE, + root_path=u"", + media_path=u"", + css_files=None, + css_inline=False, + **kwargs + ): """render a template . @param template(unicode): template to render (e.g. blog/articles.html) @@ -553,24 +590,28 @@ raise ValueError(u"template can't be empty") if theme is not None: # use want to set a theme, we add it to the template path - if template[0] == u'(': - raise ValueError(u"you can't specify theme in template path and in argument at the same time") - elif template[0] == u'/': + if template[0] == u"(": + raise ValueError( + u"you can't specify theme in template path and in argument at the same time" + ) + elif template[0] == u"/": raise ValueError(u"you can't specify theme with absolute paths") - template= u'(' + theme + u')' + template + template = u"(" + theme + u")" + template else: theme, dummy = self.env.loader.parse_template(template) template_source = self.env.get_template(template) - template_root_dir = os.path.normpath(self.base_dir) # FIXME: should be modified if we handle use extra dirs - # XXX: template_path may have a different theme as first element than theme if a default page is used - template_path = template_source.filename[len(template_root_dir)+1:] + template_root_dir = os.path.normpath( + self.base_dir + ) # FIXME: should be modified if we handle use extra dirs + # XXX: template_path may have a different theme as first element than theme if a default page is used + template_path = template_source.filename[len(template_root_dir) + 1 :] if css_files is None: css_files = self.getCSSFiles(template_path, template_root_dir) - kwargs['icon_defs'] = self._icon_defs - kwargs['icon'] = self._icon_use + kwargs["icon_defs"] = self._icon_defs + kwargs["icon"] = self._icon_use if css_inline: css_contents = [] @@ -579,13 +620,21 @@ with open(css_file_path) as f: css_contents.append(f.read()) if css_contents: - kwargs['css_content'] = '\n'.join(css_contents) + kwargs["css_content"] = "\n".join(css_contents) - scripts_handler = ScriptsHandler(self, template_path, template_root_dir, root_path) + scripts_handler = ScriptsHandler( + self, template_path, template_root_dir, root_path + ) self.setLocale(locale) # XXX: theme used in template arguments is the requested theme, which may differ from actual theme # if the template doesn't exist in the requested theme. - return template_source.render(theme=theme, root_path=root_path, media_path=media_path, - css_files=css_files, locale=self._locale, - gidx=Indexer(), script=scripts_handler, - **kwargs) + return template_source.render( + theme=theme, + root_path=root_path, + media_path=media_path, + css_files=css_files, + locale=self._locale, + gidx=Indexer(), + script=scripts_handler, + **kwargs + )
--- a/sat/tools/common/template_xmlui.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/tools/common/template_xmlui.py Wed Jun 27 20:14:46 2018 +0200 @@ -23,14 +23,16 @@ """ from sat.core.log import getLogger + log = getLogger(__name__) from sat_frontends.tools import xmlui ## Widgets ## + class Widget(object): - category = u'widget' + category = u"widget" type = None enabled = True read_only = True @@ -44,7 +46,6 @@ class ValueWidget(Widget): - def __init__(self, xmlui_parent, value): super(ValueWidget, self).__init__(xmlui_parent) self.value = value @@ -55,21 +56,19 @@ @property def labels(self): - # helper property, there is not label for ValueWidget + # helper property, there is not label for ValueWidget # but using labels make rendering more easy (one single method to call) - # values are actually returned + # values are actually returned return [self.value] class InputWidget(ValueWidget): - def __init__(self, xmlui_parent, value, read_only=False): super(InputWidget, self).__init__(xmlui_parent, value) self.read_only = read_only class OptionsWidget(Widget): - def __init__(self, xmlui_parent, options, selected, style): super(OptionsWidget, self).__init__(xmlui_parent) self.options = options @@ -91,21 +90,20 @@ def items(self): """return suitable items, according to style""" no_select = self.no_select - for value,label in self.options: + for value, label in self.options: if no_select or value in self.selected: - yield value,label + yield value, label @property def inline(self): - return u'inline' in self.style + return u"inline" in self.style @property def no_select(self): - return u'noselect' in self.style + return u"noselect" in self.style class EmptyWidget(xmlui.EmptyWidget, Widget): - def __init__(self, _xmlui_parent): Widget.__init__(self) @@ -143,8 +141,9 @@ ## Containers ## + class Container(object): - category = u'container' + category = u"container" type = None def __init__(self, xmlui_parent): @@ -162,26 +161,27 @@ class VerticalContainer(xmlui.VerticalContainer, Container): - type = u'vertical' + type = u"vertical" class PairsContainer(xmlui.PairsContainer, Container): - type = u'pairs' + type = u"pairs" class LabelContainer(xmlui.PairsContainer, Container): - type = u'label' + type = u"label" ## Factory ## + class WidgetFactory(object): - def __getattr__(self, attr): if attr.startswith("create"): cls = globals()[attr[6:]] return cls + ## Core ##
--- a/sat/tools/common/uri.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/tools/common/uri.py Wed Jun 27 20:14:46 2018 +0200 @@ -24,6 +24,7 @@ # FIXME: basic implementation, need to follow RFC 5122 + def parseXMPPUri(uri): """Parse an XMPP uri and return a dict with various information @@ -39,57 +40,64 @@ id: id of the element (item for pubsub) @raise ValueError: the scheme is not xmpp """ - uri_split = urlparse.urlsplit(uri.encode('utf-8')) - if uri_split.scheme != 'xmpp': - raise ValueError(u'this is not a XMPP URI') + uri_split = urlparse.urlsplit(uri.encode("utf-8")) + if uri_split.scheme != "xmpp": + raise ValueError(u"this is not a XMPP URI") # XXX: we don't use jid.JID for path as it can be used both in backend and frontend # which may use different JID classes - data = {u'path': urllib.unquote(uri_split.path).decode('utf-8')} + data = {u"path": urllib.unquote(uri_split.path).decode("utf-8")} - query_end = uri_split.query.find(';') + query_end = uri_split.query.find(";") query_type = uri_split.query[:query_end] - if query_end == -1 or '=' in query_type: - raise ValueError('no query type, invalid XMPP URI') + if query_end == -1 or "=" in query_type: + raise ValueError("no query type, invalid XMPP URI") pairs = urlparse.parse_qs(uri_split.geturl()) for k, v in pairs.items(): if len(v) != 1: raise NotImplementedError(u"multiple values not managed") - if k in ('path', 'type', 'sub_type'): + if k in ("path", "type", "sub_type"): raise NotImplementedError(u"reserved key used in URI, this is not supported") - data[k.decode('utf-8')] = urllib.unquote(v[0]).decode('utf-8') + data[k.decode("utf-8")] = urllib.unquote(v[0]).decode("utf-8") if query_type: - data[u'type'] = query_type.decode('utf-8') - elif u'node' in data: - data[u'type'] = u'pubsub' + data[u"type"] = query_type.decode("utf-8") + elif u"node" in data: + data[u"type"] = u"pubsub" else: - data[u'type'] = '' + data[u"type"] = "" - if u'node' in data: - if data[u'node'].startswith(u'urn:xmpp:microblog:'): - data[u'sub_type'] = 'microblog' + if u"node" in data: + if data[u"node"].startswith(u"urn:xmpp:microblog:"): + data[u"sub_type"] = "microblog" return data + def addPairs(uri, pairs): - for k,v in pairs.iteritems(): - uri.append(u';' + urllib.quote_plus(k.encode('utf-8')) + u'=' + urllib.quote_plus(v.encode('utf-8'))) + for k, v in pairs.iteritems(): + uri.append( + u";" + + urllib.quote_plus(k.encode("utf-8")) + + u"=" + + urllib.quote_plus(v.encode("utf-8")) + ) + def buildXMPPUri(type_, **kwargs): - uri = [u'xmpp:'] - subtype = kwargs.pop('subtype', None) - path = kwargs.pop('path') - uri.append(urllib.quote_plus(path.encode('utf-8')).replace(u'%40', '@')) + uri = [u"xmpp:"] + subtype = kwargs.pop("subtype", None) + path = kwargs.pop("path") + uri.append(urllib.quote_plus(path.encode("utf-8")).replace(u"%40", "@")) - if type_ == u'pubsub': - if subtype == 'microblog' and not kwargs.get('node'): - kwargs[u'node'] = 'urn:xmpp:microblog:0' + if type_ == u"pubsub": + if subtype == "microblog" and not kwargs.get("node"): + kwargs[u"node"] = "urn:xmpp:microblog:0" if kwargs: - uri.append(u'?') + uri.append(u"?") addPairs(uri, kwargs) else: - raise NotImplementedError(u'{type_} URI are not handled yet'.format(type_=type_)) + raise NotImplementedError(u"{type_} URI are not handled yet".format(type_=type_)) - return u''.join(uri) + return u"".join(uri)
--- a/sat/tools/config.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/tools/config.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ """ Configuration related useful methods """ from sat.core.log import getLogger + log = getLogger(__name__) from sat.core.constants import Const as C @@ -50,23 +51,28 @@ if not silent: log.debug(_(u"Testing file %s") % file_) if os.path.isfile(file_): - if file_.startswith(os.path.expanduser('~')): + if file_.startswith(os.path.expanduser("~")): config.read([file_]) target_file = file_ break if not target_file: # ... otherwise we create a new config file for that user - target_file = BaseDirectory.save_config_path('sat') + '/sat.conf' + target_file = BaseDirectory.save_config_path("sat") + "/sat.conf" if section and section.upper() != DEFAULTSECT and not config.has_section(section): config.add_section(section) config.set(section, option, value) - with open(target_file, 'wb') as configfile: + with open(target_file, "wb") as configfile: config.write(configfile) # for the next time that user launches sat if not silent: - if option in ('passphrase',): # list here the options storing a password - value = '******' - log.warning(_(u"Config auto-update: %(option)s set to %(value)s in the file %(config_file)s") % - {'option': option, 'value': value, 'config_file': target_file}) + if option in ("passphrase",): # list here the options storing a password + value = "******" + log.warning( + _( + u"Config auto-update: %(option)s set to %(value)s in the file %(config_file)s" + ) + % {"option": option, "value": value, "config_file": target_file} + ) + def parseMainConf(): """look for main .ini configuration file, and parse it""" @@ -77,6 +83,7 @@ log.error(_("Can't read main config !")) return config + def getConfig(config, section, name, default=None): """Get a configuration option @@ -93,25 +100,29 @@ section = DEFAULTSECT try: - value = config.get(section, name).decode('utf-8') + value = config.get(section, name).decode("utf-8") except (NoOptionError, NoSectionError) as e: if default is Exception: raise e return default - if name.endswith('_path') or name.endswith('_dir'): + if name.endswith("_path") or name.endswith("_dir"): value = os.path.expanduser(value) # thx to Brian (http://stackoverflow.com/questions/186857/splitting-a-semicolon-separated-string-to-a-dictionary-in-python/186873#186873) - elif name.endswith('_list'): - value = csv.reader([value], delimiter=',', quotechar='"', skipinitialspace=True).next() - elif name.endswith('_dict'): + elif name.endswith("_list"): + value = csv.reader( + [value], delimiter=",", quotechar='"', skipinitialspace=True + ).next() + elif name.endswith("_dict"): try: value = json.loads(value) except ValueError as e: raise exceptions.ParsingError(u"Error while parsing data: {}".format(e)) if not isinstance(value, dict): - raise exceptions.ParsingError(u"{name} value is not a dict: {value}".format(name=name, value=value)) - elif name.endswith('_json'): + raise exceptions.ParsingError( + u"{name} value is not a dict: {value}".format(name=name, value=value) + ) + elif name.endswith("_json"): try: value = json.loads(value) except ValueError as e:
--- a/sat/tools/email.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/tools/email.py Wed Jun 27 20:14:46 2018 +0200 @@ -22,13 +22,13 @@ from __future__ import absolute_import from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.mail import smtp from email.mime.text import MIMEText - -def sendEmail(host, to_emails, subject=u'', body=u'', from_email=None): +def sendEmail(host, to_emails, subject=u"", body=u"", from_email=None): """send an email using SàT configuration @param to_emails(list[unicode], unicode): list of recipients @@ -40,31 +40,35 @@ """ if isinstance(to_emails, basestring): to_emails = to_emails.split() - email_host = host.memory.getConfig(None, u'email_server') or u'localhost' - email_from = host.memory.getConfig(None, u'email_from') + email_host = host.memory.getConfig(None, u"email_server") or u"localhost" + email_from = host.memory.getConfig(None, u"email_from") if email_from is None: # we suppose that email domain and XMPP domain are identical - domain = host.memory.getConfig(None, u'xmpp_domain', u'example.net') - email_from = u'no_reply@' + domain - email_sender_domain = host.memory.getConfig(None, u'email_sender_domain') - email_port = int(host.memory.getConfig(None, u'email_port', 25)) - email_username = host.memory.getConfig(None, u'email_username') - email_password = host.memory.getConfig(None, u'email_password') - email_auth = C.bool(host.memory.getConfig(None, 'email_auth', False)) - email_starttls = C.bool(host.memory.getConfig(None, 'email_starttls', False)) + domain = host.memory.getConfig(None, u"xmpp_domain", u"example.net") + email_from = u"no_reply@" + domain + email_sender_domain = host.memory.getConfig(None, u"email_sender_domain") + email_port = int(host.memory.getConfig(None, u"email_port", 25)) + email_username = host.memory.getConfig(None, u"email_username") + email_password = host.memory.getConfig(None, u"email_password") + email_auth = C.bool(host.memory.getConfig(None, "email_auth", False)) + email_starttls = C.bool(host.memory.getConfig(None, "email_starttls", False)) + + msg = MIMEText(body, "plain", "UTF-8") + msg[u"Subject"] = subject + msg[u"From"] = email_from + msg[u"To"] = u", ".join(to_emails) - msg = MIMEText(body, 'plain', 'UTF-8') - msg[u'Subject'] = subject - msg[u'From'] = email_from - msg[u'To'] = u", ".join(to_emails) - - return smtp.sendmail(email_host.encode("utf-8"), - email_from.encode("utf-8"), - [email.encode("utf-8") for email in to_emails], - msg.as_string(), - senderDomainName = email_sender_domain.encode("utf-8") if email_sender_domain else None, - port = email_port, - username = email_username.encode("utf-8") if email_username else None, - password = email_password.encode("utf-8") if email_password else None, - requireAuthentication = email_auth, - requireTransportSecurity = email_starttls) + return smtp.sendmail( + email_host.encode("utf-8"), + email_from.encode("utf-8"), + [email.encode("utf-8") for email in to_emails], + msg.as_string(), + senderDomainName=email_sender_domain.encode("utf-8") + if email_sender_domain + else None, + port=email_port, + username=email_username.encode("utf-8") if email_username else None, + password=email_password.encode("utf-8") if email_password else None, + requireAuthentication=email_auth, + requireTransportSecurity=email_starttls, + )
--- a/sat/tools/sat_defer.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/tools/sat_defer.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ """tools related to deferred""" from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from twisted.internet import defer @@ -29,8 +30,8 @@ from sat.core.constants import Const as C from sat.memory import memory -KEY_DEFERREDS = 'deferreds' -KEY_NEXT = 'next_defer' +KEY_DEFERREDS = "deferreds" +KEY_NEXT = "next_defer" class DelayedDeferred(object): @@ -52,19 +53,19 @@ self._deferred.cancel() def addCallbacks(self, *args, **kwargs): - self._deferred.addCallbacks(*args,**kwargs) + self._deferred.addCallbacks(*args, **kwargs) def addCallback(self, *args, **kwargs): - self._deferred.addCallback(*args,**kwargs) + self._deferred.addCallback(*args, **kwargs) def addErrback(self, *args, **kwargs): - self._deferred.addErrback(*args,**kwargs) + self._deferred.addErrback(*args, **kwargs) def addBoth(self, *args, **kwargs): - self._deferred.addBoth(*args,**kwargs) + self._deferred.addBoth(*args, **kwargs) def chainDeferred(self, *args, **kwargs): - self._deferred.chainDeferred(*args,**kwargs) + self._deferred.chainDeferred(*args, **kwargs) def pause(self): self._deferred.pause() @@ -76,13 +77,14 @@ class RTDeferredSessions(memory.Sessions): """Real Time Deferred Sessions""" - def __init__(self, timeout=120): """Manage list of Deferreds in real-time, allowing to get intermediate results @param timeout (int): nb of seconds before deferreds cancellation """ - super(RTDeferredSessions, self).__init__(timeout=timeout, resettable_timeout=False) + super(RTDeferredSessions, self).__init__( + timeout=timeout, resettable_timeout=False + ) def newSession(self, deferreds, profile): """Launch a new session with a list of deferreds @@ -92,7 +94,9 @@ @param return (tupe[str, defer.Deferred]): tuple with session id and a deferred wich fire *WITHOUT RESULT* when all results are received """ data = {KEY_NEXT: defer.Deferred()} - session_id, session_data = super(RTDeferredSessions, self).newSession(data, profile=profile) + session_id, session_data = super(RTDeferredSessions, self).newSession( + data, profile=profile + ) if isinstance(deferreds, dict): session_data[KEY_DEFERREDS] = deferreds.values() iterator = deferreds.iteritems() @@ -107,7 +111,9 @@ d.addErrback(self._errback, d, session_id, profile) return session_id - def _purgeSession(self, session_id, reason=u"timeout", no_warning=False, got_result=False): + def _purgeSession( + self, session_id, reason=u"timeout", no_warning=False, got_result=False + ): """Purge the session @param session_id(str): id of the session to purge @@ -121,7 +127,9 @@ try: timer, session_data, profile = self._sessions[session_id] except ValueError: - raise exceptions.InternalError(u'was expecting timer, session_data and profile; is profile set ?') + raise exceptions.InternalError( + u"was expecting timer, session_data and profile; is profile set ?" + ) # next_defer must be called before deferreds, # else its callback will be called by _gotResult @@ -134,7 +142,9 @@ d.cancel() if not no_warning: - log.warning(u"RTDeferredList cancelled: {} (profile {})".format(reason, profile)) + log.warning( + u"RTDeferredList cancelled: {} (profile {})".format(reason, profile) + ) super(RTDeferredSessions, self)._purgeSession(session_id) @@ -165,7 +175,9 @@ """ self._purgeSession(session_id, reason=reason, no_warning=no_log) - def getResults(self, session_id, on_success=None, on_error=None, profile=C.PROF_KEY_NONE): + def getResults( + self, session_id, on_success=None, on_error=None, profile=C.PROF_KEY_NONE + ): """Get current results of a real-time deferred session result already gotten are deleted @@ -197,11 +209,13 @@ def next_cb(dummy): # we got one or several results results = {} - filtered_data = [] # used to keep deferreds without results + filtered_data = [] # used to keep deferreds without results deferreds = session_data[KEY_DEFERREDS] for d in deferreds: - if d._RTDeferred_return: # we don't use d.called as called is True before the full callbacks chain has been called + if ( + d._RTDeferred_return + ): # we don't use d.called as called is True before the full callbacks chain has been called # we have a result idx = d._RTDeferred_index success, result = d._RTDeferred_return @@ -210,7 +224,9 @@ if callable(on_success): result = yield on_success(result) else: - raise exceptions.InternalError('Unknown value of on_success: {}'.format(on_success)) + raise exceptions.InternalError( + "Unknown value of on_success: {}".format(on_success) + ) else: if on_error is not None: @@ -219,7 +235,9 @@ elif callable(on_error): result = yield on_error(result) else: - raise exceptions.InternalError('Unknown value of on_error: {}'.format(on_error)) + raise exceptions.InternalError( + "Unknown value of on_error: {}".format(on_error) + ) results[idx] = (success, result) else: filtered_data.append(d)
--- a/sat/tools/stream.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/tools/stream.py Wed Jun 27 20:14:46 2018 +0200 @@ -32,7 +32,6 @@ class IStreamProducer(interface.Interface): - def startStream(consumer): """start producing the stream @@ -42,9 +41,20 @@ class SatFile(object): """A file-like object to have high level files manipulation""" + # TODO: manage "with" statement - def __init__(self, host, client, path, mode='rb', uid=None, size=None, data_cb=None, auto_end_signals=True): + def __init__( + self, + host, + client, + path, + mode="rb", + uid=None, + size=None, + data_cb=None, + auto_end_signals=True, + ): """ @param host: %(doc_host)s @param path(str): path of the file to get @@ -67,7 +77,9 @@ self.data_cb = data_cb self.auto_end_signals = auto_end_signals metadata = self.getProgressMetadata() - self.host.registerProgressCb(self.uid, self.getProgress, metadata, profile=client.profile) + self.host.registerProgressCb( + self.uid, self.getProgress, metadata, profile=client.profile + ) self.host.bridge.progressStarted(self.uid, metadata, client.profile) def checkSize(self): @@ -91,14 +103,14 @@ error can happen even if error is None, if current size differ from given size """ if self._file.closed: - return # avoid double close (which is allowed) error + return # avoid double close (which is allowed) error if error is None: try: size_ok = self.checkSize() except exceptions.NotFound: size_ok = True if not size_ok: - error = u'declared and actual size mismatch' + error = u"declared and actual size mismatch" log.warning(error) progress_metadata = None @@ -149,35 +161,34 @@ @return (dict): metadata (check bridge for documentation) """ - metadata = {'type': C.META_TYPE_FILE} + metadata = {"type": C.META_TYPE_FILE} mode = self._file.mode - if '+' in mode: - pass # we have no direction in read/write modes - elif mode in ('r', 'rb'): - metadata['direction'] = 'out' - elif mode in ('w', 'wb'): - metadata['direction'] = 'in' - elif 'U' in mode: - metadata['direction'] = 'out' + if "+" in mode: + pass # we have no direction in read/write modes + elif mode in ("r", "rb"): + metadata["direction"] = "out" + elif mode in ("w", "wb"): + metadata["direction"] = "in" + elif "U" in mode: + metadata["direction"] = "out" else: raise exceptions.InternalError - metadata['name'] = self._file.name + metadata["name"] = self._file.name return metadata def getProgress(self, progress_id, profile): - ret = {'position': self._file.tell()} + ret = {"position": self._file.tell()} if self.size: - ret['size'] = self.size + ret["size"] = self.size return ret @interface.implementer(IStreamProducer) @interface.implementer(interfaces.IConsumer) class FileStreamObject(basic.FileSender): - def __init__(self, host, client, path, **kwargs): """
--- a/sat/tools/trigger.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/tools/trigger.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat.core.i18n import _ from sat.core.log import getLogger + log = getLogger(__name__) @@ -30,6 +31,7 @@ class SkipOtherTriggers(Exception): """ Exception to raise if normal behaviour must be followed instead of following triggers list """ + pass @@ -37,8 +39,8 @@ """This class manage triggers: code which interact to change the behaviour of SàT""" try: # FIXME: to be removed when a better solution is found - MIN_PRIORITY = float('-inf') - MAX_PRIORITY = float('+inf') + MIN_PRIORITY = float("-inf") + MAX_PRIORITY = float("+inf") except: # XXX: Pyjamas will bug if you specify ValueError here # Pyjamas uses the JS Float class MIN_PRIORITY = Number.NEGATIVE_INFINITY @@ -57,14 +59,20 @@ """ if point_name not in self.__triggers: self.__triggers[point_name] = [] - if priority != 0 and priority in [trigger_tuple[0] for trigger_tuple in self.__triggers[point_name]]: + if priority != 0 and priority in [ + trigger_tuple[0] for trigger_tuple in self.__triggers[point_name] + ]: if priority in (self.MIN_PRIORITY, self.MAX_PRIORITY): log.warning(_(u"There is already a bound priority [%s]") % point_name) else: - log.debug(_(u"There is already a trigger with the same priority [%s]") % point_name) + log.debug( + _(u"There is already a trigger with the same priority [%s]") + % point_name + ) self.__triggers[point_name].append((priority, callback)) - self.__triggers[point_name].sort(key=lambda trigger_tuple: - trigger_tuple[0], reverse=True) + self.__triggers[point_name].sort( + key=lambda trigger_tuple: trigger_tuple[0], reverse=True + ) def remove(self, point_name, callback): """Remove a trigger from a point
--- a/sat/tools/utils.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/tools/utils.py Wed Jun 27 20:14:46 2018 +0200 @@ -23,6 +23,7 @@ import os.path from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) import datetime from twisted.python import procutils @@ -35,7 +36,7 @@ import functools -NO_REPOS_DATA = u'repository data unknown' +NO_REPOS_DATA = u"repository data unknown" repos_cache_dict = None repos_cache = None @@ -45,12 +46,15 @@ remove special characters from unicode string """ + def valid_chars(unicode_source): for char in unicode_source: - if unicodedata.category(char) == 'Cc' and char!='\n': + if unicodedata.category(char) == "Cc" and char != "\n": continue yield char - return ''.join(valid_chars(ustr)) + + return "".join(valid_chars(ustr)) + def partial(func, *fixed_args, **fixed_kwargs): # FIXME: temporary hack to workaround the fact that inspect.getargspec is not working with functools.partial @@ -59,22 +63,27 @@ ori_args = inspect.getargspec(func).args func = functools.partial(func, *fixed_args, **fixed_kwargs) - if ori_args[0] == 'self': + if ori_args[0] == "self": del ori_args[0] - ori_args = ori_args[len(fixed_args):] + ori_args = ori_args[len(fixed_args) :] for kw in fixed_kwargs: ori_args.remove(kw) - exec(textwrap.dedent('''\ + exec( + textwrap.dedent( + """\ def method({args}): return func({kw_args}) - ''').format( - args = ', '.join(ori_args), - kw_args = ', '.join([a+'='+a for a in ori_args])) - , locals()) + """ + ).format( + args=", ".join(ori_args), kw_args=", ".join([a + "=" + a for a in ori_args]) + ), + locals(), + ) return method + def xmpp_date(timestamp=None, with_time=True): """Return date according to XEP-0082 specification @@ -86,8 +95,13 @@ """ template_date = u"%Y-%m-%d" template_time = u"%H:%M:%SZ" - template = u"{}T{}".format(template_date, template_time) if with_time else template_date - return datetime.datetime.utcfromtimestamp(time.time() if timestamp is None else timestamp).strftime(template) + template = ( + u"{}T{}".format(template_date, template_time) if with_time else template_date + ) + return datetime.datetime.utcfromtimestamp( + time.time() if timestamp is None else timestamp + ).strftime(template) + def generatePassword(vocabulary=None, size=20): """Generate a password with random characters. @@ -98,8 +112,11 @@ """ random.seed() if vocabulary is None: - vocabulary = [chr(i) for i in range(0x30,0x3A) + range(0x41,0x5B) + range (0x61,0x7B)] - return u''.join([random.choice(vocabulary) for i in range(15)]) + vocabulary = [ + chr(i) for i in range(0x30, 0x3A) + range(0x41, 0x5B) + range(0x61, 0x7B) + ] + return u"".join([random.choice(vocabulary) for i in range(15)]) + def getRepositoryData(module, as_string=True, is_path=False): """Retrieve info on current mecurial repository @@ -133,11 +150,11 @@ return repos_cache_dict if sys.platform == "android": - # FIXME: workaround to avoid trouble on android, need to be fixed properly + # FIXME: workaround to avoid trouble on android, need to be fixed properly repos_cache = u"Cagou android build" return repos_cache - KEYS=("node", "node_short", "branch", "date", "tag", "distance") + KEYS = ("node", "node_short", "branch", "date", "tag", "distance") ori_cwd = os.getcwd() if is_path: @@ -146,7 +163,7 @@ repos_root = os.path.abspath(os.path.dirname(module.__file__)) try: - hg_path = procutils.which('hg')[0] + hg_path = procutils.which("hg")[0] except IndexError: log.warning(u"Can't find hg executable") hg_path = None @@ -155,18 +172,27 @@ if hg_path is not None: os.chdir(repos_root) try: - hg_data_raw = subprocess.check_output(["hg","log", "-r", "-1", "--template","{node}\n" - "{node|short}\n" - "{branch}\n" - "{date|isodate}\n" - "{latesttag}\n" - "{latesttagdistance}"]) + hg_data_raw = subprocess.check_output( + [ + "hg", + "log", + "-r", + "-1", + "--template", + "{node}\n" + "{node|short}\n" + "{branch}\n" + "{date|isodate}\n" + "{latesttag}\n" + "{latesttagdistance}", + ] + ) except subprocess.CalledProcessError: hg_data = {} else: - hg_data = dict(zip(KEYS, hg_data_raw.split('\n'))) + hg_data = dict(zip(KEYS, hg_data_raw.split("\n"))) try: - hg_data['modified'] = '+' in subprocess.check_output(["hg","id","-i"]) + hg_data["modified"] = "+" in subprocess.check_output(["hg", "id", "-i"]) except subprocess.CalledProcessError: pass else: @@ -180,9 +206,9 @@ else: os.chdir(os.path.abspath(os.path.dirname(repos_root))) try: - with open('.hg/dirstate') as hg_dirstate: - hg_data['node'] = hg_dirstate.read(20).encode('hex') - hg_data['node_short'] = hg_data['node'][:12] + with open(".hg/dirstate") as hg_dirstate: + hg_data["node"] = hg_dirstate.read(20).encode("hex") + hg_data["node_short"] = hg_data["node"][:12] except IOError: log.debug(u"Can't access repository data") @@ -193,25 +219,33 @@ log.debug(u"Mercurial not available or working, trying package version") try: import pkg_resources + pkg_version = pkg_resources.get_distribution(C.APP_NAME_FILE).version - version, local_id = pkg_version.split('+', 1) + version, local_id = pkg_version.split("+", 1) except ImportError: log.warning("pkg_resources not available, can't get package data") except pkg_resources.DistributionNotFound: log.warning("can't retrieve package data") except ValueError: - log.info(u"no local version id in package: {pkg_version}".format(pkg_version=pkg_version)) + log.info( + u"no local version id in package: {pkg_version}".format( + pkg_version=pkg_version + ) + ) else: - version = version.replace('.dev0', 'D') + version = version.replace(".dev0", "D") if version != C.APP_VERSION: - log.warning("Incompatible version ({version}) and pkg_version ({pkg_version})".format( - version=C.APP_VERSION, pkg_version=pkg_version)) + log.warning( + "Incompatible version ({version}) and pkg_version ({pkg_version})".format( + version=C.APP_VERSION, pkg_version=pkg_version + ) + ) else: try: - hg_node, hg_distance = local_id.split('.') + hg_node, hg_distance = local_id.split(".") except ValueError: log.warning("Version doesn't specify repository data") - hg_data = {'node_short': hg_node, 'distance': hg_distance} + hg_data = {"node_short": hg_node, "distance": hg_distance} repos_cache_dict = hg_data @@ -219,21 +253,21 @@ if not hg_data: repos_cache = NO_REPOS_DATA else: - strings = [u'rev', hg_data['node_short']] + strings = [u"rev", hg_data["node_short"]] try: - if hg_data['modified']: + if hg_data["modified"]: strings.append(u"[M]") except KeyError: pass try: - strings.extend([u'({branch} {date})'.format(**hg_data)]) + strings.extend([u"({branch} {date})".format(**hg_data)]) except KeyError: pass try: - strings.extend([u'+{distance}'.format(**hg_data)]) + strings.extend([u"+{distance}".format(**hg_data)]) except KeyError: pass - repos_cache = u' '.join(strings) + repos_cache = u" ".join(strings) return repos_cache else: return hg_data
--- a/sat/tools/xml_tools.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat/tools/xml_tools.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from xml.dom import minidom, NotFoundErr @@ -37,14 +38,15 @@ SAT_FORM_PREFIX = "SAT_FORM_" SAT_PARAM_SEPARATOR = "_XMLUI_PARAM_" # used to have unique elements names -html_entity_re = re.compile(r'&([a-zA-Z]+?);') -XML_ENTITIES = ('quot', 'amp', 'apos', 'lt', 'gt') +html_entity_re = re.compile(r"&([a-zA-Z]+?);") +XML_ENTITIES = ("quot", "amp", "apos", "lt", "gt") # TODO: move XMLUI stuff in a separate module # TODO: rewrite this with lxml or ElementTree or domish.Element: it's complicated and difficult to maintain with current minidom implementation # Helper functions + def _dataFormField2XMLUIData(field, read_only=False): """Get data needed to create an XMLUI's Widget from Wokkel's data_form's Field. @@ -55,8 +57,8 @@ """ widget_args = [field.value] widget_kwargs = {} - if field.fieldType == 'fixed' or field.fieldType is None: - widget_type = 'text' + if field.fieldType == "fixed" or field.fieldType is None: + widget_type = "text" if field.value is None: if field.label is None: log.warning(_("Fixed field has neither value nor label, ignoring it")) @@ -65,36 +67,40 @@ field.value = field.label field.label = None widget_args[0] = field.value - elif field.fieldType == 'text-single': + elif field.fieldType == "text-single": widget_type = "string" - widget_kwargs['read_only'] = read_only - elif field.fieldType == 'jid-single': + widget_kwargs["read_only"] = read_only + elif field.fieldType == "jid-single": widget_type = "jid_input" - widget_kwargs['read_only'] = read_only - elif field.fieldType == 'text-multi': + widget_kwargs["read_only"] = read_only + elif field.fieldType == "text-multi": widget_type = "textbox" - widget_args[0] = u'\n'.join(field.values) - widget_kwargs['read_only'] = read_only - elif field.fieldType == 'text-private': + widget_args[0] = u"\n".join(field.values) + widget_kwargs["read_only"] = read_only + elif field.fieldType == "text-private": widget_type = "password" - widget_kwargs['read_only'] = read_only - elif field.fieldType == 'boolean': + widget_kwargs["read_only"] = read_only + elif field.fieldType == "boolean": widget_type = "bool" if widget_args[0] is None: - widget_args[0] = 'false' - widget_kwargs['read_only'] = read_only - elif field.fieldType == 'integer': + widget_args[0] = "false" + widget_kwargs["read_only"] = read_only + elif field.fieldType == "integer": widget_type = "integer" - widget_kwargs['read_only'] = read_only - elif field.fieldType == 'list-single': + widget_kwargs["read_only"] = read_only + elif field.fieldType == "list-single": widget_type = "list" - widget_kwargs["options"] = [(option.value, option.label or option.value) for option in field.options] + widget_kwargs["options"] = [ + (option.value, option.label or option.value) for option in field.options + ] widget_kwargs["selected"] = widget_args widget_args = [] else: - log.error(u"FIXME FIXME FIXME: Type [%s] is not managed yet by SàT" % field.fieldType) + log.error( + u"FIXME FIXME FIXME: Type [%s] is not managed yet by SàT" % field.fieldType + ) widget_type = "string" - widget_kwargs['read_only'] = read_only + widget_kwargs["read_only"] = read_only if field.var: widget_kwargs["name"] = field.var @@ -121,7 +127,7 @@ if filters is None: filters = {} if form.instructions: - form_ui.addText('\n'.join(form.instructions), 'instructions') + form_ui.addText("\n".join(form.instructions), "instructions") form_ui.changeContainer("label") @@ -130,13 +136,17 @@ form_ui.addWidget(*widget_args) for field in form.fieldList: - widget_type, widget_args, widget_kwargs = _dataFormField2XMLUIData(field, read_only) + widget_type, widget_args, widget_kwargs = _dataFormField2XMLUIData( + field, read_only + ) try: - widget_filter = filters[widget_kwargs['name']] + widget_filter = filters[widget_kwargs["name"]] except KeyError: pass else: - widget_type, widget_args, widget_kwargs = widget_filter(form_ui, widget_type, widget_args, widget_kwargs) + widget_type, widget_args, widget_kwargs = widget_filter( + form_ui, widget_type, widget_args, widget_kwargs + ) label = field.label or field.var if label: form_ui.addLabel(label) @@ -172,27 +182,29 @@ """ headers = OrderedDict() try: - reported_elt = form_xml.elements('jabber:x:data', 'reported').next() + reported_elt = form_xml.elements("jabber:x:data", "reported").next() except StopIteration: - raise exceptions.DataError("Couldn't find expected <reported> tag in %s" % form_xml.toXml()) + raise exceptions.DataError( + "Couldn't find expected <reported> tag in %s" % form_xml.toXml() + ) for elt in reported_elt.elements(): if elt.name != "field": raise exceptions.DataError("Unexpected tag") name = elt["var"] - label = elt.attributes.get('label', '') - type_ = elt.attributes.get('type') + label = elt.attributes.get("label", "") + type_ = elt.attributes.get("type") headers[name] = (label, type_) if not headers: raise exceptions.DataError("No reported fields (see XEP-0004 §3.4)") xmlui_data = [] - item_elts = form_xml.elements('jabber:x:data', 'item') + item_elts = form_xml.elements("jabber:x:data", "item") for item_elt in item_elts: for elt in item_elt.elements(): - if elt.name != 'field': + if elt.name != "field": log.warning(u"Unexpected tag (%s)" % elt.name) continue field = data_form.Field.fromElement(elt) @@ -211,7 +223,9 @@ @param xmlui_data (list[tuple]): list of (widget_type, widget_args, widget_kwargs) @return: the completed XMLUI instance """ - adv_list = AdvancedListContainer(xmlui, headers=headers, columns=len(headers), parent=xmlui.current_container) + adv_list = AdvancedListContainer( + xmlui, headers=headers, columns=len(headers), parent=xmlui.current_container + ) xmlui.changeContainer(adv_list) for widget_type, widget_args, widget_kwargs in xmlui_data: @@ -248,7 +262,10 @@ dataForm2Widgets(xml_ui, parsed_form, read_only=True) return xml_ui -def dataFormResult2XMLUI(result_form, base_form, session_id=None, prepend=None, filters=None): + +def dataFormResult2XMLUI( + result_form, base_form, session_id=None, prepend=None, filters=None +): """Convert data form result to SàT XMLUI. @param result_form (data_form.Form): result form to convert @@ -290,7 +307,11 @@ @param xmlui_data (dict): data returned by frontends for XMLUI form @return: dict of data usable by Wokkel's data form """ - return {key[len(SAT_FORM_PREFIX):]: _cleanValue(value) for key, value in xmlui_data.iteritems() if key.startswith(SAT_FORM_PREFIX)} + return { + key[len(SAT_FORM_PREFIX) :]: _cleanValue(value) + for key, value in xmlui_data.iteritems() + if key.startswith(SAT_FORM_PREFIX) + } def formEscape(name): @@ -308,7 +329,7 @@ @param xmlui_data (dict): data returned by frontends for XMLUI form @return: domish.Element """ - form = data_form.Form('submit') + form = data_form.Form("submit") form.makeFields(XMLUIResult2DataFormResult(xmlui_data)) return form.toElement() @@ -319,7 +340,7 @@ @param values (list): list of tuples @return: data_form.Form """ - form = data_form.Form('submit') + form = data_form.Form("submit") for value in values: field = data_form.Field(var=value[0], value=value[1]) form.addField(field) @@ -334,39 +355,41 @@ @return: XMLUI """ # TODO: refactor params and use Twisted directly to parse XML - params_doc = minidom.parseString(xml.encode('utf-8')) + params_doc = minidom.parseString(xml.encode("utf-8")) top = params_doc.documentElement - if top.nodeName != 'params': - raise exceptions.DataError(_('INTERNAL ERROR: parameters xml not valid')) + if top.nodeName != "params": + raise exceptions.DataError(_("INTERNAL ERROR: parameters xml not valid")) param_ui = XMLUI("param", "tabs") tabs_cont = param_ui.current_container for category in top.getElementsByTagName("category"): - category_name = category.getAttribute('name') - label = category.getAttribute('label') + category_name = category.getAttribute("name") + label = category.getAttribute("label") if not category_name: - raise exceptions.DataError(_('INTERNAL ERROR: params categories must have a name')) + raise exceptions.DataError( + _("INTERNAL ERROR: params categories must have a name") + ) tabs_cont.addTab(category_name, label=label, container=LabelContainer) for param in category.getElementsByTagName("param"): widget_kwargs = {} - param_name = param.getAttribute('name') - param_label = param.getAttribute('label') - type_ = param.getAttribute('type') - if not param_name and type_ != 'text': - raise exceptions.DataError(_('INTERNAL ERROR: params must have a name')) + param_name = param.getAttribute("name") + param_label = param.getAttribute("label") + type_ = param.getAttribute("type") + if not param_name and type_ != "text": + raise exceptions.DataError(_("INTERNAL ERROR: params must have a name")) - value = param.getAttribute('value') or None - callback_id = param.getAttribute('callback_id') or None + value = param.getAttribute("value") or None + callback_id = param.getAttribute("callback_id") or None - if type_ == 'list': + if type_ == "list": options, selected = _paramsGetListOptions(param) - widget_kwargs['options'] = options - widget_kwargs['selected'] = selected - widget_kwargs['styles'] = ['extensible'] - elif type_ == 'jids_list': - widget_kwargs['jids'] = _paramsGetListJids(param) + widget_kwargs["options"] = options + widget_kwargs["selected"] = selected + widget_kwargs["styles"] = ["extensible"] + elif type_ == "jids_list": + widget_kwargs["jids"] = _paramsGetListJids(param) if type_ in ("button", "text"): param_ui.addEmpty() @@ -378,13 +401,20 @@ widget_kwargs["value"] = value if callback_id: - widget_kwargs['callback_id'] = callback_id - others = ["%s%s%s" % (category_name, SAT_PARAM_SEPARATOR, other.getAttribute('name')) - for other in category.getElementsByTagName('param') - if other.getAttribute('type') != 'button'] - widget_kwargs['fields_back'] = others + widget_kwargs["callback_id"] = callback_id + others = [ + "%s%s%s" + % (category_name, SAT_PARAM_SEPARATOR, other.getAttribute("name")) + for other in category.getElementsByTagName("param") + if other.getAttribute("type") != "button" + ] + widget_kwargs["fields_back"] = others - widget_kwargs['name'] = "%s%s%s" % (category_name, SAT_PARAM_SEPARATOR, param_name) + widget_kwargs["name"] = "%s%s%s" % ( + category_name, + SAT_PARAM_SEPARATOR, + param_name, + ) param_ui.addWidget(type_, **widget_kwargs) @@ -399,14 +429,21 @@ @return: a tuple (options, selected_value) """ if len(param.getElementsByTagName("options")) > 0: - raise exceptions.DataError(_("The 'options' tag is not allowed in parameter of type 'list'!")) + raise exceptions.DataError( + _("The 'options' tag is not allowed in parameter of type 'list'!") + ) elems = param.getElementsByTagName("option") if len(elems) == 0: return [] options = [elem.getAttribute("value") for elem in elems] - selected = [elem.getAttribute("value") for elem in elems if elem.getAttribute("selected") == 'true'] + selected = [ + elem.getAttribute("value") + for elem in elems + if elem.getAttribute("selected") == "true" + ] return (options, selected) + def _paramsGetListJids(param): """Retrive jids from a jids_list element. @@ -415,9 +452,11 @@ @return: a list of jids """ elems = param.getElementsByTagName("jid") - jids = [elem.firstChild.data for elem in elems - if elem.firstChild is not None - and elem.firstChild.nodeType == elem.TEXT_NODE] + jids = [ + elem.firstChild.data + for elem in elems + if elem.firstChild is not None and elem.firstChild.nodeType == elem.TEXT_NODE + ] return jids @@ -426,6 +465,7 @@ class Element(object): """ Base XMLUI element """ + type = None def __init__(self, xmlui, parent=None): @@ -436,7 +476,7 @@ """ assert self.type is not None self.children = [] - if not hasattr(self, 'elem'): + if not hasattr(self, "elem"): self.elem = parent.xmlui.doc.createElement(self.type) self.xmlui = xmlui if parent is not None: @@ -457,7 +497,8 @@ class TopElement(Element): """ Main XML Element """ - type = 'top' + + type = "top" def __init__(self, xmlui): self.elem = xmlui.doc.documentElement @@ -466,7 +507,8 @@ class TabElement(Element): """ Used by TabsContainer to give name and label to tabs.""" - type = 'tab' + + type = "tab" def __init__(self, parent, name, label, selected=False): """ @@ -479,8 +521,8 @@ if not isinstance(parent, TabsContainer): raise exceptions.DataError(_("TabElement must be a child of TabsContainer")) super(TabElement, self).__init__(parent.xmlui, parent) - self.elem.setAttribute('name', name) - self.elem.setAttribute('label', label) + self.elem.setAttribute("name", name) + self.elem.setAttribute("label", label) if selected: self.setSelected(selected) @@ -489,31 +531,34 @@ @param selected (bool): set to True to select this tab """ - self.elem.setAttribute('selected', 'true' if selected else 'false') + self.elem.setAttribute("selected", "true" if selected else "false") class FieldBackElement(Element): """ Used by ButtonWidget to indicate which field have to be sent back """ - type = 'field_back' + + type = "field_back" def __init__(self, parent, name): assert isinstance(parent, ButtonWidget) super(FieldBackElement, self).__init__(parent.xmlui, parent) - self.elem.setAttribute('name', name) + self.elem.setAttribute("name", name) class InternalFieldElement(Element): """ Used by internal callbacks to indicate which fields are manipulated """ - type = 'internal_field' + + type = "internal_field" def __init__(self, parent, name): super(InternalFieldElement, self).__init__(parent.xmlui, parent) - self.elem.setAttribute('name', name) + self.elem.setAttribute("name", name) class InternalDataElement(Element): """ Used by internal callbacks to retrieve extra data """ - type = 'internal_data' + + type = "internal_data" def __init__(self, parent, children): super(InternalDataElement, self).__init__(parent.xmlui, parent) @@ -524,7 +569,8 @@ class OptionElement(Element): """" Used by ListWidget to specify options """ - type = 'option' + + type = "option" def __init__(self, parent, option, selected=False): """ @@ -541,15 +587,16 @@ value, label = option else: raise NotImplementedError - self.elem.setAttribute('value', value) - self.elem.setAttribute('label', label) + self.elem.setAttribute("value", value) + self.elem.setAttribute("label", label) if selected: - self.elem.setAttribute('selected', 'true') + self.elem.setAttribute("selected", "true") class JidElement(Element): """" Used by JidsListWidget to specify jids""" - type = 'jid' + + type = "jid" def __init__(self, parent, jid_): """ @@ -569,7 +616,8 @@ class RowElement(Element): """" Used by AdvancedListContainer """ - type = 'row' + + type = "row" def __init__(self, parent): assert isinstance(parent, AdvancedListContainer) @@ -577,13 +625,14 @@ if parent.next_row_idx is not None: if parent.auto_index: raise exceptions.DataError(_("Can't set row index if auto_index is True")) - self.elem.setAttribute('index', parent.next_row_idx) + self.elem.setAttribute("index", parent.next_row_idx) parent.next_row_idx = None class HeaderElement(Element): """" Used by AdvancedListContainer """ - type = 'header' + + type = "header" def __init__(self, parent, name=None, label=None, description=None): """ @@ -595,11 +644,11 @@ assert isinstance(parent, AdvancedListContainer) super(HeaderElement, self).__init__(parent.xmlui, parent) if name: - self.elem.setAttribute('name', name) + self.elem.setAttribute("name", name) if label: - self.elem.setAttribute('label', label) + self.elem.setAttribute("label", label) if description: - self.elem.setAttribute('description', description) + self.elem.setAttribute("description", description) ## Containers ## @@ -607,6 +656,7 @@ class Container(Element): """ And Element which contains other ones and has a layout """ + type = None def __init__(self, xmlui, parent=None): @@ -615,9 +665,9 @@ @param xmlui: XMLUI instance @parent: parent element or None """ - self.elem = xmlui.doc.createElement('container') + self.elem = xmlui.doc.createElement("container") super(Container, self).__init__(xmlui, parent) - self.elem.setAttribute('type', self.type) + self.elem.setAttribute("type", self.type) def getParentContainer(self): """ Return first parent container @@ -625,8 +675,7 @@ @return: parent container or None """ current = self.parent - while(not isinstance(current, (Container)) and - current is not None): + while not isinstance(current, (Container)) and current is not None: current = current.parent return current @@ -676,9 +725,21 @@ class AdvancedListContainer(Container): """A list which can contain other widgets, headers, etc""" + type = "advanced_list" - def __init__(self, xmlui, callback_id=None, name=None, headers=None, items=None, columns=None, selectable='no', auto_index=False, parent=None): + def __init__( + self, + xmlui, + callback_id=None, + name=None, + headers=None, + items=None, + columns=None, + selectable="no", + auto_index=False, + parent=None, + ): """Create an advanced list @param headers: optional headers information @@ -691,7 +752,7 @@ @param auto_index: if True, indexes will be generated by frontends, starting from 0 @return: created element """ - assert selectable in ('no', 'single') + assert selectable in ("no", "single") if not items and columns is None: raise exceptions.DataError(_("either items or columns need do be filled")) if headers is None: @@ -706,17 +767,19 @@ self.current_row = None if headers: if len(headers) != self._columns: - raise exceptions.DataError(_("Headers lenght doesn't correspond to columns")) + raise exceptions.DataError( + _("Headers lenght doesn't correspond to columns") + ) self.addHeaders(headers) if items: self.addItems(items) - self.elem.setAttribute('columns', str(self._columns)) + self.elem.setAttribute("columns", str(self._columns)) if callback_id is not None: - self.elem.setAttribute('callback', callback_id) - self.elem.setAttribute('selectable', selectable) + self.elem.setAttribute("callback", callback_id) + self.elem.setAttribute("selectable", selectable) self.auto_index = auto_index if auto_index: - self.elem.setAttribute('auto_index', 'true') + self.elem.setAttribute("auto_index", "true") self.next_row_idx = None def addHeaders(self, headers): @@ -770,14 +833,18 @@ @param name: name of the element or None @param parent: parent element or None """ - self.elem = xmlui.doc.createElement('widget') + self.elem = xmlui.doc.createElement("widget") super(Widget, self).__init__(xmlui, parent) if name: - self.elem.setAttribute('name', name) + self.elem.setAttribute("name", name) if name in xmlui.named_widgets: - raise exceptions.ConflictError(_(u'A widget with the name "{name}" already exists.').format(name=name)) + raise exceptions.ConflictError( + _(u'A widget with the name "{name}" already exists.').format( + name=name + ) + ) xmlui.named_widgets[name] = self - self.elem.setAttribute('type', self.type) + self.elem.setAttribute("type", self.type) def setInternalCallback(self, callback, fields, data_elts=None): """Set an internal UI callback when the widget value is changed. @@ -797,7 +864,7 @@ @param fields (list): a list of widget names (string) @param data_elts (list[Element]): extra data elements """ - self.elem.setAttribute('internal_callback', callback) + self.elem.setAttribute("internal_callback", callback) if fields: for field in fields: InternalFieldElement(self, field) @@ -807,16 +874,18 @@ class EmptyWidget(Widget): """Place holder widget""" - type = 'empty' + + type = "empty" class TextWidget(Widget): """Used for blob of text""" - type = 'text' + + type = "text" def __init__(self, xmlui, value, name=None, parent=None): super(TextWidget, self).__init__(xmlui, name, parent) - value_elt = self.xmlui.doc.createElement('value') + value_elt = self.xmlui.doc.createElement("value") text = self.xmlui.doc.createTextNode(value) value_elt.appendChild(text) self.elem.appendChild(value_elt) @@ -831,29 +900,31 @@ used most of time to display the desciption or name of the next widget """ - type = 'label' + + type = "label" def __init__(self, xmlui, label, name=None, parent=None): super(LabelWidget, self).__init__(xmlui, name, parent) - self.elem.setAttribute('value', label) + self.elem.setAttribute("value", label) class JidWidget(Widget): """Used to display a Jabber ID, some specific methods can be added""" - type = 'jid' + + type = "jid" def __init__(self, xmlui, jid, name=None, parent=None): super(JidWidget, self).__init__(xmlui, name, parent) try: - self.elem.setAttribute('value', jid.full()) + self.elem.setAttribute("value", jid.full()) except AttributeError: - self.elem.setAttribute('value', unicode(jid)) + self.elem.setAttribute("value", unicode(jid)) class DividerWidget(Widget): - type = 'divider' + type = "divider" - def __init__(self, xmlui, style='line', name=None, parent=None): + def __init__(self, xmlui, style="line", name=None, parent=None): """ Create a divider @param xmlui: XMLUI instance @@ -868,7 +939,7 @@ """ super(DividerWidget, self).__init__(xmlui, name, parent) - self.elem.setAttribute('style', style) + self.elem.setAttribute("style", style) ### Inputs ### @@ -879,19 +950,20 @@ used mainly in forms """ + def __init__(self, xmlui, name=None, parent=None, read_only=False): super(InputWidget, self).__init__(xmlui, name, parent) if read_only: - self.elem.setAttribute('read_only', 'true') + self.elem.setAttribute("read_only", "true") class StringWidget(InputWidget): - type = 'string' + type = "string" def __init__(self, xmlui, value=None, name=None, parent=None, read_only=False): super(StringWidget, self).__init__(xmlui, name, parent, read_only=read_only) if value: - value_elt = self.xmlui.doc.createElement('value') + value_elt = self.xmlui.doc.createElement("value") text = self.xmlui.doc.createTextNode(value) value_elt.appendChild(text) self.elem.appendChild(value_elt) @@ -902,20 +974,20 @@ class PasswordWidget(StringWidget): - type = 'password' + type = "password" class TextBoxWidget(StringWidget): - type = 'textbox' + type = "textbox" class JidInputWidget(StringWidget): - type = 'jid_input' + type = "jid_input" # TODO handle min and max values class IntWidget(StringWidget): - type = 'int' + type = "int" def __init__(self, xmlui, value=0, name=None, parent=None, read_only=False): try: @@ -926,25 +998,27 @@ class BoolWidget(InputWidget): - type = 'bool' + type = "bool" - def __init__(self, xmlui, value='false', name=None, parent=None, read_only=False): + def __init__(self, xmlui, value="false", name=None, parent=None, read_only=False): if isinstance(value, bool): - value = 'true' if value else 'false' - elif value == '0': - value = 'false' - elif value == '1': - value = 'true' - if value not in ('true', 'false'): + value = "true" if value else "false" + elif value == "0": + value = "false" + elif value == "1": + value = "true" + if value not in ("true", "false"): raise exceptions.DataError(_("Value must be 0, 1, false or true")) super(BoolWidget, self).__init__(xmlui, name, parent, read_only=read_only) - self.elem.setAttribute('value', value) + self.elem.setAttribute("value", value) class ButtonWidget(Widget): - type = 'button' + type = "button" - def __init__(self, xmlui, callback_id, value=None, fields_back=None, name=None, parent=None): + def __init__( + self, xmlui, callback_id, value=None, fields_back=None, name=None, parent=None + ): """Add a button @param callback_id: callback which will be called if button is pressed @@ -956,18 +1030,20 @@ if fields_back is None: fields_back = [] super(ButtonWidget, self).__init__(xmlui, name, parent) - self.elem.setAttribute('callback', callback_id) + self.elem.setAttribute("callback", callback_id) if value: - self.elem.setAttribute('value', value) + self.elem.setAttribute("value", value) for field in fields_back: FieldBackElement(self, field) class ListWidget(InputWidget): - type = 'list' - STYLES = (u'multi', u'noselect', u'extensible', u'reducible', u'inline') + type = "list" + STYLES = (u"multi", u"noselect", u"extensible", u"reducible", u"inline") - def __init__(self, xmlui, options, selected=None, styles=None, name=None, parent=None): + def __init__( + self, xmlui, options, selected=None, styles=None, name=None, parent=None + ): """ @param xmlui @@ -991,8 +1067,12 @@ styles = set() else: styles = set(styles) - if u'noselect' in styles and (u'multi' in styles or selected): - raise exceptions.DataError(_(u'"multi" flag and "selected" option are not compatible with "noselect" flag')) + if u"noselect" in styles and (u"multi" in styles or selected): + raise exceptions.DataError( + _( + u'"multi" flag and "selected" option are not compatible with "noselect" flag' + ) + ) if not options: # we can have no options if we get a submitted data form # but we can't use submitted values directly, @@ -1018,7 +1098,7 @@ if not styles.issubset(self.STYLES): raise exceptions.DataError(_(u"invalid styles")) for style in styles: - self.elem.setAttribute(style, 'yes') + self.elem.setAttribute(style, "yes") # TODO: check flags incompatibily (noselect and multi) like in __init__ def setStyle(self, style): @@ -1028,13 +1108,15 @@ def value(self): """Return the value of first selected option""" for child in self.elem.childNodes: - if child.tagName == u'option' and child.getAttribute(u'selected') == u'true': - return child.getAttribute(u'value') - return u'' + if child.tagName == u"option" and child.getAttribute(u"selected") == u"true": + return child.getAttribute(u"value") + return u"" + class JidsListWidget(InputWidget): """A list of text or jids where elements can be added/removed or modified""" - type = 'jids_list' + + type = "jids_list" def __init__(self, xmlui, jids, styles=None, name=None, parent=None): """ @@ -1047,12 +1129,12 @@ """ super(JidsListWidget, self).__init__(xmlui, name, parent) styles = set() if styles is None else set(styles) - if not styles.issubset([]): # TODO + if not styles.issubset([]): # TODO raise exceptions.DataError(_("invalid styles")) for style in styles: - self.elem.setAttribute(style, 'yes') + self.elem.setAttribute(style, "yes") if not jids: - log.debug('empty jids list') + log.debug("empty jids list") else: self.addJids(jids) @@ -1066,11 +1148,14 @@ class DialogElement(Element): """Main dialog element """ - type = 'dialog' + + type = "dialog" def __init__(self, parent, type_, level=None): if not isinstance(parent, TopElement): - raise exceptions.DataError(_("DialogElement must be a direct child of TopElement")) + raise exceptions.DataError( + _("DialogElement must be a direct child of TopElement") + ) super(DialogElement, self).__init__(parent.xmlui, parent) self.elem.setAttribute(C.XMLUI_DATA_TYPE, type_) self.elem.setAttribute(C.XMLUI_DATA_LVL, level or C.XMLUI_DATA_LVL_DEFAULT) @@ -1078,11 +1163,14 @@ class MessageElement(Element): """Element with the instruction message""" + type = C.XMLUI_DATA_MESS def __init__(self, parent, message): if not isinstance(parent, DialogElement): - raise exceptions.DataError(_("MessageElement must be a direct child of DialogElement")) + raise exceptions.DataError( + _("MessageElement must be a direct child of DialogElement") + ) super(MessageElement, self).__init__(parent.xmlui, parent) message_txt = self.xmlui.doc.createTextNode(message) self.elem.appendChild(message_txt) @@ -1090,24 +1178,30 @@ class ButtonsElement(Element): """Buttons element which indicate which set to use""" - type = 'buttons' + + type = "buttons" def __init__(self, parent, set_): if not isinstance(parent, DialogElement): - raise exceptions.DataError(_("ButtonsElement must be a direct child of DialogElement")) + raise exceptions.DataError( + _("ButtonsElement must be a direct child of DialogElement") + ) super(ButtonsElement, self).__init__(parent.xmlui, parent) - self.elem.setAttribute('set', set_) + self.elem.setAttribute("set", set_) class FileElement(Element): """File element used for FileDialog""" - type = 'file' + + type = "file" def __init__(self, parent, type_): if not isinstance(parent, DialogElement): - raise exceptions.DataError(_("FileElement must be a direct child of DialogElement")) + raise exceptions.DataError( + _("FileElement must be a direct child of DialogElement") + ) super(FileElement, self).__init__(parent.xmlui, parent) - self.elem.setAttribute('type', type_) + self.elem.setAttribute("type", type_) ## XMLUI main class @@ -1116,7 +1210,15 @@ class XMLUI(object): """This class is used to create a user interface (form/window/parameters/etc) using SàT XML""" - def __init__(self, panel_type="window", container="vertical", dialog_opt=None, title=None, submit_id=None, session_id=None): + def __init__( + self, + panel_type="window", + container="vertical", + dialog_opt=None, + title=None, + submit_id=None, + session_id=None, + ): """Init SàT XML Panel @param panel_type: one of @@ -1162,15 +1264,23 @@ @param session_id: use to keep a session attached to the dialog, must be returned by frontends @attribute named_widgets(dict): map from name to widget """ - self._introspect() # FIXME: why doing that on each XMLUI ? should be done once - if panel_type not in [C.XMLUI_WINDOW, C.XMLUI_FORM, C.XMLUI_PARAM, C.XMLUI_POPUP, C.XMLUI_DIALOG]: + self._introspect() # FIXME: why doing that on each XMLUI ? should be done once + if panel_type not in [ + C.XMLUI_WINDOW, + C.XMLUI_FORM, + C.XMLUI_PARAM, + C.XMLUI_POPUP, + C.XMLUI_DIALOG, + ]: raise exceptions.DataError(_("Unknown panel type [%s]") % panel_type) if panel_type == C.XMLUI_FORM and submit_id is None: raise exceptions.DataError(_("form XMLUI need a submit_id")) if not isinstance(container, basestring): raise exceptions.DataError(_("container argument must be a string")) if dialog_opt is not None and panel_type != C.XMLUI_DIALOG: - raise exceptions.DataError(_("dialog_opt can only be used with dialog panels")) + raise exceptions.DataError( + _("dialog_opt can only be used with dialog panels") + ) self.type = panel_type impl = minidom.getDOMImplementation() @@ -1197,11 +1307,11 @@ for obj in globals().values(): try: if issubclass(obj, Widget): - if obj.__name__ == 'Widget': + if obj.__name__ == "Widget": continue self._widgets[obj.type] = obj elif issubclass(obj, Container): - if obj.__name__ == 'Container': + if obj.__name__ == "Container": continue self._containers[obj.type] = obj except TypeError: @@ -1211,20 +1321,26 @@ self.doc.unlink() def __getattr__(self, name): - if name.startswith("add") and name not in ('addWidget',): # addWidgetName(...) create an instance of WidgetName + if name.startswith("add") and name not in ( + "addWidget", + ): # addWidgetName(...) create an instance of WidgetName if self.type == C.XMLUI_DIALOG: raise exceptions.InternalError(_("addXXX can't be used with dialogs")) class_name = name[3:] + "Widget" if class_name in globals(): cls = globals()[class_name] if issubclass(cls, Widget): + def createWidget(*args, **kwargs): if "parent" not in kwargs: kwargs["parent"] = self.current_container - if "name" not in kwargs and issubclass(cls, InputWidget): # name can be given as first argument or in keyword arguments for InputWidgets + if "name" not in kwargs and issubclass( + cls, InputWidget + ): # name can be given as first argument or in keyword arguments for InputWidgets args = list(args) kwargs["name"] = args.pop(0) return cls(self, *args, **kwargs) + return createWidget return object.__getattribute__(self, name) @@ -1270,8 +1386,13 @@ def _createDialog(self, dialog_opt): dialog_type = dialog_opt.setdefault(C.XMLUI_DATA_TYPE, C.XMLUI_DIALOG_MESSAGE) - if dialog_type in [C.XMLUI_DIALOG_CONFIRM, C.XMLUI_DIALOG_FILE] and self.submit_id is None: - raise exceptions.InternalError(_("Submit ID must be filled for this kind of dialog")) + if ( + dialog_type in [C.XMLUI_DIALOG_CONFIRM, C.XMLUI_DIALOG_FILE] + and self.submit_id is None + ): + raise exceptions.InternalError( + _("Submit ID must be filled for this kind of dialog") + ) top_element = TopElement(self) level = dialog_opt.get(C.XMLUI_DATA_LVL) dialog_elt = DialogElement(top_element, dialog_type, level) @@ -1309,9 +1430,15 @@ @param container: either container type (container it then created), or an Container instance""" if isinstance(container, basestring): - self.current_container = self._createContainer(container, self.current_container.getParentContainer() or self.main_container, **kwargs) + self.current_container = self._createContainer( + container, + self.current_container.getParentContainer() or self.main_container, + **kwargs + ) else: - self.current_container = self.main_container if container is None else container + self.current_container = ( + self.main_container if container is None else container + ) assert isinstance(self.current_container, Container) return self.current_container @@ -1331,7 +1458,8 @@ # Some sugar for XMLUI dialogs -def note(message, title='', level=C.XMLUI_DATA_LVL_INFO): + +def note(message, title="", level=C.XMLUI_DATA_LVL_INFO): """sugar to easily create a Note Dialog @param message(unicode): body of the note @@ -1339,19 +1467,22 @@ @param level(unicode): one of C.XMLUI_DATA_LVL_* @return(XMLUI): instance of XMLUI """ - note_xmlui = XMLUI(C.XMLUI_DIALOG, dialog_opt={ - C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, - C.XMLUI_DATA_MESS: message, - C.XMLUI_DATA_LVL: level}, - title=title - ) + note_xmlui = XMLUI( + C.XMLUI_DIALOG, + dialog_opt={ + C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, + C.XMLUI_DATA_MESS: message, + C.XMLUI_DATA_LVL: level, + }, + title=title, + ) return note_xmlui -def quickNote(host, client, message, title='', level=C.XMLUI_DATA_LVL_INFO): +def quickNote(host, client, message, title="", level=C.XMLUI_DATA_LVL_INFO): """more sugar to do the whole note process""" note_ui = note(message, title, level) - host.actionNew({'xmlui': note_ui.toXml()}, profile=client.profile) + host.actionNew({"xmlui": note_ui.toXml()}, profile=client.profile) def deferredUI(host, xmlui, chained=False): @@ -1363,7 +1494,7 @@ useful when backend is in a series of dialogs with an ui @return (D(data)): a deferred which fire the data """ - assert xmlui.submit_id == '' + assert xmlui.submit_id == "" xmlui_d = defer.Deferred() def onSubmit(data, profile): @@ -1373,7 +1504,15 @@ xmlui.submit_id = host.registerCallback(onSubmit, with_data=True, one_shot=True) return xmlui_d -def deferXMLUI(host, xmlui, action_extra=None, security_limit=C.NO_SECURITY_LIMIT, chained=False, profile=C.PROF_KEY_NONE): + +def deferXMLUI( + host, + xmlui, + action_extra=None, + security_limit=C.NO_SECURITY_LIMIT, + chained=False, + profile=C.PROF_KEY_NONE, +): """Create a deferred linked to XMLUI @param xmlui(XMLUI): instance of the XMLUI @@ -1387,14 +1526,29 @@ @return (data): a deferred which fire the data """ xmlui_d = deferredUI(host, xmlui, chained) - action_data = {'xmlui': xmlui.toXml()} + action_data = {"xmlui": xmlui.toXml()} if action_extra is not None: action_data.update(action_extra) - host.actionNew(action_data, security_limit=security_limit, keep_id=xmlui.submit_id, profile=profile) + host.actionNew( + action_data, + security_limit=security_limit, + keep_id=xmlui.submit_id, + profile=profile, + ) return xmlui_d -def deferDialog(host, message, title=u'Please confirm', type_=C.XMLUI_DIALOG_CONFIRM, options=None, - action_extra=None, security_limit=C.NO_SECURITY_LIMIT, chained=False, profile=C.PROF_KEY_NONE): + +def deferDialog( + host, + message, + title=u"Please confirm", + type_=C.XMLUI_DIALOG_CONFIRM, + options=None, + action_extra=None, + security_limit=C.NO_SECURITY_LIMIT, + chained=False, + profile=C.PROF_KEY_NONE, +): """Create a submitable dialog and manage it with a deferred @param message(unicode): message to display @@ -1410,22 +1564,26 @@ @return (dict): Deferred dict """ assert profile is not None - dialog_opt = {'type': type_, 'message': message} + dialog_opt = {"type": type_, "message": message} if options is not None: dialog_opt.update(options) - dialog = XMLUI(C.XMLUI_DIALOG, title=title, dialog_opt=dialog_opt, submit_id='') + dialog = XMLUI(C.XMLUI_DIALOG, title=title, dialog_opt=dialog_opt, submit_id="") return deferXMLUI(host, dialog, action_extra, security_limit, chained, profile) + def deferConfirm(*args, **kwargs): """call deferDialog and return a boolean instead of the whole data dict""" d = deferDialog(*args, **kwargs) - d.addCallback(lambda data: C.bool(data['answer'])) + d.addCallback(lambda data: C.bool(data["answer"])) return d + # Misc other funtions + class ElementParser(object): """callable class to parse XML string into Element""" + # XXX: Found at http://stackoverflow.com/questions/2093400/how-to-create-twisted-words-xish-domish-element-entirely-from-raw-xml/2095942#2095942 def _escapeHTML(self, matchobj): @@ -1438,7 +1596,7 @@ return unichr(htmlentitydefs.name2codepoint[entity]) except KeyError: log.warning(u"removing unknown entity {}".format(entity)) - return u'' + return u"" def __call__(self, raw_xml, force_spaces=False, namespace=None): """ @@ -1473,9 +1631,9 @@ parser.DocumentEndEvent = onEnd tmp = domish.Element((None, "s")) if force_spaces: - raw_xml = raw_xml.replace('\n', ' ').replace('\t', ' ') + raw_xml = raw_xml.replace("\n", " ").replace("\t", " ") tmp.addRawXml(raw_xml) - parser.parse(tmp.toXml().encode('utf-8')) + parser.parse(tmp.toXml().encode("utf-8")) top_elt = self.result.firstChildElement() # we now can check if there was a unique element on the top # and remove our wrapping <div/> is this was the case @@ -1497,7 +1655,8 @@ data.append(child.wholeText) return u"".join(data) -def findAll(elt, namespaces=None, names=None, ): + +def findAll(elt, namespaces=None, names=None): """Find child element at any depth matching criteria @param elt(domish.Element): top parent of the elements to find @@ -1508,14 +1667,16 @@ @return ((G)domish.Element): found elements """ if isinstance(namespaces, basestring): - namespaces=tuple((namespaces,)) + namespaces = tuple((namespaces,)) if isinstance(names, basestring): - names=tuple((names,)) + names = tuple((names,)) for child in elt.elements(): - if (domish.IElement.providedBy(child) and - (not names or child.name in names) and - (not namespaces or child.uri in namespaces)): + if ( + domish.IElement.providedBy(child) + and (not names or child.name in names) + and (not namespaces or child.uri in namespaces) + ): yield child for found in findAll(child, namespaces, names): yield found
--- a/sat_frontends/bridge/bridge_frontend.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/bridge/bridge_frontend.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SAT communication bridge # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) @@ -21,7 +21,7 @@ class BridgeException(Exception): """An exception which has been raised from the backend and arrived to the frontend.""" - def __init__(self, name, message='', condition=''): + def __init__(self, name, message="", condition=""): """ @param name (str): full exception class name (with module) @@ -31,11 +31,11 @@ Exception.__init__(self) self.fullname = unicode(name) self.message = unicode(message) - self.condition = unicode(condition) if condition else '' - self.module, dummy, self.classname = unicode(self.fullname).rpartition('.') + self.condition = unicode(condition) if condition else "" + self.module, dummy, self.classname = unicode(self.fullname).rpartition(".") def __str__(self): - message = (': %s' % self.message) if self.message else '' + message = (": %s" % self.message) if self.message else "" return self.classname + message def __eq__(self, other):
--- a/sat_frontends/bridge/pb.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/bridge/pb.py Wed Jun 27 20:14:46 2018 +0200 @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SAT communication bridge # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) @@ -18,6 +18,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from twisted.spread import pb @@ -25,11 +26,9 @@ class SignalsHandler(pb.Referenceable): - def __getattr__(self, name): if name.startswith("remote_"): - log.debug(u"calling an unregistered signal: {name}".format( - name = name[7:])) + log.debug(u"calling an unregistered signal: {name}".format(name=name[7:])) return lambda *args, **kwargs: None else: @@ -43,13 +42,15 @@ except AttributeError: pass else: - raise exceptions.InternalError(u"{name} signal handler has been registered twice".format( - name = method_name)) + raise exceptions.InternalError( + u"{name} signal handler has been registered twice".format( + name=method_name + ) + ) setattr(self, method_name, handler) class Bridge(object): - def __init__(self): self.signals_handler = SignalsHandler() @@ -81,11 +82,11 @@ callback = errback = None if kwargs: try: - callback = kwargs.pop('callback') + callback = kwargs.pop("callback") except KeyError: pass try: - errback = kwargs.pop('errback') + errback = kwargs.pop("errback") except KeyError: pass elif len(args) >= 2 and callable(args[-1]) and callable(args[-2]): @@ -132,7 +133,9 @@ errback = self._generic_errback d.addErrback(errback) - def addContact(self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None): + def addContact( + self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None + ): d = self.root.callRemote("addContact", entity_jid, profile_key) if callback is not None: d.addCallback(lambda dummy: callback()) @@ -148,23 +151,50 @@ errback = self._generic_errback d.addErrback(errback) - def asyncGetParamA(self, name, category, attribute="value", security_limit=-1, profile_key="@DEFAULT@", callback=None, errback=None): - d = self.root.callRemote("asyncGetParamA", name, category, attribute, security_limit, profile_key) + def asyncGetParamA( + self, + name, + category, + attribute="value", + security_limit=-1, + profile_key="@DEFAULT@", + callback=None, + errback=None, + ): + d = self.root.callRemote( + "asyncGetParamA", name, category, attribute, security_limit, profile_key + ) if callback is not None: d.addCallback(callback) if errback is None: errback = self._generic_errback d.addErrback(errback) - def asyncGetParamsValuesFromCategory(self, category, security_limit=-1, profile_key="@DEFAULT@", callback=None, errback=None): - d = self.root.callRemote("asyncGetParamsValuesFromCategory", category, security_limit, profile_key) + def asyncGetParamsValuesFromCategory( + self, + category, + security_limit=-1, + profile_key="@DEFAULT@", + callback=None, + errback=None, + ): + d = self.root.callRemote( + "asyncGetParamsValuesFromCategory", category, security_limit, profile_key + ) if callback is not None: d.addCallback(callback) if errback is None: errback = self._generic_errback d.addErrback(errback) - def connect(self, profile_key="@DEFAULT@", password='', options={}, callback=None, errback=None): + def connect( + self, + profile_key="@DEFAULT@", + password="", + options={}, + callback=None, + errback=None, + ): d = self.root.callRemote("connect", profile_key, password, options) if callback is not None: d.addCallback(callback) @@ -172,7 +202,9 @@ errback = self._generic_errback d.addErrback(errback) - def delContact(self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None): + def delContact( + self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None + ): d = self.root.callRemote("delContact", entity_jid, profile_key) if callback is not None: d.addCallback(lambda dummy: callback()) @@ -180,15 +212,45 @@ errback = self._generic_errback d.addErrback(errback) - def discoFindByFeatures(self, namespaces, identities, bare_jid=False, service=True, roster=True, own_jid=True, local_device=False, profile_key=u"@DEFAULT@", callback=None, errback=None): - d = self.root.callRemote("discoFindByFeatures", namespaces, identities, bare_jid, service, roster, own_jid, local_device, profile_key) + def discoFindByFeatures( + self, + namespaces, + identities, + bare_jid=False, + service=True, + roster=True, + own_jid=True, + local_device=False, + profile_key=u"@DEFAULT@", + callback=None, + errback=None, + ): + d = self.root.callRemote( + "discoFindByFeatures", + namespaces, + identities, + bare_jid, + service, + roster, + own_jid, + local_device, + profile_key, + ) if callback is not None: d.addCallback(callback) if errback is None: errback = self._generic_errback d.addErrback(errback) - def discoInfos(self, entity_jid, node=u'', use_cache=True, profile_key=u"@DEFAULT@", callback=None, errback=None): + def discoInfos( + self, + entity_jid, + node=u"", + use_cache=True, + profile_key=u"@DEFAULT@", + callback=None, + errback=None, + ): d = self.root.callRemote("discoInfos", entity_jid, node, use_cache, profile_key) if callback is not None: d.addCallback(callback) @@ -196,7 +258,15 @@ errback = self._generic_errback d.addErrback(errback) - def discoItems(self, entity_jid, node=u'', use_cache=True, profile_key=u"@DEFAULT@", callback=None, errback=None): + def discoItems( + self, + entity_jid, + node=u"", + use_cache=True, + profile_key=u"@DEFAULT@", + callback=None, + errback=None, + ): d = self.root.callRemote("discoItems", entity_jid, node, use_cache, profile_key) if callback is not None: d.addCallback(callback) @@ -228,7 +298,9 @@ errback = self._generic_errback d.addErrback(errback) - def getContactsFromGroup(self, group, profile_key="@DEFAULT@", callback=None, errback=None): + def getContactsFromGroup( + self, group, profile_key="@DEFAULT@", callback=None, errback=None + ): d = self.root.callRemote("getContactsFromGroup", group, profile_key) if callback is not None: d.addCallback(callback) @@ -260,7 +332,9 @@ errback = self._generic_errback d.addErrback(errback) - def getMainResource(self, contact_jid, profile_key="@DEFAULT@", callback=None, errback=None): + def getMainResource( + self, contact_jid, profile_key="@DEFAULT@", callback=None, errback=None + ): d = self.root.callRemote("getMainResource", contact_jid, profile_key) if callback is not None: d.addCallback(callback) @@ -268,7 +342,15 @@ errback = self._generic_errback d.addErrback(errback) - def getParamA(self, name, category, attribute="value", profile_key="@DEFAULT@", callback=None, errback=None): + def getParamA( + self, + name, + category, + attribute="value", + profile_key="@DEFAULT@", + callback=None, + errback=None, + ): d = self.root.callRemote("getParamA", name, category, attribute, profile_key) if callback is not None: d.addCallback(callback) @@ -284,7 +366,14 @@ errback = self._generic_errback d.addErrback(errback) - def getParamsUI(self, security_limit=-1, app='', profile_key="@DEFAULT@", callback=None, errback=None): + def getParamsUI( + self, + security_limit=-1, + app="", + profile_key="@DEFAULT@", + callback=None, + errback=None, + ): d = self.root.callRemote("getParamsUI", security_limit, app, profile_key) if callback is not None: d.addCallback(callback) @@ -324,8 +413,20 @@ errback = self._generic_errback d.addErrback(errback) - def historyGet(self, from_jid, to_jid, limit, between=True, filters='', profile="@NONE@", callback=None, errback=None): - d = self.root.callRemote("historyGet", from_jid, to_jid, limit, between, filters, profile) + def historyGet( + self, + from_jid, + to_jid, + limit, + between=True, + filters="", + profile="@NONE@", + callback=None, + errback=None, + ): + d = self.root.callRemote( + "historyGet", from_jid, to_jid, limit, between, filters, profile + ) if callback is not None: d.addCallback(callback) if errback is None: @@ -340,7 +441,9 @@ errback = self._generic_errback d.addErrback(errback) - def launchAction(self, callback_id, data, profile_key="@DEFAULT@", callback=None, errback=None): + def launchAction( + self, callback_id, data, profile_key="@DEFAULT@", callback=None, errback=None + ): d = self.root.callRemote("launchAction", callback_id, data, profile_key) if callback is not None: d.addCallback(callback) @@ -364,8 +467,19 @@ errback = self._generic_errback d.addErrback(errback) - def menuLaunch(self, menu_type, path, data, security_limit, profile_key, callback=None, errback=None): - d = self.root.callRemote("menuLaunch", menu_type, path, data, security_limit, profile_key) + def menuLaunch( + self, + menu_type, + path, + data, + security_limit, + profile_key, + callback=None, + errback=None, + ): + d = self.root.callRemote( + "menuLaunch", menu_type, path, data, security_limit, profile_key + ) if callback is not None: d.addCallback(callback) if errback is None: @@ -380,8 +494,20 @@ errback = self._generic_errback d.addErrback(errback) - def messageSend(self, to_jid, message, subject={}, mess_type="auto", extra={}, profile_key="@NONE@", callback=None, errback=None): - d = self.root.callRemote("messageSend", to_jid, message, subject, mess_type, extra, profile_key) + def messageSend( + self, + to_jid, + message, + subject={}, + mess_type="auto", + extra={}, + profile_key="@NONE@", + callback=None, + errback=None, + ): + d = self.root.callRemote( + "messageSend", to_jid, message, subject, mess_type, extra, profile_key + ) if callback is not None: d.addCallback(lambda dummy: callback()) if errback is None: @@ -396,7 +522,9 @@ errback = self._generic_errback d.addErrback(errback) - def paramsRegisterApp(self, xml, security_limit=-1, app='', callback=None, errback=None): + def paramsRegisterApp( + self, xml, security_limit=-1, app="", callback=None, errback=None + ): d = self.root.callRemote("paramsRegisterApp", xml, security_limit, app) if callback is not None: d.addCallback(lambda dummy: callback()) @@ -404,7 +532,9 @@ errback = self._generic_errback d.addErrback(errback) - def profileCreate(self, profile, password='', component='', callback=None, errback=None): + def profileCreate( + self, profile, password="", component="", callback=None, errback=None + ): d = self.root.callRemote("profileCreate", profile, password, component) if callback is not None: d.addCallback(lambda dummy: callback()) @@ -412,7 +542,9 @@ errback = self._generic_errback d.addErrback(errback) - def profileIsSessionStarted(self, profile_key="@DEFAULT@", callback=None, errback=None): + def profileIsSessionStarted( + self, profile_key="@DEFAULT@", callback=None, errback=None + ): d = self.root.callRemote("profileIsSessionStarted", profile_key) if callback is not None: d.addCallback(callback) @@ -436,7 +568,9 @@ errback = self._generic_errback d.addErrback(errback) - def profileStartSession(self, password='', profile_key="@DEFAULT@", callback=None, errback=None): + def profileStartSession( + self, password="", profile_key="@DEFAULT@", callback=None, errback=None + ): d = self.root.callRemote("profileStartSession", password, profile_key) if callback is not None: d.addCallback(callback) @@ -444,7 +578,9 @@ errback = self._generic_errback d.addErrback(errback) - def profilesListGet(self, clients=True, components=False, callback=None, errback=None): + def profilesListGet( + self, clients=True, components=False, callback=None, errback=None + ): d = self.root.callRemote("profilesListGet", clients, components) if callback is not None: d.addCallback(callback) @@ -492,15 +628,34 @@ errback = self._generic_errback d.addErrback(errback) - def setParam(self, name, value, category, security_limit=-1, profile_key="@DEFAULT@", callback=None, errback=None): - d = self.root.callRemote("setParam", name, value, category, security_limit, profile_key) + def setParam( + self, + name, + value, + category, + security_limit=-1, + profile_key="@DEFAULT@", + callback=None, + errback=None, + ): + d = self.root.callRemote( + "setParam", name, value, category, security_limit, profile_key + ) if callback is not None: d.addCallback(lambda dummy: callback()) if errback is None: errback = self._generic_errback d.addErrback(errback) - def setPresence(self, to_jid='', show='', statuses={}, profile_key="@DEFAULT@", callback=None, errback=None): + def setPresence( + self, + to_jid="", + show="", + statuses={}, + profile_key="@DEFAULT@", + callback=None, + errback=None, + ): d = self.root.callRemote("setPresence", to_jid, show, statuses, profile_key) if callback is not None: d.addCallback(lambda dummy: callback()) @@ -508,7 +663,9 @@ errback = self._generic_errback d.addErrback(errback) - def subscription(self, sub_type, entity, profile_key="@DEFAULT@", callback=None, errback=None): + def subscription( + self, sub_type, entity, profile_key="@DEFAULT@", callback=None, errback=None + ): d = self.root.callRemote("subscription", sub_type, entity, profile_key) if callback is not None: d.addCallback(lambda dummy: callback()) @@ -516,7 +673,15 @@ errback = self._generic_errback d.addErrback(errback) - def updateContact(self, entity_jid, name, groups, profile_key="@DEFAULT@", callback=None, errback=None): + def updateContact( + self, + entity_jid, + name, + groups, + profile_key="@DEFAULT@", + callback=None, + errback=None, + ): d = self.root.callRemote("updateContact", entity_jid, name, groups, profile_key) if callback is not None: d.addCallback(lambda dummy: callback())
--- a/sat_frontends/jp/arg_tools.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/arg_tools.py Wed Jun 27 20:14:46 2018 +0200 @@ -26,9 +26,9 @@ @param smart(bool): if True, only escape if needed """ - if smart and not ' ' in arg and not '"' in arg: + if smart and not " " in arg and not '"' in arg: return arg - return u'"' + arg.replace(u'"',u'\\"') + u'"' + return u'"' + arg.replace(u'"', u'\\"') + u'"' def get_cmd_choices(cmd=None, parser=None): @@ -61,7 +61,7 @@ # else USE args would not work correctly (only for current parser) parser_args = [] for arg in args: - if arg.startswith('-'): + if arg.startswith("-"): break try: parser = get_cmd_choices(arg, parser) @@ -71,25 +71,31 @@ # post_args are remaning given args, # without the ones corresponding to parsers - post_args = args[len(parser_args):] + post_args = args[len(parser_args) :] opt_args = [] pos_args = [] actions = {a.dest: a for a in parser._actions} for arg, value in use.iteritems(): try: - if arg == u'item' and not u'item' in actions: + if arg == u"item" and not u"item" in actions: # small hack when --item is appended to a --items list - arg = u'items' + arg = u"items" action = actions[arg] except KeyError: if verbose: - host.disp(_(u'ignoring {name}={value}, not corresponding to any argument (in USE)').format( - name=arg, - value=escape(value))) + host.disp( + _( + u"ignoring {name}={value}, not corresponding to any argument (in USE)" + ).format(name=arg, value=escape(value)) + ) else: if verbose: - host.disp(_(u'arg {name}={value} (in USE)').format(name=arg, value=escape(value))) + host.disp( + _(u"arg {name}={value} (in USE)").format( + name=arg, value=escape(value) + ) + ) if not action.option_strings: pos_args.append(value) else:
--- a/sat_frontends/jp/cmd_account.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_account.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from sat_frontends.jp.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core.i18n import _ from sat_frontends.jp import base @@ -30,103 +31,194 @@ class AccountCreate(base.CommandBase): - def __init__(self, host): - super(AccountCreate, self).__init__(host, 'create', use_profile=False, use_verbose=True, help=_(u'create a XMPP account')) + super(AccountCreate, self).__init__( + host, + "create", + use_profile=False, + use_verbose=True, + help=_(u"create a XMPP account"), + ) self.need_loop = True def add_parser_options(self): - self.parser.add_argument('jid', type=base.unicode_decoder, help=_(u'jid to create')) - self.parser.add_argument('password', type=base.unicode_decoder, help=_(u'password of the account')) - self.parser.add_argument('-p', '--profile', type=base.unicode_decoder, help=_(u"create a profile to use this account (default: don't create profile)")) - self.parser.add_argument('-e', '--email', type=base.unicode_decoder, default="", help=_(u"email (usage depends of XMPP server)")) - self.parser.add_argument('-H', '--host', type=base.unicode_decoder, default="", help=_(u"server host (IP address or domain, default: use localhost)")) - self.parser.add_argument('-P', '--port', type=int, default=0, help=_(u"server port (IP address or domain, default: use localhost)")) + self.parser.add_argument( + "jid", type=base.unicode_decoder, help=_(u"jid to create") + ) + self.parser.add_argument( + "password", type=base.unicode_decoder, help=_(u"password of the account") + ) + self.parser.add_argument( + "-p", + "--profile", + type=base.unicode_decoder, + help=_( + u"create a profile to use this account (default: don't create profile)" + ), + ) + self.parser.add_argument( + "-e", + "--email", + type=base.unicode_decoder, + default="", + help=_(u"email (usage depends of XMPP server)"), + ) + self.parser.add_argument( + "-H", + "--host", + type=base.unicode_decoder, + default="", + help=_(u"server host (IP address or domain, default: use localhost)"), + ) + self.parser.add_argument( + "-P", + "--port", + type=int, + default=0, + help=_(u"server port (IP address or domain, default: use localhost)"), + ) def _setParamCb(self): - self.host.bridge.setParam("Password", self.args.password, "Connection", profile_key=self.args.profile, callback=self.host.quit, errback=self.errback) + self.host.bridge.setParam( + "Password", + self.args.password, + "Connection", + profile_key=self.args.profile, + callback=self.host.quit, + errback=self.errback, + ) def _session_started(self, dummy): - self.host.bridge.setParam("JabberID", self.args.jid, "Connection", profile_key=self.args.profile, callback=self._setParamCb, errback=self.errback) + self.host.bridge.setParam( + "JabberID", + self.args.jid, + "Connection", + profile_key=self.args.profile, + callback=self._setParamCb, + errback=self.errback, + ) def _profileCreateCb(self): self.disp(_(u"profile created"), 1) - self.host.bridge.profileStartSession(self.args.password, self.args.profile, callback=self._session_started, errback=self.errback) + self.host.bridge.profileStartSession( + self.args.password, + self.args.profile, + callback=self._session_started, + errback=self.errback, + ) def _profileCreateEb(self, failure_): - self.disp(_(u"Can't create profile {profile} to associate with jid {jid}: {msg}").format( - profile = self.args.profile, - jid = self.args.jid, - msg = failure_), error=True) + self.disp( + _( + u"Can't create profile {profile} to associate with jid {jid}: {msg}" + ).format(profile=self.args.profile, jid=self.args.jid, msg=failure_), + error=True, + ) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def accountNewCb(self): self.disp(_(u"XMPP account created"), 1) if self.args.profile is not None: self.disp(_(u"creating profile"), 2) - self.host.bridge.profileCreate(self.args.profile, self.args.password, "", callback=self._profileCreateCb, errback=self._profileCreateEb) + self.host.bridge.profileCreate( + self.args.profile, + self.args.password, + "", + callback=self._profileCreateCb, + errback=self._profileCreateEb, + ) else: self.host.quit() def accountNewEb(self, failure_): - self.disp(_(u"Can't create new account on server {host} with jid {jid}: {msg}").format( - host = self.args.host or u"localhost", - jid = self.args.jid, - msg = failure_), error=True) + self.disp( + _(u"Can't create new account on server {host} with jid {jid}: {msg}").format( + host=self.args.host or u"localhost", jid=self.args.jid, msg=failure_ + ), + error=True, + ) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): - self.host.bridge.inBandAccountNew(self.args.jid, self.args.password, self.args.email, self.args.host, self.args.port, - callback=self.accountNewCb, errback=self.accountNewEb) - + self.host.bridge.inBandAccountNew( + self.args.jid, + self.args.password, + self.args.email, + self.args.host, + self.args.port, + callback=self.accountNewCb, + errback=self.accountNewEb, + ) class AccountModify(base.CommandBase): - def __init__(self, host): - super(AccountModify, self).__init__(host, 'modify', help=_(u'change password for XMPP account')) + super(AccountModify, self).__init__( + host, "modify", help=_(u"change password for XMPP account") + ) self.need_loop = True def add_parser_options(self): - self.parser.add_argument('password', type=base.unicode_decoder, help=_(u'new XMPP password')) + self.parser.add_argument( + "password", type=base.unicode_decoder, help=_(u"new XMPP password") + ) def start(self): - self.host.bridge.inBandPasswordChange(self.args.password, self.args.profile, - callback=self.host.quit, errback=self.errback) + self.host.bridge.inBandPasswordChange( + self.args.password, + self.args.profile, + callback=self.host.quit, + errback=self.errback, + ) class AccountDelete(base.CommandBase): - def __init__(self, host): - super(AccountDelete, self).__init__(host, 'delete', help=_(u'delete a XMPP account')) + super(AccountDelete, self).__init__( + host, "delete", help=_(u"delete a XMPP account") + ) self.need_loop = True def add_parser_options(self): - self.parser.add_argument('-f', '--force', action='store_true', help=_(u'delete account without confirmation')) + self.parser.add_argument( + "-f", + "--force", + action="store_true", + help=_(u"delete account without confirmation"), + ) def _got_jid(self, jid_str): jid_ = jid.JID(jid_str) if not self.args.force: - message = (u"You are about to delete the XMPP account with jid {jid_}\n" - u"This is the XMPP account of profile \"{profile}\"\n" - u"Are you sure that you want to delete this account ?".format( - jid_ = jid_, - profile=self.profile - )) + message = ( + u"You are about to delete the XMPP account with jid {jid_}\n" + u'This is the XMPP account of profile "{profile}"\n' + u"Are you sure that you want to delete this account ?".format( + jid_=jid_, profile=self.profile + ) + ) res = raw_input("{} (y/N)? ".format(message)) if res not in ("y", "Y"): self.disp(_(u"Account deletion cancelled")) self.host.quit(2) - self.host.bridge.inBandUnregister(jid_.domain, self.args.profile, - callback=self.host.quit, errback=self.errback) + self.host.bridge.inBandUnregister( + jid_.domain, self.args.profile, callback=self.host.quit, errback=self.errback + ) def start(self): - self.host.bridge.asyncGetParamA("JabberID", "Connection", profile_key=self.profile, - callback=self._got_jid, errback=self.errback) + self.host.bridge.asyncGetParamA( + "JabberID", + "Connection", + profile_key=self.profile, + callback=self._got_jid, + errback=self.errback, + ) class Account(base.CommandBase): subcommands = (AccountCreate, AccountModify, AccountDelete) def __init__(self, host): - super(Account, self).__init__(host, 'account', use_profile=False, help=(u'XMPP account management')) + super(Account, self).__init__( + host, "account", use_profile=False, help=(u"XMPP account management") + )
--- a/sat_frontends/jp/cmd_adhoc.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_adhoc.py Wed Jun 27 20:14:46 2018 +0200 @@ -25,71 +25,134 @@ __commands__ = ["AdHoc"] -FLAG_LOOP = 'LOOP' -MAGIC_BAREJID = '@PROFILE_BAREJID@' +FLAG_LOOP = "LOOP" +MAGIC_BAREJID = "@PROFILE_BAREJID@" class Remote(base.CommandBase): def __init__(self, host): - super(Remote, self).__init__(host, 'remote', use_verbose=True, help=_(u'remote control a software')) + super(Remote, self).__init__( + host, "remote", use_verbose=True, help=_(u"remote control a software") + ) def add_parser_options(self): self.parser.add_argument("software", type=str, help=_(u"software name")) - self.parser.add_argument("-j", "--jids", type=base.unicode_decoder, nargs='*', default=[], help=_(u"jids allowed to use the command")) - self.parser.add_argument("-g", "--groups", type=base.unicode_decoder, nargs='*', default=[], help=_(u"groups allowed to use the command")) - self.parser.add_argument("--forbidden-groups", type=base.unicode_decoder, nargs='*', default=[], help=_(u"groups that are *NOT* allowed to use the command")) - self.parser.add_argument("--forbidden-jids", type=base.unicode_decoder, nargs='*', default=[], help=_(u"jids that are *NOT* allowed to use the command")) - self.parser.add_argument("-l", "--loop", action="store_true", help=_(u"loop on the commands")) + self.parser.add_argument( + "-j", + "--jids", + type=base.unicode_decoder, + nargs="*", + default=[], + help=_(u"jids allowed to use the command"), + ) + self.parser.add_argument( + "-g", + "--groups", + type=base.unicode_decoder, + nargs="*", + default=[], + help=_(u"groups allowed to use the command"), + ) + self.parser.add_argument( + "--forbidden-groups", + type=base.unicode_decoder, + nargs="*", + default=[], + help=_(u"groups that are *NOT* allowed to use the command"), + ) + self.parser.add_argument( + "--forbidden-jids", + type=base.unicode_decoder, + nargs="*", + default=[], + help=_(u"jids that are *NOT* allowed to use the command"), + ) + self.parser.add_argument( + "-l", "--loop", action="store_true", help=_(u"loop on the commands") + ) def start(self): name = self.args.software.lower() flags = [] - magics = {jid for jid in self.args.jids if jid.count('@')>1} + magics = {jid for jid in self.args.jids if jid.count("@") > 1} magics.add(MAGIC_BAREJID) jids = set(self.args.jids).difference(magics) if self.args.loop: flags.append(FLAG_LOOP) - bus_name, methods = self.host.bridge.adHocDBusAddAuto(name, jids, self.args.groups, magics, - self.args.forbidden_jids, self.args.forbidden_groups, - flags, self.profile) + bus_name, methods = self.host.bridge.adHocDBusAddAuto( + name, + jids, + self.args.groups, + magics, + self.args.forbidden_jids, + self.args.forbidden_groups, + flags, + self.profile, + ) if not bus_name: self.disp(_("No bus name found"), 1) return self.disp(_("Bus name found: [%s]" % bus_name), 1) for method in methods: path, iface, command = method - self.disp(_("Command found: (path:%(path)s, iface: %(iface)s) [%(command)s]" % {'path': path, - 'iface': iface, - 'command': command - }),1) + self.disp( + _( + "Command found: (path:%(path)s, iface: %(iface)s) [%(command)s]" + % {"path": path, "iface": iface, "command": command} + ), + 1, + ) class Run(base.CommandBase): """Run an Ad-Hoc command""" def __init__(self, host): - super(Run, self).__init__(host, 'run', use_verbose=True, help=_(u'run an Ad-Hoc command')) - self.need_loop=True + super(Run, self).__init__( + host, "run", use_verbose=True, help=_(u"run an Ad-Hoc command") + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument('-j', '--jid', type=base.unicode_decoder, default=u'', help=_(u"jid of the service (default: profile's server")) - self.parser.add_argument("-S", "--submit", action='append_const', const=xmlui_manager.SUBMIT, dest='workflow', help=_(u"submit form/page")) - self.parser.add_argument("-f", + self.parser.add_argument( + "-j", + "--jid", + type=base.unicode_decoder, + default=u"", + help=_(u"jid of the service (default: profile's server"), + ) + self.parser.add_argument( + "-S", + "--submit", + action="append_const", + const=xmlui_manager.SUBMIT, + dest="workflow", + help=_(u"submit form/page"), + ) + self.parser.add_argument( + "-f", "--field", type=base.unicode_decoder, - action='append', + action="append", nargs=2, - dest='workflow', + dest="workflow", metavar=(u"KEY", u"VALUE"), - help=_(u"field value")) - self.parser.add_argument('node', type=base.unicode_decoder, nargs='?', default=u'', help=_(u"node of the command (default: list commands)")) + help=_(u"field value"), + ) + self.parser.add_argument( + "node", + type=base.unicode_decoder, + nargs="?", + default=u"", + help=_(u"node of the command (default: list commands)"), + ) def adHocRunCb(self, xmlui_raw): xmlui = xmlui_manager.create(self.host, xmlui_raw) workflow = self.args.workflow xmlui.show(workflow) if not workflow: - if xmlui.type == 'form': + if xmlui.type == "form": xmlui.submitForm() else: self.host.quit() @@ -100,20 +163,31 @@ self.args.node, self.profile, callback=self.adHocRunCb, - errback=partial(self.errback, - msg=_(u"can't get ad-hoc commands list: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't get ad-hoc commands list: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class List(base.CommandBase): """Run an Ad-Hoc command""" def __init__(self, host): - super(List, self).__init__(host, 'list', use_verbose=True, help=_(u'list Ad-Hoc commands of a service')) - self.need_loop=True + super(List, self).__init__( + host, "list", use_verbose=True, help=_(u"list Ad-Hoc commands of a service") + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument('-j', '--jid', type=base.unicode_decoder, default=u'', help=_(u"jid of the service (default: profile's server")) + self.parser.add_argument( + "-j", + "--jid", + type=base.unicode_decoder, + default=u"", + help=_(u"jid of the service (default: profile's server"), + ) def adHocListCb(self, xmlui_raw): xmlui = xmlui_manager.create(self.host, xmlui_raw) @@ -126,13 +200,18 @@ self.args.jid, self.profile, callback=self.adHocListCb, - errback=partial(self.errback, - msg=_(u"can't get ad-hoc commands list: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't get ad-hoc commands list: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class AdHoc(base.CommandBase): subcommands = (Run, List, Remote) def __init__(self, host): - super(AdHoc, self).__init__(host, 'ad-hoc', use_profile=False, help=_('Ad-hoc commands')) + super(AdHoc, self).__init__( + host, "ad-hoc", use_profile=False, help=_("Ad-hoc commands") + )
--- a/sat_frontends/jp/cmd_avatar.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_avatar.py Wed Jun 27 20:14:46 2018 +0200 @@ -28,16 +28,20 @@ __commands__ = ["Avatar"] -DISPLAY_CMD = ['xv', 'display', 'gwenview', 'showtell'] +DISPLAY_CMD = ["xv", "display", "gwenview", "showtell"] class Set(base.CommandBase): def __init__(self, host): - super(Set, self).__init__(host, 'set', use_verbose=True, help=_('set avatar of the profile')) - self.need_loop=True + super(Set, self).__init__( + host, "set", use_verbose=True, help=_("set avatar of the profile") + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("image_path", type=str, help=_("path to the image to upload")) + self.parser.add_argument( + "image_path", type=str, help=_("path to the image to upload") + ) def start(self): """Send files to jabber contact""" @@ -46,30 +50,37 @@ self.disp(_(u"file [{}] doesn't exist !").format(path), error=True) self.host.quit(1) path = os.path.abspath(path) - self.host.bridge.avatarSet(path, self.profile, callback=self._avatarCb, errback=self._avatarEb) + self.host.bridge.avatarSet( + path, self.profile, callback=self._avatarCb, errback=self._avatarEb + ) def _avatarCb(self): self.disp(_("avatar has been set"), 1) self.host.quit() def _avatarEb(self, failure_): - self.disp(_("error while uploading avatar: {msg}").format(msg=failure_), error=True) + self.disp( + _("error while uploading avatar: {msg}").format(msg=failure_), error=True + ) self.host.quit(C.EXIT_ERROR) class Get(base.CommandBase): - def __init__(self, host): - super(Get, self).__init__(host, 'get', use_verbose=True, help=_('retrieve avatar of an entity')) - self.need_loop=True + super(Get, self).__init__( + host, "get", use_verbose=True, help=_("retrieve avatar of an entity") + ) + self.need_loop = True def add_parser_options(self): self.parser.add_argument("jid", type=base.unicode_decoder, help=_("entity")) - self.parser.add_argument("-s", "--show", action="store_true", help=_(u"show avatar")) + self.parser.add_argument( + "-s", "--show", action="store_true", help=_(u"show avatar") + ) def showImage(self, path): sat_conf = config.parseMainConf() - cmd = config.getConfig(sat_conf, 'jp', 'image_cmd') + cmd = config.getConfig(sat_conf, "jp", "image_cmd") cmds = [cmd] + DISPLAY_CMD if cmd else DISPLAY_CMD for cmd in cmds: try: @@ -83,6 +94,7 @@ # didn't worked with commands, we try our luck with webbrowser # in some cases, webbrowser can actually open the associated display program import webbrowser + webbrowser.open(path) def _avatarGetCb(self, avatar_path): @@ -101,11 +113,20 @@ self.host.quit(C.EXIT_ERROR) def start(self): - self.host.bridge.avatarGet(self.args.jid, False, False, self.profile, callback=self._avatarGetCb, errback=self._avatarGetEb) + self.host.bridge.avatarGet( + self.args.jid, + False, + False, + self.profile, + callback=self._avatarGetCb, + errback=self._avatarGetEb, + ) class Avatar(base.CommandBase): subcommands = (Set, Get) def __init__(self, host): - super(Avatar, self).__init__(host, 'avatar', use_profile=False, help=_('avatar uploading/retrieving')) + super(Avatar, self).__init__( + host, "avatar", use_profile=False, help=_("avatar uploading/retrieving") + )
--- a/sat_frontends/jp/cmd_blog.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_blog.py Wed Jun 27 20:14:46 2018 +0200 @@ -40,40 +40,48 @@ __commands__ = ["Blog"] -SYNTAX_XHTML = u'xhtml' +SYNTAX_XHTML = u"xhtml" # extensions to use with known syntaxes SYNTAX_EXT = { - '': 'txt', # used when the syntax is not found + "": "txt", # used when the syntax is not found SYNTAX_XHTML: "xhtml", - "markdown": "md" - } + "markdown": "md", +} -CONF_SYNTAX_EXT = u'syntax_ext_dict' +CONF_SYNTAX_EXT = u"syntax_ext_dict" BLOG_TMP_DIR = u"blog" # key to remove from metadata tmp file if they exist -KEY_TO_REMOVE_METADATA = ('id','content', 'content_xhtml', 'comments_node', 'comments_service', 'updated') +KEY_TO_REMOVE_METADATA = ( + "id", + "content", + "content_xhtml", + "comments_node", + "comments_service", + "updated", +) -URL_REDIRECT_PREFIX = 'url_redirect_' +URL_REDIRECT_PREFIX = "url_redirect_" INOTIFY_INSTALL = '"pip install inotify"' -MB_KEYS = (u"id", - u"url", - u"atom_id", - u"updated", - u"published", - u"language", - u"comments", # this key is used for all comments* keys - u"tags", # this key is used for all tag* keys - u"author", - u"author_jid", - u"author_email", - u"author_jid_verified", - u"content", - u"content_xhtml", - u"title", - u"title_xhtml", - ) -OUTPUT_OPT_NO_HEADER = u'no-header' +MB_KEYS = ( + u"id", + u"url", + u"atom_id", + u"updated", + u"published", + u"language", + u"comments", # this key is used for all comments* keys + u"tags", # this key is used for all tag* keys + u"author", + u"author_jid", + u"author_email", + u"author_jid_verified", + u"content", + u"content_xhtml", + u"title", + u"title_xhtml", +) +OUTPUT_OPT_NO_HEADER = u"no-header" def guessSyntaxFromPath(host, sat_conf, path): @@ -84,9 +92,9 @@ @return(unicode): syntax to use """ # we first try to guess syntax with extension - ext = os.path.splitext(path)[1][1:] # we get extension without the '.' + ext = os.path.splitext(path)[1][1:] # we get extension without the '.' if ext: - for k,v in SYNTAX_EXT.iteritems(): + for k, v in SYNTAX_EXT.iteritems(): if k and ext == v: return k @@ -98,39 +106,61 @@ """handle common option for publising commands (Set and Edit)""" def add_parser_options(self): - self.parser.add_argument("-T", '--title', type=base.unicode_decoder, help=_(u"title of the item")) - self.parser.add_argument("-t", '--tag', type=base.unicode_decoder, action='append', help=_(u"tag (category) of your item")) - self.parser.add_argument("-C", "--comments", action='store_true', help=_(u"enable comments")) - self.parser.add_argument("-S", '--syntax', type=base.unicode_decoder, help=_(u"syntax to use (default: get profile's default syntax)")) + self.parser.add_argument( + "-T", "--title", type=base.unicode_decoder, help=_(u"title of the item") + ) + self.parser.add_argument( + "-t", + "--tag", + type=base.unicode_decoder, + action="append", + help=_(u"tag (category) of your item"), + ) + self.parser.add_argument( + "-C", "--comments", action="store_true", help=_(u"enable comments") + ) + self.parser.add_argument( + "-S", + "--syntax", + type=base.unicode_decoder, + help=_(u"syntax to use (default: get profile's default syntax)"), + ) def setMbDataContent(self, content, mb_data): if self.args.syntax is None: # default syntax has been used - mb_data['content_rich'] = content + mb_data["content_rich"] = content elif self.current_syntax == SYNTAX_XHTML: - mb_data['content_xhtml'] = content + mb_data["content_xhtml"] = content else: - mb_data['content_xhtml'] = self.host.bridge.syntaxConvert(content, self.current_syntax, SYNTAX_XHTML, False, self.profile) + mb_data["content_xhtml"] = self.host.bridge.syntaxConvert( + content, self.current_syntax, SYNTAX_XHTML, False, self.profile + ) def setMbDataFromArgs(self, mb_data): """set microblog metadata according to command line options if metadata already exist, it will be overwritten """ - mb_data['allow_comments'] = C.boolConst(self.args.comments) + mb_data["allow_comments"] = C.boolConst(self.args.comments) if self.args.tag: - data_format.iter2dict('tag', self.args.tag, mb_data, check_conflict=False) + data_format.iter2dict("tag", self.args.tag, mb_data, check_conflict=False) if self.args.title is not None: - mb_data['title'] = self.args.title + mb_data["title"] = self.args.title class Set(base.CommandBase, BlogPublishCommon): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.SINGLE_ITEM}, - help=_(u'publish a new blog item or update an existing one')) + base.CommandBase.__init__( + self, + host, + "set", + use_pubsub=True, + pubsub_flags={C.SINGLE_ITEM}, + help=_(u"publish a new blog item or update an existing one"), + ) BlogPublishCommon.__init__(self) - self.need_loop=True + self.need_loop = True def add_parser_options(self): BlogPublishCommon.add_parser_options(self) @@ -143,7 +173,7 @@ self.pubsub_item = self.args.item mb_data = {} self.setMbDataFromArgs(mb_data) - content = codecs.getreader('utf-8')(sys.stdin).read() + content = codecs.getreader("utf-8")(sys.stdin).read() self.setMbDataContent(content, mb_data) self.host.bridge.mbSend( @@ -152,77 +182,111 @@ mb_data, self.profile, callback=self.exitCb, - errback=partial(self.errback, - msg=_(u"can't send item: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't send item: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class Get(base.CommandBase): TEMPLATE = u"blog/articles.html" def __init__(self, host): - extra_outputs = {'default': self.default_output, - 'fancy': self.fancy_output} - base.CommandBase.__init__(self, host, 'get', use_verbose=True, use_pubsub=True, pubsub_flags={C.MULTI_ITEMS}, - use_output=C.OUTPUT_COMPLEX, extra_outputs=extra_outputs, help=_(u'get blog item(s)')) - self.need_loop=True + extra_outputs = {"default": self.default_output, "fancy": self.fancy_output} + base.CommandBase.__init__( + self, + host, + "get", + use_verbose=True, + use_pubsub=True, + pubsub_flags={C.MULTI_ITEMS}, + use_output=C.OUTPUT_COMPLEX, + extra_outputs=extra_outputs, + help=_(u"get blog item(s)"), + ) + self.need_loop = True def add_parser_options(self): - # TODO: a key(s) argument to select keys to display - self.parser.add_argument("-k", "--key", type=base.unicode_decoder, action='append', dest='keys', - help=_(u"microblog data key(s) to display (default: depend of verbosity)")) + # TODO: a key(s) argument to select keys to display + self.parser.add_argument( + "-k", + "--key", + type=base.unicode_decoder, + action="append", + dest="keys", + help=_(u"microblog data key(s) to display (default: depend of verbosity)"), + ) # TODO: add MAM filters def template_data_mapping(self, data): - return {u'items': data_objects.BlogItems(data)} + return {u"items": data_objects.BlogItems(data)} def format_comments(self, item, keys): - comments_data = data_format.dict2iterdict(u'comments', item, (u'node', u'service'), pop=True) + comments_data = data_format.dict2iterdict( + u"comments", item, (u"node", u"service"), pop=True + ) lines = [] for data in comments_data: - lines.append(data[u'comments']) - for k in (u'node', u'service'): + lines.append(data[u"comments"]) + for k in (u"node", u"service"): if OUTPUT_OPT_NO_HEADER in self.args.output_opts: - header = u'' + header = u"" else: - header = C.A_HEADER + k + u': ' + A.RESET + header = C.A_HEADER + k + u": " + A.RESET lines.append(header + data[k]) - return u'\n'.join(lines) + return u"\n".join(lines) def format_tags(self, item, keys): - tags = data_format.dict2iter('tag', item, pop=True) - return u', '.join(tags) + tags = data_format.dict2iter("tag", item, pop=True) + return u", ".join(tags) def format_updated(self, item, keys): - return self.format_time(item['updated']) + return self.format_time(item["updated"]) def format_published(self, item, keys): - return self.format_time(item['published']) + return self.format_time(item["published"]) def format_url(self, item, keys): - return uri.buildXMPPUri(u'pubsub', - subtype=u'microblog', - path=self.metadata[u'service'], - node=self.metadata[u'node'], - item=item[u'id']) + return uri.buildXMPPUri( + u"pubsub", + subtype=u"microblog", + path=self.metadata[u"service"], + node=self.metadata[u"node"], + item=item[u"id"], + ) def get_keys(self): """return keys to display according to verbosity or explicit key request""" verbosity = self.args.verbose if self.args.keys: if not set(MB_KEYS).issuperset(self.args.keys): - self.disp(u"following keys are invalid: {invalid}.\n" - u"Valid keys are: {valid}.".format( - invalid = u', '.join(set(self.args.keys).difference(MB_KEYS)), - valid = u', '.join(sorted(MB_KEYS))), - error=True) + self.disp( + u"following keys are invalid: {invalid}.\n" + u"Valid keys are: {valid}.".format( + invalid=u", ".join(set(self.args.keys).difference(MB_KEYS)), + valid=u", ".join(sorted(MB_KEYS)), + ), + error=True, + ) self.host.quit(C.EXIT_BAD_ARG) return self.args.keys else: if verbosity == 0: - return (u'title', u'content') + return (u"title", u"content") elif verbosity == 1: - return (u"title", u"tags", u"author", u"author_jid", u"author_email", u"author_jid_verified", u"published", u"updated", u"content") + return ( + u"title", + u"tags", + u"author", + u"author_jid", + u"author_email", + u"author_jid_verified", + u"published", + u"updated", + u"content", + ) else: return MB_KEYS @@ -231,7 +295,7 @@ items, self.metadata = data keys = self.get_keys() - # k_cb use format_[key] methods for complex formattings + # k_cb use format_[key] methods for complex formattings k_cb = {} for k in keys: try: @@ -245,18 +309,19 @@ if k not in item and k not in k_cb: continue if OUTPUT_OPT_NO_HEADER in self.args.output_opts: - header = '' + header = "" else: header = u"{k_fmt}{key}:{k_fmt_e} {sep}".format( - k_fmt = C.A_HEADER, - key = k, - k_fmt_e = A.RESET, - sep = u'\n' if 'content' in k else u'') + k_fmt=C.A_HEADER, + key=k, + k_fmt_e=A.RESET, + sep=u"\n" if "content" in k else u"", + ) value = k_cb[k](item, keys) if k in k_cb else item[k] self.disp(header + value) # we want a separation line after each item but the last one - if idx < len(items)-1: - print(u'') + if idx < len(items) - 1: + print(u"") def format_time(self, timestamp): """return formatted date for timestamp @@ -273,53 +338,53 @@ this output doesn't use keys filter """ # thanks to http://stackoverflow.com/a/943921 - rows, columns = map(int, os.popen('stty size', 'r').read().split()) + rows, columns = map(int, os.popen("stty size", "r").read().split()) items, metadata = data verbosity = self.args.verbose - sep = A.color(A.FG_BLUE, columns * u'▬') + sep = A.color(A.FG_BLUE, columns * u"▬") if items: - print(u'\n' + sep + '\n') + print(u"\n" + sep + "\n") for idx, item in enumerate(items): - title = item.get(u'title') + title = item.get(u"title") if verbosity > 0: - author = item[u'author'] - published, updated = item[u'published'], item.get('updated') + author = item[u"author"] + published, updated = item[u"published"], item.get("updated") else: author = published = updated = None if verbosity > 1: - tags = list(data_format.dict2iter('tag', item, pop=True)) + tags = list(data_format.dict2iter("tag", item, pop=True)) else: tags = None - content = item.get(u'content') + content = item.get(u"content") if title: - print(A.color(A.BOLD, A.FG_CYAN, item[u'title'])) + print(A.color(A.BOLD, A.FG_CYAN, item[u"title"])) meta = [] if author: meta.append(A.color(A.FG_YELLOW, author)) if published: - meta.append(A.color(A.FG_YELLOW, u'on ', self.format_time(published))) + meta.append(A.color(A.FG_YELLOW, u"on ", self.format_time(published))) if updated != published: - meta.append(A.color(A.FG_YELLOW, u'(updated on ', self.format_time(updated), u')')) - print(u' '.join(meta)) + meta.append( + A.color(A.FG_YELLOW, u"(updated on ", self.format_time(updated), u")") + ) + print(u" ".join(meta)) if tags: - print(A.color(A.FG_MAGENTA, u', '.join(tags))) + print(A.color(A.FG_MAGENTA, u", ".join(tags))) if (title or tags) and content: print("") if content: self.disp(content) - print(u'\n' + sep + '\n') - + print(u"\n" + sep + "\n") def mbGetCb(self, mb_result): self.output(mb_result) self.host.quit(C.EXIT_OK) def mbGetEb(self, failure_): - self.disp(u"can't get blog items: {reason}".format( - reason=failure_), error=True) + self.disp(u"can't get blog items: {reason}".format(reason=failure_), error=True) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): @@ -331,26 +396,41 @@ {}, self.profile, callback=self.mbGetCb, - errback=self.mbGetEb) + errback=self.mbGetEb, + ) class Edit(base.CommandBase, BlogPublishCommon, common.BaseEdit): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'edit', use_pubsub=True, pubsub_flags={C.SINGLE_ITEM}, - use_draft=True, use_verbose=True, help=_(u'edit an existing or new blog post')) + base.CommandBase.__init__( + self, + host, + "edit", + use_pubsub=True, + pubsub_flags={C.SINGLE_ITEM}, + use_draft=True, + use_verbose=True, + help=_(u"edit an existing or new blog post"), + ) BlogPublishCommon.__init__(self) common.BaseEdit.__init__(self, self.host, BLOG_TMP_DIR, use_metadata=True) @property def current_syntax(self): if self._current_syntax is None: - self._current_syntax = self.host.bridge.getParamA("Syntax", "Composition", "value", self.profile) + self._current_syntax = self.host.bridge.getParamA( + "Syntax", "Composition", "value", self.profile + ) return self._current_syntax def add_parser_options(self): BlogPublishCommon.add_parser_options(self) - self.parser.add_argument("-P", "--preview", action="store_true", help=_(u"launch a blog preview in parallel")) + self.parser.add_argument( + "-P", + "--preview", + action="store_true", + help=_(u"launch a blog preview in parallel"), + ) def buildMetadataFile(self, content_file_path, mb_data=None): """Build a metadata file using json @@ -367,11 +447,15 @@ if os.path.exists(meta_file_path): self.disp(u"Metadata file already exists, we re-use it") try: - with open(meta_file_path, 'rb') as f: + with open(meta_file_path, "rb") as f: mb_data = json.load(f) except (OSError, IOError, ValueError) as e: - self.disp(u"Can't read existing metadata file at {path}, aborting: {reason}".format( - path=meta_file_path, reason=e), error=True) + self.disp( + u"Can't read existing metadata file at {path}, aborting: {reason}".format( + path=meta_file_path, reason=e + ), + error=True, + ) self.host.quit(1) else: mb_data = {} if mb_data is None else mb_data.copy() @@ -387,15 +471,22 @@ # then we create the file and write metadata there, as JSON dict # XXX: if we port jp one day on Windows, O_BINARY may need to be added here - with os.fdopen(os.open(meta_file_path, os.O_RDWR | os.O_CREAT | os.O_TRUNC,0o600), 'w+b') as f: + with os.fdopen( + os.open(meta_file_path, os.O_RDWR | os.O_CREAT | os.O_TRUNC, 0o600), "w+b" + ) as f: # we need to use an intermediate unicode buffer to write to the file unicode without escaping characters - unicode_dump = json.dumps(mb_data, ensure_ascii=False, indent=4, separators=(',', ': '), sort_keys=True) - f.write(unicode_dump.encode('utf-8')) + unicode_dump = json.dumps( + mb_data, + ensure_ascii=False, + indent=4, + separators=(",", ": "), + sort_keys=True, + ) + f.write(unicode_dump.encode("utf-8")) return mb_data, meta_file_path - def edit(self, content_file_path, content_file_obj, - mb_data=None): + def edit(self, content_file_path, content_file_obj, mb_data=None): """Edit the file contening the content using editor, and publish it""" # we first create metadata file meta_ori, meta_file_path = self.buildMetadataFile(content_file_path, mb_data) @@ -405,37 +496,61 @@ self.disp(u"Preview requested, launching it", 1) # we redirect outputs to /dev/null to avoid console pollution in editor # if user wants to see messages, (s)he can call "blog preview" directly - DEVNULL = open(os.devnull, 'wb') - subprocess.Popen([sys.argv[0], "blog", "preview", "--inotify", "true", "-p", self.profile, content_file_path], stdout=DEVNULL, stderr=subprocess.STDOUT) + DEVNULL = open(os.devnull, "wb") + subprocess.Popen( + [ + sys.argv[0], + "blog", + "preview", + "--inotify", + "true", + "-p", + self.profile, + content_file_path, + ], + stdout=DEVNULL, + stderr=subprocess.STDOUT, + ) # we launch editor - self.runEditor("blog_editor_args", content_file_path, content_file_obj, meta_file_path=meta_file_path, meta_ori=meta_ori) + self.runEditor( + "blog_editor_args", + content_file_path, + content_file_obj, + meta_file_path=meta_file_path, + meta_ori=meta_ori, + ) def publish(self, content, mb_data): self.setMbDataContent(content, mb_data) if self.pubsub_item is not None: - mb_data['id'] = self.pubsub_item + mb_data["id"] = self.pubsub_item - self.host.bridge.mbSend(self.pubsub_service, self.pubsub_node, mb_data, self.profile) + self.host.bridge.mbSend( + self.pubsub_service, self.pubsub_node, mb_data, self.profile + ) self.disp(u"Blog item published") - def getTmpSuff(self): # we get current syntax to determine file extension - return SYNTAX_EXT.get(self.current_syntax, SYNTAX_EXT['']) + return SYNTAX_EXT.get(self.current_syntax, SYNTAX_EXT[""]) def getItemData(self, service, node, item): items = [item] if item is not None else [] - mb_data = self.host.bridge.mbGet(service, node, 1, items, {}, self.profile)[0][0] + mb_data = self.host.bridge.mbGet(service, node, 1, items, {}, self.profile)[0][0] try: - content = mb_data['content_xhtml'] + content = mb_data["content_xhtml"] except KeyError: - content = mb_data['content'] + content = mb_data["content"] if content: - content = self.host.bridge.syntaxConvert(content, 'text', SYNTAX_XHTML, False, self.profile) + content = self.host.bridge.syntaxConvert( + content, "text", SYNTAX_XHTML, False, self.profile + ) if content and self.current_syntax != SYNTAX_XHTML: - content = self.host.bridge.syntaxConvert(content, SYNTAX_XHTML, self.current_syntax, False, self.profile) + content = self.host.bridge.syntaxConvert( + content, SYNTAX_XHTML, self.current_syntax, False, self.profile + ) if content and self.current_syntax == SYNTAX_XHTML: try: from lxml import etree @@ -446,22 +561,37 @@ root = etree.fromstring(content, parser) content = etree.tostring(root, encoding=unicode, pretty_print=True) - return content, mb_data, mb_data['id'] + return content, mb_data, mb_data["id"] def start(self): # if there are user defined extension, we use them - SYNTAX_EXT.update(config.getConfig(self.sat_conf, 'jp', CONF_SYNTAX_EXT, {})) + SYNTAX_EXT.update(config.getConfig(self.sat_conf, "jp", CONF_SYNTAX_EXT, {})) self._current_syntax = self.args.syntax if self._current_syntax is not None: try: - self._current_syntax = self.args.syntax = self.host.bridge.syntaxGet(self.current_syntax) + self._current_syntax = self.args.syntax = self.host.bridge.syntaxGet( + self.current_syntax + ) except Exception as e: - if "NotFound" in unicode(e): # FIXME: there is not good way to check bridge errors - self.parser.error(_(u"unknown syntax requested ({syntax})").format(syntax=self.args.syntax)) + if "NotFound" in unicode( + e + ): # FIXME: there is not good way to check bridge errors + self.parser.error( + _(u"unknown syntax requested ({syntax})").format( + syntax=self.args.syntax + ) + ) else: raise e - self.pubsub_service, self.pubsub_node, self.pubsub_item, content_file_path, content_file_obj, mb_data = self.getItemPath() + ( + self.pubsub_service, + self.pubsub_node, + self.pubsub_item, + content_file_path, + content_file_obj, + mb_data, + ) = self.getItemPath() self.edit(content_file_path, content_file_obj, mb_data=mb_data) @@ -470,23 +600,42 @@ # TODO: need to be rewritten with template output def __init__(self, host): - base.CommandBase.__init__(self, host, 'preview', use_verbose=True, help=_(u'preview a blog content')) + base.CommandBase.__init__( + self, host, "preview", use_verbose=True, help=_(u"preview a blog content") + ) def add_parser_options(self): - self.parser.add_argument("--inotify", type=str, choices=('auto', 'true', 'false'), default=u'auto', help=_(u"use inotify to handle preview")) - self.parser.add_argument("file", type=base.unicode_decoder, nargs='?', default=u'current', help=_(u"path to the content file")) + self.parser.add_argument( + "--inotify", + type=str, + choices=("auto", "true", "false"), + default=u"auto", + help=_(u"use inotify to handle preview"), + ) + self.parser.add_argument( + "file", + type=base.unicode_decoder, + nargs="?", + default=u"current", + help=_(u"path to the content file"), + ) def showPreview(self): # we implement showPreview here so we don't have to import webbrowser and urllib # when preview is not used - url = 'file:{}'.format(self.urllib.quote(self.preview_file_path)) + url = "file:{}".format(self.urllib.quote(self.preview_file_path)) self.webbrowser.open_new_tab(url) def _launchPreviewExt(self, cmd_line, opt_name): - url = 'file:{}'.format(self.urllib.quote(self.preview_file_path)) - args = common.parse_args(self.host, cmd_line, url=url, preview_file=self.preview_file_path) + url = "file:{}".format(self.urllib.quote(self.preview_file_path)) + args = common.parse_args( + self.host, cmd_line, url=url, preview_file=self.preview_file_path + ) if not args: - self.disp(u"Couln't find command in \"{name}\", abording".format(name=opt_name), error=True) + self.disp( + u'Couln\'t find command in "{name}", abording'.format(name=opt_name), + error=True, + ) self.host.quit(1) subprocess.Popen(args) @@ -497,91 +646,121 @@ self._launchPreviewExt(self.update_cb_cmd, "blog_preview_update_cmd") def updateContent(self): - with open(self.content_file_path, 'rb') as f: - content = f.read().decode('utf-8-sig') + with open(self.content_file_path, "rb") as f: + content = f.read().decode("utf-8-sig") if content and self.syntax != SYNTAX_XHTML: - # we use safe=True because we want to have a preview as close as possible to what the - # people will see - content = self.host.bridge.syntaxConvert(content, self.syntax, SYNTAX_XHTML, True, self.profile) + # we use safe=True because we want to have a preview as close as possible + # to what the people will see + content = self.host.bridge.syntaxConvert( + content, self.syntax, SYNTAX_XHTML, True, self.profile + ) - xhtml = (u'<html xmlns="http://www.w3.org/1999/xhtml">' + - u'<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /></head>'+ - '<body>{}</body>' + - u'</html>').format(content) + xhtml = ( + u'<html xmlns="http://www.w3.org/1999/xhtml">' + u'<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" />' + u"</head>" + u"<body>{}</body>" + u"</html>" + ).format(content) - with open(self.preview_file_path, 'wb') as f: - f.write(xhtml.encode('utf-8')) + with open(self.preview_file_path, "wb") as f: + f.write(xhtml.encode("utf-8")) def start(self): import webbrowser import urllib + self.webbrowser, self.urllib = webbrowser, urllib - if self.args.inotify != 'false': + if self.args.inotify != "false": try: import inotify.adapters import inotify.constants from inotify.calls import InotifyError except ImportError: - if self.args.inotify == 'auto': + if self.args.inotify == "auto": inotify = None - self.disp(u'inotify module not found, deactivating feature. You can install it with {install}'.format(install=INOTIFY_INSTALL)) + self.disp( + u"inotify module not found, deactivating feature. You can install" + u" it with {install}".format(install=INOTIFY_INSTALL) + ) else: - self.disp(u"inotify not found, can't activate the feature! Please install it with {install}".format(install=INOTIFY_INSTALL), error=True) + self.disp( + u"inotify not found, can't activate the feature! Please install " + u"it with {install}".format(install=INOTIFY_INSTALL), + error=True, + ) self.host.quit(1) else: # we deactivate logging in inotify, which is quite annoying try: inotify.adapters._LOGGER.setLevel(40) except AttributeError: - self.disp(u"Logger doesn't exists, inotify may have chanded", error=True) + self.disp( + u"Logger doesn't exists, inotify may have chanded", error=True + ) else: - inotify=None + inotify = None sat_conf = config.parseMainConf() - SYNTAX_EXT.update(config.getConfig(sat_conf, 'jp', CONF_SYNTAX_EXT, {})) + SYNTAX_EXT.update(config.getConfig(sat_conf, "jp", CONF_SYNTAX_EXT, {})) try: - self.open_cb_cmd = config.getConfig(sat_conf, 'jp', "blog_preview_open_cmd", Exception) + self.open_cb_cmd = config.getConfig( + sat_conf, "jp", "blog_preview_open_cmd", Exception + ) except (NoOptionError, NoSectionError): self.open_cb_cmd = None open_cb = self.showPreview else: open_cb = self.openPreviewExt - self.update_cb_cmd = config.getConfig(sat_conf, 'jp', "blog_preview_update_cmd", self.open_cb_cmd) + self.update_cb_cmd = config.getConfig( + sat_conf, "jp", "blog_preview_update_cmd", self.open_cb_cmd + ) if self.update_cb_cmd is None: update_cb = self.showPreview else: update_cb = self.updatePreviewExt # which file do we need to edit? - if self.args.file == 'current': + if self.args.file == "current": self.content_file_path = self.getCurrentFile(sat_conf, self.profile) else: self.content_file_path = os.path.abspath(self.args.file) self.syntax = guessSyntaxFromPath(self.host, sat_conf, self.content_file_path) - # at this point the syntax is converted, we can display the preview - preview_file = tempfile.NamedTemporaryFile(suffix='.xhtml', delete=False) + preview_file = tempfile.NamedTemporaryFile(suffix=".xhtml", delete=False) self.preview_file_path = preview_file.name preview_file.close() self.updateContent() if inotify is None: - # XXX: we don't delete file automatically because browser need it (and webbrowser.open can return before it is read) - self.disp(u'temporary file created at {}\nthis file will NOT BE DELETED AUTOMATICALLY, please delete it yourself when you have finished'.format(self.preview_file_path)) + # XXX: we don't delete file automatically because browser need it + # (and webbrowser.open can return before it is read) + self.disp( + u"temporary file created at {}\nthis file will NOT BE DELETED " + u"AUTOMATICALLY, please delete it yourself when you have finished".format( + self.preview_file_path + ) + ) open_cb() else: open_cb() - i = inotify.adapters.Inotify(block_duration_s=60) # no need for 1 s duraction, inotify drive actions here + i = inotify.adapters.Inotify( + block_duration_s=60 + ) # no need for 1 s duraction, inotify drive actions here def add_watch(): - i.add_watch(self.content_file_path, mask=inotify.constants.IN_CLOSE_WRITE | - inotify.constants.IN_DELETE_SELF | - inotify.constants.IN_MOVE_SELF) + i.add_watch( + self.content_file_path, + mask=inotify.constants.IN_CLOSE_WRITE + | inotify.constants.IN_DELETE_SELF + | inotify.constants.IN_MOVE_SELF, + ) + add_watch() try: @@ -589,7 +768,12 @@ if event is not None: self.disp(u"Content updated", 1) if {"IN_DELETE_SELF", "IN_MOVE_SELF"}.intersection(event[1]): - self.disp(u"{} event catched, changing the watch".format(", ".join(event[1])), 2) + self.disp( + u"{} event catched, changing the watch".format( + ", ".join(event[1]) + ), + 2, + ) try: add_watch() except InotifyError: @@ -600,7 +784,9 @@ self.updateContent() update_cb() except InotifyError: - self.disp(u"Can't catch inotify events, as the file been deleted?", error=True) + self.disp( + u"Can't catch inotify events, as the file been deleted?", error=True + ) finally: os.unlink(self.preview_file_path) try: @@ -611,81 +797,174 @@ class Import(base.CommandAnswering): def __init__(self, host): - super(Import, self).__init__(host, 'import', use_pubsub=True, use_progress=True, help=_(u'import an external blog')) - self.need_loop=True + super(Import, self).__init__( + host, + "import", + use_pubsub=True, + use_progress=True, + help=_(u"import an external blog"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("importer", type=base.unicode_decoder, nargs='?', help=_(u"importer name, nothing to display importers list")) - self.parser.add_argument('--host', type=base.unicode_decoder, help=_(u"original blog host")) - self.parser.add_argument('--no-images-upload', action='store_true', help=_(u"do *NOT* upload images (default: do upload images)")) - self.parser.add_argument('--upload-ignore-host', help=_(u"do not upload images from this host (default: upload all images)")) - self.parser.add_argument("--ignore-tls-errors", action="store_true", help=_("ignore invalide TLS certificate for uploads")) - self.parser.add_argument('-o', '--option', action='append', nargs=2, default=[], metavar=(u'NAME', u'VALUE'), - help=_(u"importer specific options (see importer description)")) - self.parser.add_argument("location", type=base.unicode_decoder, nargs='?', - help=_(u"importer data location (see importer description), nothing to show importer description")) + self.parser.add_argument( + "importer", + type=base.unicode_decoder, + nargs="?", + help=_(u"importer name, nothing to display importers list"), + ) + self.parser.add_argument( + "--host", type=base.unicode_decoder, help=_(u"original blog host") + ) + self.parser.add_argument( + "--no-images-upload", + action="store_true", + help=_(u"do *NOT* upload images (default: do upload images)"), + ) + self.parser.add_argument( + "--upload-ignore-host", + help=_(u"do not upload images from this host (default: upload all images)"), + ) + self.parser.add_argument( + "--ignore-tls-errors", + action="store_true", + help=_("ignore invalide TLS certificate for uploads"), + ) + self.parser.add_argument( + "-o", + "--option", + action="append", + nargs=2, + default=[], + metavar=(u"NAME", u"VALUE"), + help=_(u"importer specific options (see importer description)"), + ) + self.parser.add_argument( + "location", + type=base.unicode_decoder, + nargs="?", + help=_( + u"importer data location (see importer description), nothing to show " + u"importer description" + ), + ) def onProgressStarted(self, metadata): - self.disp(_(u'Blog upload started'),2) + self.disp(_(u"Blog upload started"), 2) def onProgressFinished(self, metadata): - self.disp(_(u'Blog uploaded successfully'),2) - redirections = {k[len(URL_REDIRECT_PREFIX):]:v for k,v in metadata.iteritems() - if k.startswith(URL_REDIRECT_PREFIX)} + self.disp(_(u"Blog uploaded successfully"), 2) + redirections = { + k[len(URL_REDIRECT_PREFIX) :]: v + for k, v in metadata.iteritems() + if k.startswith(URL_REDIRECT_PREFIX) + } if redirections: - conf = u'\n'.join([ - u'url_redirections_profile = {}'.format(self.profile), - u"url_redirections_dict = {}".format( - # we need to add ' ' before each new line and to double each '%' for ConfigParser - u'\n '.join(json.dumps(redirections, indent=1, separators=(',',': ')).replace(u'%', u'%%').split(u'\n'))), - ]) - self.disp(_(u'\nTo redirect old URLs to new ones, put the following lines in your sat.conf file, in [libervia] section:\n\n{conf}'.format(conf=conf))) + conf = u"\n".join( + [ + u"url_redirections_profile = {}".format(self.profile), + u"url_redirections_dict = {}".format( + # we need to add ' ' before each new line + # and to double each '%' for ConfigParser + u"\n ".join( + json.dumps(redirections, indent=1, separators=(",", ": ")) + .replace(u"%", u"%%") + .split(u"\n") + ) + ), + ] + ) + self.disp( + _( + u"\nTo redirect old URLs to new ones, put the following lines in your" + u" sat.conf file, in [libervia] section:\n\n{conf}".format(conf=conf) + ) + ) def onProgressError(self, error_msg): - self.disp(_(u'Error while uploading blog: {}').format(error_msg),error=True) + self.disp(_(u"Error while uploading blog: {}").format(error_msg), error=True) def error(self, failure): - self.disp(_("Error while trying to upload a blog: {reason}").format(reason=failure), error=True) + self.disp( + _("Error while trying to upload a blog: {reason}").format(reason=failure), + error=True, + ) self.host.quit(1) def start(self): if self.args.location is None: - for name in ('option', 'service', 'no_images_upload'): + for name in ("option", "service", "no_images_upload"): if getattr(self.args, name): - self.parser.error(_(u"{name} argument can't be used without location argument").format(name=name)) + self.parser.error( + _( + u"{name} argument can't be used without location argument" + ).format(name=name) + ) if self.args.importer is None: - self.disp(u'\n'.join([u'{}: {}'.format(name, desc) for name, desc in self.host.bridge.blogImportList()])) + self.disp( + u"\n".join( + [ + u"{}: {}".format(name, desc) + for name, desc in self.host.bridge.blogImportList() + ] + ) + ) else: try: - short_desc, long_desc = self.host.bridge.blogImportDesc(self.args.importer) + short_desc, long_desc = self.host.bridge.blogImportDesc( + self.args.importer + ) except Exception as e: - msg = [l for l in unicode(e).split('\n') if l][-1] # we only keep the last line + msg = [l for l in unicode(e).split("\n") if l][ + -1 + ] # we only keep the last line self.disp(msg) self.host.quit(1) else: - self.disp(u"{name}: {short_desc}\n\n{long_desc}".format(name=self.args.importer, short_desc=short_desc, long_desc=long_desc)) + self.disp( + u"{name}: {short_desc}\n\n{long_desc}".format( + name=self.args.importer, + short_desc=short_desc, + long_desc=long_desc, + ) + ) self.host.quit() else: # we have a location, an import is requested options = {key: value for key, value in self.args.option} if self.args.host: - options['host'] = self.args.host + options["host"] = self.args.host if self.args.ignore_tls_errors: - options['ignore_tls_errors'] = C.BOOL_TRUE + options["ignore_tls_errors"] = C.BOOL_TRUE if self.args.no_images_upload: - options['upload_images'] = C.BOOL_FALSE + options["upload_images"] = C.BOOL_FALSE if self.args.upload_ignore_host: - self.parser.error(u"upload-ignore-host option can't be used when no-images-upload is set") + self.parser.error( + u"upload-ignore-host option can't be used when no-images-upload " + u"is set" + ) elif self.args.upload_ignore_host: - options['upload_ignore_host'] = self.args.upload_ignore_host + options["upload_ignore_host"] = self.args.upload_ignore_host + def gotId(id_): self.progress_id = id_ - self.host.bridge.blogImport(self.args.importer, self.args.location, options, self.args.service, self.args.node, self.profile, - callback=gotId, errback=self.error) + + self.host.bridge.blogImport( + self.args.importer, + self.args.location, + options, + self.args.service, + self.args.node, + self.profile, + callback=gotId, + errback=self.error, + ) class Blog(base.CommandBase): subcommands = (Set, Get, Edit, Preview, Import) def __init__(self, host): - super(Blog, self).__init__(host, 'blog', use_profile=False, help=_('blog/microblog management')) + super(Blog, self).__init__( + host, "blog", use_profile=False, help=_("blog/microblog management") + )
--- a/sat_frontends/jp/cmd_debug.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_debug.py Wed Jun 27 20:14:46 2018 +0200 @@ -28,32 +28,35 @@ class BridgeCommon(object): - def evalArgs(self): if self.args.arg: try: - return eval(u'[{}]'.format(u",".join(self.args.arg))) + return eval(u"[{}]".format(u",".join(self.args.arg))) except SyntaxError as e: - self.disp(u"Can't evaluate arguments: {mess}\n{text}\n{offset}^".format( - mess=e, - text=e.text.decode('utf-8'), - offset=u" "*(e.offset-1) - ), error=True) + self.disp( + u"Can't evaluate arguments: {mess}\n{text}\n{offset}^".format( + mess=e, text=e.text.decode("utf-8"), offset=u" " * (e.offset - 1) + ), + error=True, + ) self.host.quit(C.EXIT_BAD_ARG) else: return [] class Method(base.CommandBase, BridgeCommon): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'method', help=_(u'call a bridge method')) + base.CommandBase.__init__(self, host, "method", help=_(u"call a bridge method")) BridgeCommon.__init__(self) - self.need_loop=True + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("method", type=str, help=_(u"name of the method to execute")) - self.parser.add_argument("arg", type=base.unicode_decoder, nargs="*", help=_(u"argument of the method")) + self.parser.add_argument( + "method", type=str, help=_(u"name of the method to execute") + ) + self.parser.add_argument( + "arg", type=base.unicode_decoder, nargs="*", help=_(u"argument of the method") + ) def method_cb(self, ret=None): if ret is not None: @@ -61,14 +64,22 @@ self.host.quit() def method_eb(self, failure): - self.disp(_(u"Error while executing {}: {}".format(self.args.method, failure)), error=True) + self.disp( + _(u"Error while executing {}: {}".format(self.args.method, failure)), + error=True, + ) self.host.quit(C.EXIT_ERROR) def start(self): method = getattr(self.host.bridge, self.args.method) args = self.evalArgs() try: - method(*args, profile=self.profile, callback=self.method_cb, errback=self.method_eb) + method( + *args, + profile=self.profile, + callback=self.method_cb, + errback=self.method_eb + ) except TypeError: # maybe the method doesn't need a profile ? try: @@ -78,14 +89,19 @@ class Signal(base.CommandBase, BridgeCommon): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'signal', help=_(u'send a fake signal from backend')) + base.CommandBase.__init__( + self, host, "signal", help=_(u"send a fake signal from backend") + ) BridgeCommon.__init__(self) def add_parser_options(self): - self.parser.add_argument("signal", type=str, help=_(u"name of the signal to send")) - self.parser.add_argument("arg", type=base.unicode_decoder, nargs="*", help=_(u"argument of the signal")) + self.parser.add_argument( + "signal", type=str, help=_(u"name of the signal to send") + ) + self.parser.add_argument( + "arg", type=base.unicode_decoder, nargs="*", help=_(u"argument of the signal") + ) def start(self): args = self.evalArgs() @@ -99,27 +115,36 @@ subcommands = (Method, Signal) def __init__(self, host): - super(Bridge, self).__init__(host, 'bridge', use_profile=False, help=_('bridge s(t)imulation')) + super(Bridge, self).__init__( + host, "bridge", use_profile=False, help=_("bridge s(t)imulation") + ) class Monitor(base.CommandBase): - def __init__(self, host): - super(Monitor, self).__init__(host, - 'monitor', - use_verbose=True, - use_profile=False, - use_output=C.OUTPUT_XML, - help=_('monitor XML stream')) + super(Monitor, self).__init__( + host, + "monitor", + use_verbose=True, + use_profile=False, + use_output=C.OUTPUT_XML, + help=_("monitor XML stream"), + ) self.need_loop = True def add_parser_options(self): - self.parser.add_argument("-d", "--direction", choices=('in', 'out', 'both'), default='both', help=_(u"stream direction filter")) + self.parser.add_argument( + "-d", + "--direction", + choices=("in", "out", "both"), + default="both", + help=_(u"stream direction filter"), + ) def printXML(self, direction, xml_data, profile): - if self.args.direction == 'in' and direction != 'IN': + if self.args.direction == "in" and direction != "IN": return - if self.args.direction == 'out' and direction != 'OUT': + if self.args.direction == "out" and direction != "OUT": return verbosity = self.host.verbosity if not xml_data.strip(): @@ -130,31 +155,41 @@ whiteping = False if verbosity: - profile_disp = u' ({})'.format(profile) if verbosity>1 else u'' - if direction == 'IN': - self.disp(A.color(A.BOLD, A.FG_YELLOW, '<<<===== IN ====', A.FG_WHITE, profile_disp)) + profile_disp = u" ({})".format(profile) if verbosity > 1 else u"" + if direction == "IN": + self.disp( + A.color( + A.BOLD, A.FG_YELLOW, "<<<===== IN ====", A.FG_WHITE, profile_disp + ) + ) else: - self.disp(A.color(A.BOLD, A.FG_CYAN, '==== OUT ====>>>', A.FG_WHITE, profile_disp)) + self.disp( + A.color( + A.BOLD, A.FG_CYAN, "==== OUT ====>>>", A.FG_WHITE, profile_disp + ) + ) if whiteping: - self.disp('[WHITESPACE PING]') + self.disp("[WHITESPACE PING]") else: try: self.output(xml_data) except Exception: - # initial stream is not valid XML, + # initial stream is not valid XML, # in this case we print directly to data - # FIXME: we should test directly lxml.etree.XMLSyntaxError + # FIXME: we should test directly lxml.etree.XMLSyntaxError # but importing lxml directly here is not clean # should be wrapped in a custom Exception self.disp(xml_data) - self.disp(u'') + self.disp(u"") def start(self): - self.host.bridge.register_signal('xmlLog', self.printXML, 'plugin') + self.host.bridge.register_signal("xmlLog", self.printXML, "plugin") class Debug(base.CommandBase): subcommands = (Bridge, Monitor) def __init__(self, host): - super(Debug, self).__init__(host, 'debug', use_profile=False, help=_('debugging tools')) + super(Debug, self).__init__( + host, "debug", use_profile=False, help=_("debugging tools") + )
--- a/sat_frontends/jp/cmd_event.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_event.py Wed Jun 27 20:14:46 2018 +0200 @@ -30,29 +30,31 @@ __commands__ = ["Event"] -OUTPUT_OPT_TABLE = u'table' +OUTPUT_OPT_TABLE = u"table" # TODO: move date parsing to base, it may be useful for other commands class Get(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, - host, - 'get', - use_output=C.OUTPUT_DICT, - use_pubsub=True, pubsub_flags={C.NODE, C.ITEM, C.SINGLE_ITEM}, - use_verbose=True, - help=_(u'get event data')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "get", + use_output=C.OUTPUT_DICT, + use_pubsub=True, + pubsub_flags={C.NODE, C.ITEM, C.SINGLE_ITEM}, + use_verbose=True, + help=_(u"get event data"), + ) + self.need_loop = True def add_parser_options(self): pass def eventInviteeGetCb(self, result): event_date, event_data = result - event_data['date'] = event_date + event_data["date"] = event_date self.output(event_data) self.host.quit() @@ -63,18 +65,36 @@ self.args.item, self.profile, callback=self.eventInviteeGetCb, - errback=partial(self.errback, - msg=_(u"can't get event data: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't get event data: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class EventBase(object): - def add_parser_options(self): - self.parser.add_argument("-i", "--id", type=base.unicode_decoder, default=u'', help=_(u"ID of the PubSub Item")) - self.parser.add_argument("-d", "--date", type=unicode, help=_(u"date of the event")) - self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields', - metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set")) + self.parser.add_argument( + "-i", + "--id", + type=base.unicode_decoder, + default=u"", + help=_(u"ID of the PubSub Item"), + ) + self.parser.add_argument( + "-d", "--date", type=unicode, help=_(u"date of the event") + ) + self.parser.add_argument( + "-f", + "--field", + type=base.unicode_decoder, + action="append", + nargs=2, + dest="fields", + metavar=(u"KEY", u"VALUE"), + help=_(u"configuration field to set"), + ) def parseFields(self): return dict(self.args.fields) if self.args.fields else {} @@ -85,7 +105,9 @@ date = int(self.args.date) except ValueError: try: - date_time = du_parser.parse(self.args.date, dayfirst=not (u'-' in self.args.date)) + date_time = du_parser.parse( + self.args.date, dayfirst=not (u"-" in self.args.date) + ) except ValueError as e: self.parser.error(_(u"Can't parse date: {msg}").format(msg=e)) if date_time.tzinfo is None: @@ -99,12 +121,18 @@ class Create(EventBase, base.CommandBase): def __init__(self, host): - super(Create, self).__init__(host, 'create', use_pubsub=True, pubsub_flags={C.NODE}, help=_('create or replace event')) + super(Create, self).__init__( + host, + "create", + use_pubsub=True, + pubsub_flags={C.NODE}, + help=_("create or replace event"), + ) EventBase.__init__(self) - self.need_loop=True + self.need_loop = True def eventCreateCb(self, node): - self.disp(_(u'Event created successfuly on node {node}').format(node=node)) + self.disp(_(u"Event created successfuly on node {node}").format(node=node)) self.host.quit() def start(self): @@ -118,16 +146,25 @@ self.args.id, self.profile, callback=self.eventCreateCb, - errback=partial(self.errback, - msg=_(u"can't create event: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't create event: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class Modify(EventBase, base.CommandBase): def __init__(self, host): - super(Modify, self).__init__(host, 'modify', use_pubsub=True, pubsub_flags={C.NODE}, help=_('modify an existing event')) + super(Modify, self).__init__( + host, + "modify", + use_pubsub=True, + pubsub_flags={C.NODE}, + help=_("modify an existing event"), + ) EventBase.__init__(self) - self.need_loop=True + self.need_loop = True def start(self): fields = self.parseFields() @@ -140,22 +177,27 @@ fields, self.profile, callback=self.host.quit, - errback=partial(self.errback, - msg=_(u"can't update event data: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't update event data: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class InviteeGet(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, - host, - 'get', - use_output=C.OUTPUT_DICT, - use_pubsub=True, pubsub_flags={C.NODE, C.ITEM, C.SINGLE_ITEM}, - use_verbose=True, - help=_(u'get event attendance')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "get", + use_output=C.OUTPUT_DICT, + use_pubsub=True, + pubsub_flags={C.NODE, C.ITEM, C.SINGLE_ITEM}, + use_verbose=True, + help=_(u"get event attendance"), + ) + self.need_loop = True def add_parser_options(self): pass @@ -170,19 +212,37 @@ self.args.node, self.profile, callback=self.eventInviteeGetCb, - errback=partial(self.errback, - msg=_(u"can't get event data: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't get event data: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class InviteeSet(base.CommandBase): def __init__(self, host): - super(InviteeSet, self).__init__(host, 'set', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, help=_('set event attendance')) - self.need_loop=True + super(InviteeSet, self).__init__( + host, + "set", + use_output=C.OUTPUT_DICT, + use_pubsub=True, + pubsub_flags={C.NODE}, + help=_("set event attendance"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields', - metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set")) + self.parser.add_argument( + "-f", + "--field", + type=base.unicode_decoder, + action="append", + nargs=2, + dest="fields", + metavar=(u"KEY", u"VALUE"), + help=_(u"configuration field to set"), + ) def start(self): fields = dict(self.args.fields) if self.args.fields else {} @@ -192,40 +252,55 @@ fields, self.profile, callback=self.host.quit, - errback=partial(self.errback, - msg=_(u"can't set event data: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't set event data: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class InviteesList(base.CommandBase): - def __init__(self, host): - extra_outputs = {'default': self.default_output} - base.CommandBase.__init__(self, - host, - 'list', - use_output=C.OUTPUT_DICT_DICT, - extra_outputs=extra_outputs, - use_pubsub=True, pubsub_flags={C.NODE}, - use_verbose=True, - help=_(u'get event attendance')) - self.need_loop=True + extra_outputs = {"default": self.default_output} + base.CommandBase.__init__( + self, + host, + "list", + use_output=C.OUTPUT_DICT_DICT, + extra_outputs=extra_outputs, + use_pubsub=True, + pubsub_flags={C.NODE}, + use_verbose=True, + help=_(u"get event attendance"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument('-m', '--missing', action='store_true', help=_(u'show missing people (invited but no R.S.V.P. so far)')) - self.parser.add_argument('-R', '--no-rsvp', action='store_true', help=_(u"don't show people which gave R.S.V.P.")) + self.parser.add_argument( + "-m", + "--missing", + action="store_true", + help=_(u"show missing people (invited but no R.S.V.P. so far)"), + ) + self.parser.add_argument( + "-R", + "--no-rsvp", + action="store_true", + help=_(u"don't show people which gave R.S.V.P."), + ) def _attend_filter(self, attend, row): - if attend == u'yes': + if attend == u"yes": attend_color = C.A_SUCCESS - elif attend == u'no': + elif attend == u"no": attend_color = C.A_FAILURE else: attend_color = A.FG_WHITE return A.color(attend_color, attend) def _guests_filter(self, guests): - return u'(' + unicode(guests) + ')' if guests else u'' + return u"(" + unicode(guests) + ")" if guests else u"" def default_output(self, event_data): data = [] @@ -236,74 +311,97 @@ guests = 0 guests_maybe = 0 for jid_, jid_data in event_data.iteritems(): - jid_data[u'jid'] = jid_ + jid_data[u"jid"] = jid_ try: - guests_int = int(jid_data['guests']) + guests_int = int(jid_data["guests"]) except (ValueError, KeyError): pass - attend = jid_data.get(u'attend',u'') - if attend == 'yes': + attend = jid_data.get(u"attend", u"") + if attend == "yes": attendees_yes += 1 guests += guests_int - elif attend == 'maybe': + elif attend == "maybe": attendees_maybe += 1 guests_maybe += guests_int - elif attend == 'no': + elif attend == "no": attendees_no += 1 - jid_data[u'guests'] = '' + jid_data[u"guests"] = "" else: attendees_missing += 1 - jid_data[u'guests'] = '' + jid_data[u"guests"] = "" data.append(jid_data) show_table = OUTPUT_OPT_TABLE in self.args.output_opts - table = common.Table.fromDict(self.host, + table = common.Table.fromDict( + self.host, data, - (u'nick',) + ((u'jid',) if self.host.verbosity else ()) + (u'attend', 'guests'), + (u"nick",) + + ((u"jid",) if self.host.verbosity else ()) + + (u"attend", "guests"), headers=None, - filters = { u'nick': A.color(C.A_HEADER, u'{}' if show_table else u'{} '), - u'jid': u'{}' if show_table else u'{} ', - u'attend': self._attend_filter, - u'guests': u'{}' if show_table else self._guests_filter, - }, - defaults = { u'nick': u'', - u'attend': u'', - u'guests': 1 - } - ) + filters={ + u"nick": A.color(C.A_HEADER, u"{}" if show_table else u"{} "), + u"jid": u"{}" if show_table else u"{} ", + u"attend": self._attend_filter, + u"guests": u"{}" if show_table else self._guests_filter, + }, + defaults={u"nick": u"", u"attend": u"", u"guests": 1}, + ) if show_table: table.display() else: - table.display_blank(show_header=False, col_sep=u'') + table.display_blank(show_header=False, col_sep=u"") if not self.args.no_rsvp: - self.disp(u'') - self.disp(A.color( - C.A_SUBHEADER, - _(u'Attendees: '), - A.RESET, - unicode(len(data)), - _(u' ('), - C.A_SUCCESS, - _(u'yes: '), - unicode(attendees_yes), - A.FG_WHITE, - _(u', maybe: '), - unicode(attendees_maybe), - u', ', - C.A_FAILURE, - _(u'no: '), - unicode(attendees_no), - A.RESET, - u')' - )) - self.disp(A.color(C.A_SUBHEADER, _(u'confirmed guests: '), A.RESET, unicode(guests))) - self.disp(A.color(C.A_SUBHEADER, _(u'unconfirmed guests: '), A.RESET, unicode(guests_maybe))) - self.disp(A.color(C.A_SUBHEADER, _(u'total: '), A.RESET, unicode(guests+guests_maybe))) + self.disp(u"") + self.disp( + A.color( + C.A_SUBHEADER, + _(u"Attendees: "), + A.RESET, + unicode(len(data)), + _(u" ("), + C.A_SUCCESS, + _(u"yes: "), + unicode(attendees_yes), + A.FG_WHITE, + _(u", maybe: "), + unicode(attendees_maybe), + u", ", + C.A_FAILURE, + _(u"no: "), + unicode(attendees_no), + A.RESET, + u")", + ) + ) + self.disp( + A.color(C.A_SUBHEADER, _(u"confirmed guests: "), A.RESET, unicode(guests)) + ) + self.disp( + A.color( + C.A_SUBHEADER, + _(u"unconfirmed guests: "), + A.RESET, + unicode(guests_maybe), + ) + ) + self.disp( + A.color( + C.A_SUBHEADER, _(u"total: "), A.RESET, unicode(guests + guests_maybe) + ) + ) if attendees_missing: - self.disp('') - self.disp(A.color(C.A_SUBHEADER, _(u'missing people (no reply): '), A.RESET, unicode(attendees_missing))) + self.disp("") + self.disp( + A.color( + C.A_SUBHEADER, + _(u"missing people (no reply): "), + A.RESET, + unicode(attendees_missing), + ) + ) def eventInviteesListCb(self, event_data, prefilled_data): """fill nicknames and keep only requested people @@ -325,7 +423,7 @@ # we get nicknames for everybody, make it easier for organisers for jid_, data in prefilled_data.iteritems(): id_data = self.host.bridge.identityGet(jid_, self.profile) - data[u'nick'] = id_data.get(u'nick', u'') + data[u"nick"] = id_data.get(u"nick", u"") self.output(prefilled_data) self.host.quit() @@ -335,17 +433,23 @@ self.args.service, self.args.node, self.profile, - callback=partial(self.eventInviteesListCb, - prefilled_data=prefilled_data), - errback=partial(self.errback, - msg=_(u"can't get event data: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + callback=partial(self.eventInviteesListCb, prefilled_data=prefilled_data), + errback=partial( + self.errback, + msg=_(u"can't get event data: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) def psNodeAffiliationsGetCb(self, affiliations): # we fill all affiliations with empty data # answered one will be filled in eventInviteesListCb # we only consider people with "publisher" affiliation as invited, creators are not, and members can just observe - prefilled = {jid_: {} for jid_, affiliation in affiliations.iteritems() if affiliation in (u'publisher',)} + prefilled = { + jid_: {} + for jid_, affiliation in affiliations.iteritems() + if affiliation in (u"publisher",) + } self.getList(prefilled) def start(self): @@ -357,27 +461,79 @@ self.args.node, self.profile, callback=self.psNodeAffiliationsGetCb, - errback=partial(self.errback, - msg=_(u"can't get event data: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't get event data: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) else: self.getList() class InviteeInvite(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'invite', use_pubsub=True, pubsub_flags={C.NODE, C.SINGLE_ITEM}, help=_(u'invite someone to the event through email')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "invite", + use_pubsub=True, + pubsub_flags={C.NODE, C.SINGLE_ITEM}, + help=_(u"invite someone to the event through email"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("-e", "--email", action="append", type=base.unicode_decoder, default=[], help='email(s) to send the invitation to') - self.parser.add_argument("-N", "--name", type=base.unicode_decoder, default='', help='name of the invitee') - self.parser.add_argument("-H", "--host-name", type=base.unicode_decoder, default='', help='name of the host') - self.parser.add_argument("-l", "--lang", type=base.unicode_decoder, default='', help='main language spoken by the invitee') - self.parser.add_argument("-U", "--url-template", type=base.unicode_decoder, default='', help='template to construct the URL') - self.parser.add_argument("-S", "--subject", type=base.unicode_decoder, default='', help='subject of the invitation email (default: generic subject)') - self.parser.add_argument("-b", "--body", type=base.unicode_decoder, default='', help='body of the invitation email (default: generic body)') + self.parser.add_argument( + "-e", + "--email", + action="append", + type=base.unicode_decoder, + default=[], + help="email(s) to send the invitation to", + ) + self.parser.add_argument( + "-N", + "--name", + type=base.unicode_decoder, + default="", + help="name of the invitee", + ) + self.parser.add_argument( + "-H", + "--host-name", + type=base.unicode_decoder, + default="", + help="name of the host", + ) + self.parser.add_argument( + "-l", + "--lang", + type=base.unicode_decoder, + default="", + help="main language spoken by the invitee", + ) + self.parser.add_argument( + "-U", + "--url-template", + type=base.unicode_decoder, + default="", + help="template to construct the URL", + ) + self.parser.add_argument( + "-S", + "--subject", + type=base.unicode_decoder, + default="", + help="subject of the invitation email (default: generic subject)", + ) + self.parser.add_argument( + "-b", + "--body", + type=base.unicode_decoder, + default="", + help="body of the invitation email (default: generic body)", + ) def start(self): email = self.args.email[0] if self.args.email else None @@ -397,20 +553,27 @@ self.args.body, self.args.profile, callback=self.host.quit, - errback=partial(self.errback, - msg=_(u"can't create invitation: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't create invitation: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class Invitee(base.CommandBase): subcommands = (InviteeGet, InviteeSet, InviteesList, InviteeInvite) def __init__(self, host): - super(Invitee, self).__init__(host, 'invitee', use_profile=False, help=_(u'manage invities')) + super(Invitee, self).__init__( + host, "invitee", use_profile=False, help=_(u"manage invities") + ) class Event(base.CommandBase): subcommands = (Get, Create, Modify, Invitee) def __init__(self, host): - super(Event, self).__init__(host, 'event', use_profile=False, help=_('event management')) + super(Event, self).__init__( + host, "event", use_profile=False, help=_("event management") + )
--- a/sat_frontends/jp/cmd_file.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_file.py Wed Jun 27 20:14:46 2018 +0200 @@ -29,7 +29,7 @@ from sat_frontends.tools import jid from sat.tools.common.ansi import ANSI as A import tempfile -import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI +import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI from functools import partial import json @@ -38,32 +38,60 @@ class Send(base.CommandBase): def __init__(self, host): - super(Send, self).__init__(host, 'send', use_progress=True, use_verbose=True, help=_('send a file to a contact')) - self.need_loop=True + super(Send, self).__init__( + host, + "send", + use_progress=True, + use_verbose=True, + help=_("send a file to a contact"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("files", type=str, nargs='+', metavar='file', help=_(u"a list of file")) - self.parser.add_argument("jid", type=base.unicode_decoder, help=_(u"the destination jid")) - self.parser.add_argument("-b", "--bz2", action="store_true", help=_(u"make a bzip2 tarball")) - self.parser.add_argument("-d", "--path", type=base.unicode_decoder, help=(u"path to the directory where the file must be stored")) - self.parser.add_argument("-N", "--namespace", type=base.unicode_decoder, help=(u"namespace of the file")) - self.parser.add_argument("-n", "--name", type=base.unicode_decoder, default=u'', help=(u"name to use (DEFAULT: use source file name)")) + self.parser.add_argument( + "files", type=str, nargs="+", metavar="file", help=_(u"a list of file") + ) + self.parser.add_argument( + "jid", type=base.unicode_decoder, help=_(u"the destination jid") + ) + self.parser.add_argument( + "-b", "--bz2", action="store_true", help=_(u"make a bzip2 tarball") + ) + self.parser.add_argument( + "-d", + "--path", + type=base.unicode_decoder, + help=(u"path to the directory where the file must be stored"), + ) + self.parser.add_argument( + "-N", + "--namespace", + type=base.unicode_decoder, + help=(u"namespace of the file"), + ) + self.parser.add_argument( + "-n", + "--name", + type=base.unicode_decoder, + default=u"", + help=(u"name to use (DEFAULT: use source file name)"), + ) def start(self): """Send files to jabber contact""" self.send_files() def onProgressStarted(self, metadata): - self.disp(_(u'File copy started'),2) + self.disp(_(u"File copy started"), 2) def onProgressFinished(self, metadata): - self.disp(_(u'File sent successfully'),2) + self.disp(_(u"File sent successfully"), 2) def onProgressError(self, error_msg): if error_msg == C.PROGRESS_ERROR_DECLINED: - self.disp(_(u'The file has been refused by your contact')) + self.disp(_(u"The file has been refused by your contact")) else: - self.disp(_(u'Error while sending file: {}').format(error_msg),error=True) + self.disp(_(u"Error while sending file: {}").format(error_msg), error=True) def gotId(self, data, file_): """Called when a progress id has been received @@ -71,17 +99,22 @@ @param pid(unicode): progress id @param file_(str): file path """ - #FIXME: this show progress only for last progress_id + # FIXME: this show progress only for last progress_id self.disp(_(u"File request sent to {jid}".format(jid=self.full_dest_jid)), 1) try: - self.progress_id = data['progress'] + self.progress_id = data["progress"] except KeyError: # TODO: if 'xmlui' key is present, manage xmlui message display - self.disp(_(u"Can't send file to {jid}".format(jid=self.full_dest_jid)), error=True) + self.disp( + _(u"Can't send file to {jid}".format(jid=self.full_dest_jid)), error=True + ) self.host.quit(2) def error(self, failure): - self.disp(_("Error while trying to send a file: {reason}").format(reason=failure), error=True) + self.disp( + _("Error while trying to send a file: {reason}").format(reason=failure), + error=True, + ) self.host.quit(1) def send_files(self): @@ -90,71 +123,140 @@ self.disp(_(u"file [{}] doesn't exist !").format(file_), error=True) self.host.quit(1) if not self.args.bz2 and os.path.isdir(file_): - self.disp(_(u"[{}] is a dir ! Please send files inside or use compression").format(file_)) + self.disp( + _( + u"[{}] is a dir ! Please send files inside or use compression" + ).format(file_) + ) self.host.quit(1) self.full_dest_jid = self.host.get_full_jid(self.args.jid) extra = {} if self.args.path: - extra[u'path'] = self.args.path + extra[u"path"] = self.args.path if self.args.namespace: - extra[u'namespace'] = self.args.namespace + extra[u"namespace"] = self.args.namespace if self.args.bz2: - with tempfile.NamedTemporaryFile('wb', delete=False) as buf: + with tempfile.NamedTemporaryFile("wb", delete=False) as buf: self.host.addOnQuitCallback(os.unlink, buf.name) self.disp(_(u"bz2 is an experimental option, use with caution")) - #FIXME: check free space + # FIXME: check free space self.disp(_(u"Starting compression, please wait...")) sys.stdout.flush() bz2 = tarfile.open(mode="w:bz2", fileobj=buf) - archive_name = u'{}.tar.bz2'.format(os.path.basename(self.args.files[0]) or u'compressed_files') + archive_name = u"{}.tar.bz2".format( + os.path.basename(self.args.files[0]) or u"compressed_files" + ) for file_ in self.args.files: self.disp(_(u"Adding {}").format(file_), 1) bz2.add(file_) bz2.close() self.disp(_(u"Done !"), 1) - self.host.bridge.fileSend(self.full_dest_jid, buf.name, self.args.name or archive_name, '', extra, self.profile, - callback=lambda pid, file_=buf.name: self.gotId(pid, file_), errback=self.error) + self.host.bridge.fileSend( + self.full_dest_jid, + buf.name, + self.args.name or archive_name, + "", + extra, + self.profile, + callback=lambda pid, file_=buf.name: self.gotId(pid, file_), + errback=self.error, + ) else: for file_ in self.args.files: path = os.path.abspath(file_) - self.host.bridge.fileSend(self.full_dest_jid, path, self.args.name, '', extra, self.profile, - callback=lambda pid, file_=file_: self.gotId(pid, file_), errback=self.error) + self.host.bridge.fileSend( + self.full_dest_jid, + path, + self.args.name, + "", + extra, + self.profile, + callback=lambda pid, file_=file_: self.gotId(pid, file_), + errback=self.error, + ) class Request(base.CommandBase): - def __init__(self, host): - super(Request, self).__init__(host, 'request', use_progress=True, use_verbose=True, help=_('request a file from a contact')) - self.need_loop=True + super(Request, self).__init__( + host, + "request", + use_progress=True, + use_verbose=True, + help=_("request a file from a contact"), + ) + self.need_loop = True @property def filename(self): return self.args.name or self.args.hash or u"output" def add_parser_options(self): - self.parser.add_argument("jid", type=base.unicode_decoder, help=_(u"the destination jid")) - self.parser.add_argument("-D", "--dest", type=base.unicode_decoder, help=_(u"destination path where the file will be saved (default: [current_dir]/[name|hash])")) - self.parser.add_argument("-n", "--name", type=base.unicode_decoder, default=u'', help=_(u"name of the file")) - self.parser.add_argument("-H", "--hash", type=base.unicode_decoder, default=u'', help=_(u"hash of the file")) - self.parser.add_argument("-a", "--hash-algo", type=base.unicode_decoder, default=u'sha-256', help=_(u"hash algorithm use for --hash (default: sha-256)")) - self.parser.add_argument("-d", "--path", type=base.unicode_decoder, help=(u"path to the directory containing the file")) - self.parser.add_argument("-N", "--namespace", type=base.unicode_decoder, help=(u"namespace of the file")) - self.parser.add_argument("-f", "--force", action='store_true', help=_(u"overwrite existing file without confirmation")) + self.parser.add_argument( + "jid", type=base.unicode_decoder, help=_(u"the destination jid") + ) + self.parser.add_argument( + "-D", + "--dest", + type=base.unicode_decoder, + help=_( + u"destination path where the file will be saved (default: [current_dir]/[name|hash])" + ), + ) + self.parser.add_argument( + "-n", + "--name", + type=base.unicode_decoder, + default=u"", + help=_(u"name of the file"), + ) + self.parser.add_argument( + "-H", + "--hash", + type=base.unicode_decoder, + default=u"", + help=_(u"hash of the file"), + ) + self.parser.add_argument( + "-a", + "--hash-algo", + type=base.unicode_decoder, + default=u"sha-256", + help=_(u"hash algorithm use for --hash (default: sha-256)"), + ) + self.parser.add_argument( + "-d", + "--path", + type=base.unicode_decoder, + help=(u"path to the directory containing the file"), + ) + self.parser.add_argument( + "-N", + "--namespace", + type=base.unicode_decoder, + help=(u"namespace of the file"), + ) + self.parser.add_argument( + "-f", + "--force", + action="store_true", + help=_(u"overwrite existing file without confirmation"), + ) def onProgressStarted(self, metadata): - self.disp(_(u'File copy started'),2) + self.disp(_(u"File copy started"), 2) def onProgressFinished(self, metadata): - self.disp(_(u'File received successfully'),2) + self.disp(_(u"File received successfully"), 2) def onProgressError(self, error_msg): if error_msg == C.PROGRESS_ERROR_DECLINED: - self.disp(_(u'The file request has been refused')) + self.disp(_(u"The file request has been refused")) else: - self.disp(_(u'Error while requesting file: {}').format(error_msg), error=True) + self.disp(_(u"Error while requesting file: {}").format(error_msg), error=True) def gotId(self, progress_id): """Called when a progress id has been received @@ -164,13 +266,16 @@ self.progress_id = progress_id def error(self, failure): - self.disp(_("Error while trying to send a file: {reason}").format(reason=failure), error=True) + self.disp( + _("Error while trying to send a file: {reason}").format(reason=failure), + error=True, + ) self.host.quit(1) def start(self): if not self.args.name and not self.args.hash: - self.parser.error(_(u'at least one of --name or --hash must be provided')) - # extra = dict(self.args.extra) + self.parser.error(_(u"at least one of --name or --hash must be provided")) + # extra = dict(self.args.extra) if self.args.dest: path = os.path.abspath(os.path.expanduser(self.args.dest)) if os.path.isdir(path): @@ -179,8 +284,10 @@ path = os.path.abspath(self.filename) if os.path.exists(path) and not self.args.force: - message = _(u'File {path} already exists! Do you want to overwrite?').format(path=path) - confirm = raw_input(u"{} (y/N) ".format(message).encode('utf-8')) + message = _(u"File {path} already exists! Do you want to overwrite?").format( + path=path + ) + confirm = raw_input(u"{} (y/N) ".format(message).encode("utf-8")) if confirm not in (u"y", u"Y"): self.disp(_(u"file request cancelled")) self.host.quit(2) @@ -188,60 +295,73 @@ self.full_dest_jid = self.host.get_full_jid(self.args.jid) extra = {} if self.args.path: - extra[u'path'] = self.args.path + extra[u"path"] = self.args.path if self.args.namespace: - extra[u'namespace'] = self.args.namespace - self.host.bridge.fileJingleRequest(self.full_dest_jid, - path, - self.args.name, - self.args.hash, - self.args.hash_algo if self.args.hash else u'', - extra, - self.profile, - callback=self.gotId, - errback=partial(self.errback, - msg=_(u"can't request file: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + extra[u"namespace"] = self.args.namespace + self.host.bridge.fileJingleRequest( + self.full_dest_jid, + path, + self.args.name, + self.args.hash, + self.args.hash_algo if self.args.hash else u"", + extra, + self.profile, + callback=self.gotId, + errback=partial( + self.errback, + msg=_(u"can't request file: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class Receive(base.CommandAnswering): - def __init__(self, host): - super(Receive, self).__init__(host, 'receive', use_progress=True, use_verbose=True, help=_('wait for a file to be sent by a contact')) - self._overwrite_refused = False # True when one overwrite as already been refused - self.action_callbacks = {C.META_TYPE_FILE: self.onFileAction, - C.META_TYPE_OVERWRITE: self.onOverwriteAction} + super(Receive, self).__init__( + host, + "receive", + use_progress=True, + use_verbose=True, + help=_("wait for a file to be sent by a contact"), + ) + self._overwrite_refused = False # True when one overwrite as already been refused + self.action_callbacks = { + C.META_TYPE_FILE: self.onFileAction, + C.META_TYPE_OVERWRITE: self.onOverwriteAction, + } def onProgressStarted(self, metadata): - self.disp(_(u'File copy started'),2) + self.disp(_(u"File copy started"), 2) def onProgressFinished(self, metadata): - self.disp(_(u'File received successfully'),2) - if metadata.get('hash_verified', False): + self.disp(_(u"File received successfully"), 2) + if metadata.get("hash_verified", False): try: - self.disp(_(u'hash checked: {algo}:{checksum}').format( - algo=metadata['hash_algo'], - checksum=metadata['hash']), - 1) + self.disp( + _(u"hash checked: {algo}:{checksum}").format( + algo=metadata["hash_algo"], checksum=metadata["hash"] + ), + 1, + ) except KeyError: - self.disp(_(u'hash is checked but hash value is missing', 1), error=True) + self.disp(_(u"hash is checked but hash value is missing", 1), error=True) else: self.disp(_(u"hash can't be verified"), 1) def onProgressError(self, error_msg): - self.disp(_(u'Error while receiving file: {}').format(error_msg),error=True) + self.disp(_(u"Error while receiving file: {}").format(error_msg), error=True) def getXmluiId(self, action_data): # FIXME: we temporarily use ElementTree, but a real XMLUI managing module # should be available in the futur # TODO: XMLUI module try: - xml_ui = action_data['xmlui'] + xml_ui = action_data["xmlui"] except KeyError: self.disp(_(u"Action has no XMLUI"), 1) else: - ui = ET.fromstring(xml_ui.encode('utf-8')) - xmlui_id = ui.get('submit') + ui = ET.fromstring(xml_ui.encode("utf-8")) + xmlui_id = ui.get("submit") if not xmlui_id: self.disp(_(u"Invalid XMLUI received"), error=True) return xmlui_id @@ -251,12 +371,12 @@ if xmlui_id is None: return self.host.quitFromSignal(1) try: - from_jid = jid.JID(action_data['meta_from_jid']) + from_jid = jid.JID(action_data["meta_from_jid"]) except KeyError: self.disp(_(u"Ignoring action without from_jid data"), 1) return try: - progress_id = action_data['meta_progress_id'] + progress_id = action_data["meta_progress_id"] except KeyError: self.disp(_(u"ignoring action without progress id"), 1) return @@ -264,10 +384,12 @@ if not self.bare_jids or from_jid.bare in self.bare_jids: if self._overwrite_refused: self.disp(_(u"File refused because overwrite is needed"), error=True) - self.host.bridge.launchAction(xmlui_id, {'cancelled': C.BOOL_TRUE}, profile_key=profile) + self.host.bridge.launchAction( + xmlui_id, {"cancelled": C.BOOL_TRUE}, profile_key=profile + ) return self.host.quitFromSignal(2) self.progress_id = progress_id - xmlui_data = {'path': self.path} + xmlui_data = {"path": self.path} self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile) def onOverwriteAction(self, action_data, action_id, security_limit, profile): @@ -275,7 +397,7 @@ if xmlui_id is None: return self.host.quitFromSignal(1) try: - progress_id = action_data['meta_progress_id'] + progress_id = action_data["meta_progress_id"] except KeyError: self.disp(_(u"ignoring action without progress id"), 1) return @@ -288,14 +410,36 @@ self.disp(_(u"Refused to overwrite"), 2) self._overwrite_refused = True - xmlui_data = {'answer': C.boolConst(self.args.force)} + xmlui_data = {"answer": C.boolConst(self.args.force)} self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile) def add_parser_options(self): - self.parser.add_argument("jids", type=base.unicode_decoder, nargs="*", help=_(u'jids accepted (accept everything if none is specified)')) - self.parser.add_argument("-m", "--multiple", action="store_true", help=_(u"accept multiple files (you'll have to stop manually)")) - self.parser.add_argument("-f", "--force", action="store_true", help=_(u"force overwritting of existing files (/!\\ name is choosed by sender)")) - self.parser.add_argument("--path", default='.', metavar='DIR', help=_(u"destination path (default: working directory)")) + self.parser.add_argument( + "jids", + type=base.unicode_decoder, + nargs="*", + help=_(u"jids accepted (accept everything if none is specified)"), + ) + self.parser.add_argument( + "-m", + "--multiple", + action="store_true", + help=_(u"accept multiple files (you'll have to stop manually)"), + ) + self.parser.add_argument( + "-f", + "--force", + action="store_true", + help=_( + u"force overwritting of existing files (/!\\ name is choosed by sender)" + ), + ) + self.parser.add_argument( + "--path", + default=".", + metavar="DIR", + help=_(u"destination path (default: working directory)"), + ) def start(self): self.bare_jids = [jid.JID(jid_).bare for jid_ in self.args.jids] @@ -305,36 +449,46 @@ self.host.quit(2) if self.args.multiple: self.host.quit_on_progress_end = False - self.disp(_(u"waiting for incoming file request"),2) + self.disp(_(u"waiting for incoming file request"), 2) class Upload(base.CommandBase): - def __init__(self, host): - super(Upload, self).__init__(host, 'upload', use_progress=True, use_verbose=True, help=_('upload a file')) - self.need_loop=True + super(Upload, self).__init__( + host, "upload", use_progress=True, use_verbose=True, help=_("upload a file") + ) + self.need_loop = True def add_parser_options(self): self.parser.add_argument("file", type=str, help=_("file to upload")) - self.parser.add_argument("jid", type=base.unicode_decoder, nargs='?', help=_("jid of upload component (nothing to autodetect)")) - self.parser.add_argument("--ignore-tls-errors", action="store_true", help=_("ignore invalide TLS certificate")) + self.parser.add_argument( + "jid", + type=base.unicode_decoder, + nargs="?", + help=_("jid of upload component (nothing to autodetect)"), + ) + self.parser.add_argument( + "--ignore-tls-errors", + action="store_true", + help=_("ignore invalide TLS certificate"), + ) def onProgressStarted(self, metadata): - self.disp(_(u'File upload started'),2) + self.disp(_(u"File upload started"), 2) def onProgressFinished(self, metadata): - self.disp(_(u'File uploaded successfully'),2) + self.disp(_(u"File uploaded successfully"), 2) try: - url = metadata['url'] + url = metadata["url"] except KeyError: - self.disp(u'download URL not found in metadata') + self.disp(u"download URL not found in metadata") else: - self.disp(_(u'URL to retrieve the file:'),1) + self.disp(_(u"URL to retrieve the file:"), 1) # XXX: url is display alone on a line to make parsing easier self.disp(url) def onProgressError(self, error_msg): - self.disp(_(u'Error while uploading file: {}').format(error_msg),error=True) + self.disp(_(u"Error while uploading file: {}").format(error_msg), error=True) def gotId(self, data, file_): """Called when a progress id has been received @@ -343,14 +497,17 @@ @param file_(str): file path """ try: - self.progress_id = data['progress'] + self.progress_id = data["progress"] except KeyError: # TODO: if 'xmlui' key is present, manage xmlui message display self.disp(_(u"Can't upload file"), error=True) self.host.quit(2) def error(self, failure): - self.disp(_("Error while trying to upload a file: {reason}").format(reason=failure), error=True) + self.disp( + _("Error while trying to upload a file: {reason}").format(reason=failure), + error=True, + ) self.host.quit(1) def start(self): @@ -362,31 +519,58 @@ self.disp(_(u"[{}] is a dir! Can't upload a dir").format(file_)) self.host.quit(1) - self.full_dest_jid = self.host.get_full_jid(self.args.jid) if self.args.jid is not None else '' + self.full_dest_jid = ( + self.host.get_full_jid(self.args.jid) if self.args.jid is not None else "" + ) options = {} if self.args.ignore_tls_errors: - options['ignore_tls_errors'] = C.BOOL_TRUE + options["ignore_tls_errors"] = C.BOOL_TRUE path = os.path.abspath(file_) - self.host.bridge.fileUpload(path, '', self.full_dest_jid, options, self.profile, callback=lambda pid, file_=file_: self.gotId(pid, file_), errback=self.error) + self.host.bridge.fileUpload( + path, + "", + self.full_dest_jid, + options, + self.profile, + callback=lambda pid, file_=file_: self.gotId(pid, file_), + errback=self.error, + ) class ShareList(base.CommandBase): - def __init__(self, host): - extra_outputs = {'default': self.default_output} - super(ShareList, self).__init__(host, 'list', use_output=C.OUTPUT_LIST_DICT, extra_outputs=extra_outputs, help=_(u'retrieve files shared by an entity'), use_verbose=True) - self.need_loop=True + extra_outputs = {"default": self.default_output} + super(ShareList, self).__init__( + host, + "list", + use_output=C.OUTPUT_LIST_DICT, + extra_outputs=extra_outputs, + help=_(u"retrieve files shared by an entity"), + use_verbose=True, + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("-d", "--path", default=u'', help=_(u"path to the directory containing the files")) - self.parser.add_argument("jid", type=base.unicode_decoder, nargs='?', default='', help=_("jid of sharing entity (nothing to check our own jid)")) + self.parser.add_argument( + "-d", + "--path", + default=u"", + help=_(u"path to the directory containing the files"), + ) + self.parser.add_argument( + "jid", + type=base.unicode_decoder, + nargs="?", + default="", + help=_("jid of sharing entity (nothing to check our own jid)"), + ) def file_gen(self, files_data): for file_data in files_data: - yield file_data[u'name'] - yield file_data.get(u'size', '') - yield file_data.get(u'hash','') + yield file_data[u"name"] + yield file_data.get(u"size", "") + yield file_data.get(u"hash", "") def _name_filter(self, name, row): if row.type == C.FILE_TYPE_DIRECTORY: @@ -394,42 +578,41 @@ elif row.type == C.FILE_TYPE_FILE: return A.color(C.A_FILE, name) else: - self.disp(_(u'unknown file type: {type}').format(type=row.type), error=True) + self.disp(_(u"unknown file type: {type}").format(type=row.type), error=True) return name def _size_filter(self, size, row): if not size: - return u'' + return u"" size = int(size) - # cf. https://stackoverflow.com/a/1094933 (thanks) - suffix = u'o' - for unit in [u'', u'Ki', u'Mi', u'Gi', u'Ti', u'Pi', u'Ei', u'Zi']: + # cf. https://stackoverflow.com/a/1094933 (thanks) + suffix = u"o" + for unit in [u"", u"Ki", u"Mi", u"Gi", u"Ti", u"Pi", u"Ei", u"Zi"]: if abs(size) < 1024.0: return A.color(A.BOLD, u"{:.2f}".format(size), unit, suffix) size /= 1024.0 - return A.color(A.BOLD, u"{:.2f}".format(size), u'Yi', suffix) + return A.color(A.BOLD, u"{:.2f}".format(size), u"Yi", suffix) def default_output(self, files_data): """display files a way similar to ls""" - files_data.sort(key=lambda d: d['name'].lower()) + files_data.sort(key=lambda d: d["name"].lower()) show_header = False if self.verbosity == 0: - headers = (u'name', u'type') + headers = (u"name", u"type") elif self.verbosity == 1: - headers = (u'name', u'type', u'size') + headers = (u"name", u"type", u"size") elif self.verbosity > 1: show_header = True - headers = (u'name', u'type', u'size', u'hash') - table = common.Table.fromDict(self.host, - files_data, - headers, - filters={u'name': self._name_filter, - u'size': self._size_filter}, - defaults={u'size': u'', - u'hash': u''}, - ) - table.display_blank(show_header=show_header, hide_cols=['type']) + headers = (u"name", u"type", u"size", u"hash") + table = common.Table.fromDict( + self.host, + files_data, + headers, + filters={u"name": self._name_filter, u"size": self._size_filter}, + defaults={u"size": u"", u"hash": u""}, + ) + table.display_blank(show_header=show_header, hide_cols=["type"]) def _FISListCb(self, files_data): self.output(files_data) @@ -442,40 +625,66 @@ {}, self.profile, callback=self._FISListCb, - errback=partial(self.errback, - msg=_(u"can't retrieve shared files: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't retrieve shared files: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class SharePath(base.CommandBase): - def __init__(self, host): - super(SharePath, self).__init__(host, 'path', help=_(u'share a file or directory'), use_verbose=True) - self.need_loop=True + super(SharePath, self).__init__( + host, "path", help=_(u"share a file or directory"), use_verbose=True + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("-n", "--name", type=base.unicode_decoder, default=u'', help=_(u"virtual name to use (default: use directory/file name)")) + self.parser.add_argument( + "-n", + "--name", + type=base.unicode_decoder, + default=u"", + help=_(u"virtual name to use (default: use directory/file name)"), + ) perm_group = self.parser.add_mutually_exclusive_group() - perm_group.add_argument("-j", "--jid", type=base.unicode_decoder, action='append', dest="jids", default=[], help=_(u"jid of contacts allowed to retrieve the files")) - perm_group.add_argument("--public", action='store_true', help=_(u"share publicly the file(s) (/!\\ *everybody* will be able to access them)")) - self.parser.add_argument("path", type=base.unicode_decoder, help=_(u"path to a file or directory to share")) - + perm_group.add_argument( + "-j", + "--jid", + type=base.unicode_decoder, + action="append", + dest="jids", + default=[], + help=_(u"jid of contacts allowed to retrieve the files"), + ) + perm_group.add_argument( + "--public", + action="store_true", + help=_( + u"share publicly the file(s) (/!\\ *everybody* will be able to access them)" + ), + ) + self.parser.add_argument( + "path", + type=base.unicode_decoder, + help=_(u"path to a file or directory to share"), + ) def _FISSharePathCb(self, name): - self.disp(_(u'{path} shared under the name "{name}"').format( - path = self.path, - name = name)) + self.disp( + _(u'{path} shared under the name "{name}"').format(path=self.path, name=name) + ) self.host.quit() def start(self): self.path = os.path.abspath(self.args.path) if self.args.public: - access = {u'read': {u'type': u'public'}} + access = {u"read": {u"type": u"public"}} else: jids = self.args.jids if jids: - access = {u'read': {u'type': 'whitelist', - u'jids': jids}} + access = {u"read": {u"type": "whitelist", u"jids": jids}} else: access = {} self.host.bridge.FISSharePath( @@ -484,20 +693,27 @@ json.dumps(access, ensure_ascii=False), self.profile, callback=self._FISSharePathCb, - errback=partial(self.errback, - msg=_(u"can't share path: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't share path: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class Share(base.CommandBase): subcommands = (ShareList, SharePath) def __init__(self, host): - super(Share, self).__init__(host, 'share', use_profile=False, help=_(u'files sharing management')) + super(Share, self).__init__( + host, "share", use_profile=False, help=_(u"files sharing management") + ) class File(base.CommandBase): subcommands = (Send, Request, Receive, Upload, Share) def __init__(self, host): - super(File, self).__init__(host, 'file', use_profile=False, help=_(u'files sending/receiving/management')) + super(File, self).__init__( + host, "file", use_profile=False, help=_(u"files sending/receiving/management") + )
--- a/sat_frontends/jp/cmd_forums.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_forums.py Wed Jun 27 20:14:46 2018 +0200 @@ -33,23 +33,36 @@ class Edit(base.CommandBase, common.BaseEdit): - use_items=False + use_items = False def __init__(self, host): - base.CommandBase.__init__(self, host, 'edit', use_pubsub=True, use_draft=True, use_verbose=True, help=_(u'edit forums')) + base.CommandBase.__init__( + self, + host, + "edit", + use_pubsub=True, + use_draft=True, + use_verbose=True, + help=_(u"edit forums"), + ) common.BaseEdit.__init__(self, self.host, FORUMS_TMP_DIR) - self.need_loop=True + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("-k", "--key", type=base.unicode_decoder, default=u'', - help=_(u"forum key (DEFAULT: default forums)")) + self.parser.add_argument( + "-k", + "--key", + type=base.unicode_decoder, + default=u"", + help=_(u"forum key (DEFAULT: default forums)"), + ) def getTmpSuff(self): """return suffix used for content file""" - return u'json' + return u"json" def forumsSetCb(self): - self.disp(_(u'forums have been edited'), 1) + self.disp(_(u"forums have been edited"), 1) self.host.quit() def publish(self, forums_raw): @@ -60,9 +73,12 @@ self.args.key, self.profile, callback=self.forumsSetCb, - errback=partial(self.errback, - msg=_(u"can't set forums: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't set forums: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) def forumsGetCb(self, forums_json): content_file_obj, content_file_path = self.getTmpFile() @@ -71,19 +87,21 @@ # we loads and dumps to have pretty printed json forums = json.loads(forums_json) # cf. https://stackoverflow.com/a/18337754 - f = codecs.getwriter('utf-8')(content_file_obj) + f = codecs.getwriter("utf-8")(content_file_obj) json.dump(forums, f, ensure_ascii=False, indent=4) content_file_obj.seek(0) self.runEditor("forums_editor_args", content_file_path, content_file_obj) def forumsGetEb(self, failure_): # FIXME: error handling with bridge is broken, need to be properly fixed - if failure_.condition == u'item-not-found': - self.forumsGetCb(u'') + if failure_.condition == u"item-not-found": + self.forumsGetCb(u"") else: - self.errback(failure_, - msg=_(u"can't get forums structure: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK) + self.errback( + failure_, + msg=_(u"can't get forums structure: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ) def start(self): self.host.bridge.forumsGet( @@ -92,55 +110,66 @@ self.args.key, self.profile, callback=self.forumsGetCb, - errback=self.forumsGetEb) + errback=self.forumsGetEb, + ) class Get(base.CommandBase): - def __init__(self, host): - extra_outputs = {'default': self.default_output} - base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_COMPLEX, extra_outputs=extra_outputs, use_pubsub=True, use_verbose=True, help=_(u'get forums structure')) - self.need_loop=True + extra_outputs = {"default": self.default_output} + base.CommandBase.__init__( + self, + host, + "get", + use_output=C.OUTPUT_COMPLEX, + extra_outputs=extra_outputs, + use_pubsub=True, + use_verbose=True, + help=_(u"get forums structure"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("-k", "--key", type=base.unicode_decoder, default=u'', - help=_(u"forum key (DEFAULT: default forums)")) + self.parser.add_argument( + "-k", + "--key", + type=base.unicode_decoder, + default=u"", + help=_(u"forum key (DEFAULT: default forums)"), + ) def default_output(self, forums, level=0): for forum in forums: keys = list(forum.keys()) keys.sort() try: - keys.remove(u'title') + keys.remove(u"title") except ValueError: pass else: - keys.insert(0, u'title') + keys.insert(0, u"title") try: - keys.remove(u'sub-forums') + keys.remove(u"sub-forums") except ValueError: pass else: - keys.append(u'sub-forums') + keys.append(u"sub-forums") for key in keys: value = forum[key] - if key == 'sub-forums': - self.default_output(value, level+1) + if key == "sub-forums": + self.default_output(value, level + 1) else: - if self.host.verbosity < 1 and key != u'title': + if self.host.verbosity < 1 and key != u"title": continue head_color = C.A_LEVEL_COLORS[level % len(C.A_LEVEL_COLORS)] - self.disp(A.color(level * 4 * u' ', - head_color, - key, - A.RESET, - u': ', - value)) + self.disp( + A.color(level * 4 * u" ", head_color, key, A.RESET, u": ", value) + ) def forumsGetCb(self, forums_raw): if not forums_raw: - self.disp(_(u'no schema found'), 1) + self.disp(_(u"no schema found"), 1) self.host.quit(1) forums = json.loads(forums_raw) self.output(forums) @@ -153,14 +182,18 @@ self.args.key, self.profile, callback=self.forumsGetCb, - errback=partial(self.errback, - msg=_(u"can't get forums: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) - + errback=partial( + self.errback, + msg=_(u"can't get forums: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class Forums(base.CommandBase): subcommands = (Get, Edit) def __init__(self, host): - super(Forums, self).__init__(host, 'forums', use_profile=False, help=_(u'Forums structure edition')) + super(Forums, self).__init__( + host, "forums", use_profile=False, help=_(u"Forums structure edition") + )
--- a/sat_frontends/jp/cmd_identity.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_identity.py Wed Jun 27 20:14:46 2018 +0200 @@ -29,18 +29,21 @@ class Get(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, - host, - 'get', - use_output=C.OUTPUT_DICT, - use_verbose=True, - help=_(u'get identity data')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "get", + use_output=C.OUTPUT_DICT, + use_verbose=True, + help=_(u"get identity data"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("jid", type=base.unicode_decoder, help=_(u"entity to check")) + self.parser.add_argument( + "jid", type=base.unicode_decoder, help=_(u"entity to check") + ) def identityGetCb(self, data): self.output(data) @@ -52,19 +55,31 @@ jid_, self.profile, callback=self.identityGetCb, - errback=partial(self.errback, - msg=_(u"can't get identity data: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't get identity data: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class Set(base.CommandBase): def __init__(self, host): - super(Set, self).__init__(host, 'set', help=_('modify an existing event')) + super(Set, self).__init__(host, "set", help=_("modify an existing event")) def add_parser_options(self): - self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields', - metavar=(u"KEY", u"VALUE"), required=True, help=_(u"identity field(s) to set")) - self.need_loop=True + self.parser.add_argument( + "-f", + "--field", + type=base.unicode_decoder, + action="append", + nargs=2, + dest="fields", + metavar=(u"KEY", u"VALUE"), + required=True, + help=_(u"identity field(s) to set"), + ) + self.need_loop = True def start(self): fields = dict(self.args.fields) @@ -72,13 +87,18 @@ fields, self.profile, callback=self.host.quit, - errback=partial(self.errback, - msg=_(u"can't set identity data data: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't set identity data data: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class Identity(base.CommandBase): subcommands = (Get, Set) def __init__(self, host): - super(Identity, self).__init__(host, 'identity', use_profile=False, help=_('identity management')) + super(Identity, self).__init__( + host, "identity", use_profile=False, help=_("identity management") + )
--- a/sat_frontends/jp/cmd_input.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_input.py Wed Jun 27 20:14:46 2018 +0200 @@ -28,21 +28,22 @@ import sys __commands__ = ["Input"] -OPT_STDIN = 'stdin' -OPT_SHORT = 'short' -OPT_LONG = 'long' -OPT_POS = 'positional' -OPT_IGNORE = 'ignore' +OPT_STDIN = "stdin" +OPT_SHORT = "short" +OPT_LONG = "long" +OPT_POS = "positional" +OPT_IGNORE = "ignore" OPT_TYPES = (OPT_STDIN, OPT_SHORT, OPT_LONG, OPT_POS, OPT_IGNORE) -OPT_EMPTY_SKIP = 'skip' -OPT_EMPTY_IGNORE = 'ignore' +OPT_EMPTY_SKIP = "skip" +OPT_EMPTY_IGNORE = "ignore" OPT_EMPTY_CHOICES = (OPT_EMPTY_SKIP, OPT_EMPTY_IGNORE) class InputCommon(base.CommandBase): - def __init__(self, host, name, help): - base.CommandBase.__init__(self, host, name, use_verbose=True, use_profile=False, help=help) + base.CommandBase.__init__( + self, host, name, use_verbose=True, use_profile=False, help=help + ) self.idx = 0 self.reset() @@ -54,15 +55,61 @@ self._values_ori = [] def add_parser_options(self): - self.parser.add_argument("--encoding", default='utf-8', help=_(u"encoding of the input data")) - self.parser.add_argument("-i", "--stdin", action='append_const', const=(OPT_STDIN, None), dest='arguments', help=_(u"standard input")) - self.parser.add_argument("-s", "--short", type=self.opt(OPT_SHORT), action='append', dest='arguments', help=_(u"short option")) - self.parser.add_argument("-l", "--long", type=self.opt(OPT_LONG), action='append', dest='arguments', help=_(u"long option")) - self.parser.add_argument("-p", "--positional", type=self.opt(OPT_POS), action='append', dest='arguments', help=_(u"positional argument")) - self.parser.add_argument("-x", "--ignore", action='append_const', const=(OPT_IGNORE, None), dest='arguments', help=_(u"ignore value")) - self.parser.add_argument("-D", "--debug", action='store_true', help=_(u"don't actually run commands but echo what would be launched")) - self.parser.add_argument("--log", type=argparse.FileType('wb'), help=_(u"log stdout to FILE")) - self.parser.add_argument("--log-err", type=argparse.FileType('wb'), help=_(u"log stderr to FILE")) + self.parser.add_argument( + "--encoding", default="utf-8", help=_(u"encoding of the input data") + ) + self.parser.add_argument( + "-i", + "--stdin", + action="append_const", + const=(OPT_STDIN, None), + dest="arguments", + help=_(u"standard input"), + ) + self.parser.add_argument( + "-s", + "--short", + type=self.opt(OPT_SHORT), + action="append", + dest="arguments", + help=_(u"short option"), + ) + self.parser.add_argument( + "-l", + "--long", + type=self.opt(OPT_LONG), + action="append", + dest="arguments", + help=_(u"long option"), + ) + self.parser.add_argument( + "-p", + "--positional", + type=self.opt(OPT_POS), + action="append", + dest="arguments", + help=_(u"positional argument"), + ) + self.parser.add_argument( + "-x", + "--ignore", + action="append_const", + const=(OPT_IGNORE, None), + dest="arguments", + help=_(u"ignore value"), + ) + self.parser.add_argument( + "-D", + "--debug", + action="store_true", + help=_(u"don't actually run commands but echo what would be launched"), + ) + self.parser.add_argument( + "--log", type=argparse.FileType("wb"), help=_(u"log stdout to FILE") + ) + self.parser.add_argument( + "--log-err", type=argparse.FileType("wb"), help=_(u"log stderr to FILE") + ) self.parser.add_argument("command", nargs=argparse.REMAINDER) def opt(self, type_): @@ -75,7 +122,10 @@ try: arg_type, arg_name = arguments[self.args_idx] except IndexError: - self.disp(_(u"arguments in input data and in arguments sequence don't match"), error=True) + self.disp( + _(u"arguments in input data and in arguments sequence don't match"), + error=True, + ) self.host.quit(C.EXIT_DATA_ERROR) self.args_idx += 1 while self.args_idx < len(arguments): @@ -97,8 +147,16 @@ if value is False: # we skip the whole row if self.args.debug: - self.disp(A.color(C.A_SUBHEADER, _(u'values: '), A.RESET, u', '.join(self._values_ori)), 2) - self.disp(A.color(A.BOLD, _(u'**SKIPPING**\n'))) + self.disp( + A.color( + C.A_SUBHEADER, + _(u"values: "), + A.RESET, + u", ".join(self._values_ori), + ), + 2, + ) + self.disp(A.color(A.BOLD, _(u"**SKIPPING**\n"))) self.reset() self.idx += 1 raise exceptions.CancelError @@ -108,61 +166,81 @@ for v in value: if arg_type == OPT_STDIN: - self._stdin.append(v.encode('utf-8')) + self._stdin.append(v.encode("utf-8")) elif arg_type == OPT_SHORT: - self._opts.append('-{}'.format(arg_name)) - self._opts.append(v.encode('utf-8')) + self._opts.append("-{}".format(arg_name)) + self._opts.append(v.encode("utf-8")) elif arg_type == OPT_LONG: - self._opts.append('--{}'.format(arg_name)) - self._opts.append(v.encode('utf-8')) + self._opts.append("--{}".format(arg_name)) + self._opts.append(v.encode("utf-8")) elif arg_type == OPT_POS: - self._pos.append(v.encode('utf-8')) + self._pos.append(v.encode("utf-8")) elif arg_type == OPT_IGNORE: pass else: - self.parser.error(_(u"Invalid argument, an option type is expected, got {type_}:{name}").format( - type_=arg_type, name=arg_name)) + self.parser.error( + _( + u"Invalid argument, an option type is expected, got {type_}:{name}" + ).format(type_=arg_type, name=arg_name) + ) def runCommand(self): """run requested command with parsed arguments""" if self.args_idx != len(self.args.arguments): - self.disp(_(u"arguments in input data and in arguments sequence don't match"), error=True) + self.disp( + _(u"arguments in input data and in arguments sequence don't match"), + error=True, + ) self.host.quit(C.EXIT_DATA_ERROR) - self.disp(A.color(C.A_HEADER, _(u'command {idx}').format(idx=self.idx)), no_lf=not self.args.debug) - stdin = ''.join(self._stdin) + self.disp( + A.color(C.A_HEADER, _(u"command {idx}").format(idx=self.idx)), + no_lf=not self.args.debug, + ) + stdin = "".join(self._stdin) if self.args.debug: - self.disp(A.color(C.A_SUBHEADER, _(u'values: '), A.RESET, u', '.join(self._values_ori)), 2) + self.disp( + A.color( + C.A_SUBHEADER, _(u"values: "), A.RESET, u", ".join(self._values_ori) + ), + 2, + ) if stdin: - self.disp(A.color(C.A_SUBHEADER, u'--- STDIN ---')) - self.disp(stdin.decode('utf-8')) - self.disp(A.color(C.A_SUBHEADER, u'-------------')) - self.disp(u'{indent}{prog} {static} {options} {positionals}'.format( - indent = 4*u' ', - prog=sys.argv[0], - static = ' '.join(self.args.command).decode('utf-8'), - options = u' '.join([o.decode('utf-8') for o in self._opts]), - positionals = u' '.join([p.decode('utf-8') for p in self._pos]) - )) - self.disp(u'\n') + self.disp(A.color(C.A_SUBHEADER, u"--- STDIN ---")) + self.disp(stdin.decode("utf-8")) + self.disp(A.color(C.A_SUBHEADER, u"-------------")) + self.disp( + u"{indent}{prog} {static} {options} {positionals}".format( + indent=4 * u" ", + prog=sys.argv[0], + static=" ".join(self.args.command).decode("utf-8"), + options=u" ".join([o.decode("utf-8") for o in self._opts]), + positionals=u" ".join([p.decode("utf-8") for p in self._pos]), + ) + ) + self.disp(u"\n") else: - self.disp(u' (' + u', '.join(self._values_ori) + u')', 2, no_lf=True) + self.disp(u" (" + u", ".join(self._values_ori) + u")", 2, no_lf=True) args = [sys.argv[0]] + self.args.command + self._opts + self._pos - p = subprocess.Popen(args, - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen( + args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) (stdout, stderr) = p.communicate(stdin) log = self.args.log log_err = self.args.log_err - log_tpl = '{command}\n{buff}\n\n' + log_tpl = "{command}\n{buff}\n\n" if log: - log.write(log_tpl.format(command=' '.join(args), buff=stdout)) + log.write(log_tpl.format(command=" ".join(args), buff=stdout)) if log_err: - log_err.write(log_tpl.format(command=' '.join(args), buff=stderr)) + log_err.write(log_tpl.format(command=" ".join(args), buff=stderr)) ret = p.wait() if ret == 0: - self.disp(A.color(C.A_SUCCESS, _(u'OK'))) + self.disp(A.color(C.A_SUCCESS, _(u"OK"))) else: - self.disp(A.color(C.A_FAILURE, _(u'FAILED'))) + self.disp(A.color(C.A_FAILURE, _(u"FAILED"))) self.reset() self.idx += 1 @@ -181,32 +259,57 @@ class Csv(InputCommon): - def __init__(self, host): - super(Csv, self).__init__(host, 'csv', _(u'comma-separated values')) + super(Csv, self).__init__(host, "csv", _(u"comma-separated values")) def add_parser_options(self): InputCommon.add_parser_options(self) - self.parser.add_argument("-r", "--row", type=int, default=0, help=_(u"starting row (previous ones will be ignored)")) - self.parser.add_argument("-S", "--split", action='append_const', const=('split', None), dest='arguments', help=_(u"split value in several options")) - self.parser.add_argument("-E", "--empty", action='append', type=self.opt('empty'), dest='arguments', - help=_(u"action to do on empty value ({choices})").format(choices=u', '.join(OPT_EMPTY_CHOICES))) + self.parser.add_argument( + "-r", + "--row", + type=int, + default=0, + help=_(u"starting row (previous ones will be ignored)"), + ) + self.parser.add_argument( + "-S", + "--split", + action="append_const", + const=("split", None), + dest="arguments", + help=_(u"split value in several options"), + ) + self.parser.add_argument( + "-E", + "--empty", + action="append", + type=self.opt("empty"), + dest="arguments", + help=_(u"action to do on empty value ({choices})").format( + choices=u", ".join(OPT_EMPTY_CHOICES) + ), + ) def filter(self, filter_type, filter_arg, value): - if filter_type == 'split': + if filter_type == "split": return value.split() - elif filter_type == 'empty': + elif filter_type == "empty": if filter_arg == OPT_EMPTY_IGNORE: return value if value else None elif filter_arg == OPT_EMPTY_SKIP: return value if value else False else: - self.parser.error(_(u"--empty value must be one of {choices}").format(choices=u', '.join(OPT_EMPTY_CHOICES))) + self.parser.error( + _(u"--empty value must be one of {choices}").format( + choices=u", ".join(OPT_EMPTY_CHOICES) + ) + ) super(Csv, self).filter(filter_type, filter_arg, value) def start(self): import csv + reader = csv.reader(sys.stdin) for idx, row in enumerate(reader): try: @@ -216,7 +319,7 @@ self.addValue(value.decode(self.args.encoding)) self.runCommand() except exceptions.CancelError: - # this row has been cancelled, we skip it + # this row has been cancelled, we skip it continue @@ -224,4 +327,9 @@ subcommands = (Csv,) def __init__(self, host): - super(Input, self).__init__(host, 'input', use_profile=False, help=_(u'launch command with external input')) + super(Input, self).__init__( + host, + "input", + use_profile=False, + help=_(u"launch command with external input"), + )
--- a/sat_frontends/jp/cmd_invitation.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_invitation.py Wed Jun 27 20:14:46 2018 +0200 @@ -29,32 +29,111 @@ class Create(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'create', use_profile=False, use_output=C.OUTPUT_DICT, help=_(u'create and send an invitation')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "create", + use_profile=False, + use_output=C.OUTPUT_DICT, + help=_(u"create and send an invitation"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("-j", "--jid", type=base.unicode_decoder, default='', help='jid of the invitee (default: generate one)') - self.parser.add_argument("-P", "--password", type=base.unicode_decoder, default='', help='password of the invitee profile/XMPP account (default: generate one)') - self.parser.add_argument("-n", "--name", type=base.unicode_decoder, default='', help='name of the invitee') - self.parser.add_argument("-N", "--host-name", type=base.unicode_decoder, default='', help='name of the host') - self.parser.add_argument("-e", "--email", action="append", type=base.unicode_decoder, default=[], help='email(s) to send the invitation to (if --no-email is set, email will just be saved)') - self.parser.add_argument("--no-email", action="store_true", help='do NOT send invitation email') - self.parser.add_argument("-l", "--lang", type=base.unicode_decoder, default='', help='main language spoken by the invitee') - self.parser.add_argument("-u", "--url", type=base.unicode_decoder, default='', help='template to construct the URL') - self.parser.add_argument("-s", "--subject", type=base.unicode_decoder, default='', help='subject of the invitation email (default: generic subject)') - self.parser.add_argument("-b", "--body", type=base.unicode_decoder, default='', help='body of the invitation email (default: generic body)') - self.parser.add_argument("-x", "--extra", metavar=('KEY', 'VALUE'), type=base.unicode_decoder, action='append', nargs=2, default=[], help='extra data to associate with invitation/invitee') - self.parser.add_argument("-p", "--profile", type=base.unicode_decoder, default='', help="profile doing the invitation (default: don't associate profile)") + self.parser.add_argument( + "-j", + "--jid", + type=base.unicode_decoder, + default="", + help="jid of the invitee (default: generate one)", + ) + self.parser.add_argument( + "-P", + "--password", + type=base.unicode_decoder, + default="", + help="password of the invitee profile/XMPP account (default: generate one)", + ) + self.parser.add_argument( + "-n", + "--name", + type=base.unicode_decoder, + default="", + help="name of the invitee", + ) + self.parser.add_argument( + "-N", + "--host-name", + type=base.unicode_decoder, + default="", + help="name of the host", + ) + self.parser.add_argument( + "-e", + "--email", + action="append", + type=base.unicode_decoder, + default=[], + help="email(s) to send the invitation to (if --no-email is set, email will just be saved)", + ) + self.parser.add_argument( + "--no-email", action="store_true", help="do NOT send invitation email" + ) + self.parser.add_argument( + "-l", + "--lang", + type=base.unicode_decoder, + default="", + help="main language spoken by the invitee", + ) + self.parser.add_argument( + "-u", + "--url", + type=base.unicode_decoder, + default="", + help="template to construct the URL", + ) + self.parser.add_argument( + "-s", + "--subject", + type=base.unicode_decoder, + default="", + help="subject of the invitation email (default: generic subject)", + ) + self.parser.add_argument( + "-b", + "--body", + type=base.unicode_decoder, + default="", + help="body of the invitation email (default: generic body)", + ) + self.parser.add_argument( + "-x", + "--extra", + metavar=("KEY", "VALUE"), + type=base.unicode_decoder, + action="append", + nargs=2, + default=[], + help="extra data to associate with invitation/invitee", + ) + self.parser.add_argument( + "-p", + "--profile", + type=base.unicode_decoder, + default="", + help="profile doing the invitation (default: don't associate profile)", + ) def invitationCreateCb(self, invitation_data): self.output(invitation_data) self.host.quit(C.EXIT_OK) def invitationCreateEb(self, failure_): - self.disp(u"can't create invitation: {reason}".format( - reason=failure_), error=True) + self.disp( + u"can't create invitation: {reason}".format(reason=failure_), error=True + ) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): @@ -63,11 +142,13 @@ emails_extra = self.args.email[1:] if self.args.no_email: if email: - extra['email'] = email - data_format.iter2dict(u'emails_extra', emails_extra) + extra["email"] = email + data_format.iter2dict(u"emails_extra", emails_extra) else: if not email: - self.parser.error(_(u'you need to specify an email address to send email invitation')) + self.parser.error( + _(u"you need to specify an email address to send email invitation") + ) self.host.bridge.invitationCreate( email, @@ -83,46 +164,66 @@ extra, self.args.profile, callback=self.invitationCreateCb, - errback=self.invitationCreateEb) + errback=self.invitationCreateEb, + ) class Get(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'get', use_profile=False, use_output=C.OUTPUT_DICT, help=_(u'get invitation data')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "get", + use_profile=False, + use_output=C.OUTPUT_DICT, + help=_(u"get invitation data"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("id", type=base.unicode_decoder, - help=_(u"invitation UUID")) - self.parser.add_argument("-j", "--with-jid", action="store_true", help=_(u"start profile session and retrieve jid")) + self.parser.add_argument( + "id", type=base.unicode_decoder, help=_(u"invitation UUID") + ) + self.parser.add_argument( + "-j", + "--with-jid", + action="store_true", + help=_(u"start profile session and retrieve jid"), + ) def output_data(self, data, jid_=None): if jid_ is not None: - data['jid'] = jid_ + data["jid"] = jid_ self.output(data) self.host.quit() def invitationGetCb(self, invitation_data): if self.args.with_jid: - profile = invitation_data[u'guest_profile'] + profile = invitation_data[u"guest_profile"] + def session_started(dummy): self.host.bridge.asyncGetParamA( - u'JabberID', - u'Connection', + u"JabberID", + u"Connection", profile_key=profile, callback=lambda jid_: self.output_data(invitation_data, jid_), - errback=partial(self.errback, - msg=_(u"can't retrieve jid: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't retrieve jid: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) self.host.bridge.profileStartSession( - invitation_data[u'password'], + invitation_data[u"password"], profile, callback=session_started, - errback=partial(self.errback, - msg=_(u"can't start session: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't start session: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) else: self.output_data(invitation_data) @@ -130,61 +231,120 @@ self.host.bridge.invitationGet( self.args.id, callback=self.invitationGetCb, - errback=partial(self.errback, - msg=_(u"can't get invitation data: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't get invitation data: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class Modify(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'modify', use_profile=False, help=_(u'modify existing invitation')) - self.need_loop=True + base.CommandBase.__init__( + self, host, "modify", use_profile=False, help=_(u"modify existing invitation") + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("--replace", action='store_true', help='replace the whole data') - self.parser.add_argument("-n", "--name", type=base.unicode_decoder, default='', help='name of the invitee') - self.parser.add_argument("-N", "--host-name", type=base.unicode_decoder, default='', help='name of the host') - self.parser.add_argument("-e", "--email", type=base.unicode_decoder, default='', help='email to send the invitation to (if --no-email is set, email will just be saved)') - self.parser.add_argument("-l", "--lang", dest="language", type=base.unicode_decoder, default='', - help='main language spoken by the invitee') - self.parser.add_argument("-x", "--extra", metavar=('KEY', 'VALUE'), type=base.unicode_decoder, action='append', nargs=2, default=[], help='extra data to associate with invitation/invitee') - self.parser.add_argument("-p", "--profile", type=base.unicode_decoder, default='', help="profile doing the invitation (default: don't associate profile") - self.parser.add_argument("id", type=base.unicode_decoder, - help=_(u"invitation UUID")) + self.parser.add_argument( + "--replace", action="store_true", help="replace the whole data" + ) + self.parser.add_argument( + "-n", + "--name", + type=base.unicode_decoder, + default="", + help="name of the invitee", + ) + self.parser.add_argument( + "-N", + "--host-name", + type=base.unicode_decoder, + default="", + help="name of the host", + ) + self.parser.add_argument( + "-e", + "--email", + type=base.unicode_decoder, + default="", + help="email to send the invitation to (if --no-email is set, email will just be saved)", + ) + self.parser.add_argument( + "-l", + "--lang", + dest="language", + type=base.unicode_decoder, + default="", + help="main language spoken by the invitee", + ) + self.parser.add_argument( + "-x", + "--extra", + metavar=("KEY", "VALUE"), + type=base.unicode_decoder, + action="append", + nargs=2, + default=[], + help="extra data to associate with invitation/invitee", + ) + self.parser.add_argument( + "-p", + "--profile", + type=base.unicode_decoder, + default="", + help="profile doing the invitation (default: don't associate profile", + ) + self.parser.add_argument( + "id", type=base.unicode_decoder, help=_(u"invitation UUID") + ) def invitationModifyCb(self): - self.disp(_(u'invitations have been modified correctly')) + self.disp(_(u"invitations have been modified correctly")) self.host.quit(C.EXIT_OK) def invitationModifyEb(self, failure_): - self.disp(u"can't create invitation: {reason}".format( - reason=failure_), error=True) + self.disp( + u"can't create invitation: {reason}".format(reason=failure_), error=True + ) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): extra = dict(self.args.extra) - for arg_name in ('name', 'host_name', 'email', 'language', 'profile'): + for arg_name in ("name", "host_name", "email", "language", "profile"): value = getattr(self.args, arg_name) if not value: continue if arg_name in extra: - self.parser.error(_(u"you can't set {arg_name} in both optional argument and extra").format(arg_name=arg_name)) + self.parser.error( + _( + u"you can't set {arg_name} in both optional argument and extra" + ).format(arg_name=arg_name) + ) extra[arg_name] = value self.host.bridge.invitationModify( self.args.id, extra, self.args.replace, callback=self.invitationModifyCb, - errback=self.invitationModifyEb) + errback=self.invitationModifyEb, + ) class List(base.CommandBase): - def __init__(self, host): - extra_outputs = {'default': self.default_output} - base.CommandBase.__init__(self, host, 'list', use_profile=False, use_output=C.OUTPUT_COMPLEX, extra_outputs=extra_outputs, help=_(u'list invitations data')) - self.need_loop=True + extra_outputs = {"default": self.default_output} + base.CommandBase.__init__( + self, + host, + "list", + use_profile=False, + use_output=C.OUTPUT_COMPLEX, + extra_outputs=extra_outputs, + help=_(u"list invitations data"), + ) + self.need_loop = True def default_output(self, data): for idx, datum in enumerate(data.iteritems()): @@ -192,12 +352,17 @@ self.disp(u"\n") key, invitation_data = datum self.disp(A.color(C.A_HEADER, key)) - indent = u' ' + indent = u" " for k, v in invitation_data.iteritems(): - self.disp(indent + A.color(C.A_SUBHEADER, k + u':') + u' ' + unicode(v)) + self.disp(indent + A.color(C.A_SUBHEADER, k + u":") + u" " + unicode(v)) def add_parser_options(self): - self.parser.add_argument("-p", "--profile", default=C.PROF_KEY_NONE, help=_(u"return only invitations linked to this profile")) + self.parser.add_argument( + "-p", + "--profile", + default=C.PROF_KEY_NONE, + help=_(u"return only invitations linked to this profile"), + ) def invitationListCb(self, data): self.output(data) @@ -207,13 +372,21 @@ self.host.bridge.invitationList( self.args.profile, callback=self.invitationListCb, - errback=partial(self.errback, - msg=_(u"can't list invitations: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't list invitations: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class Invitation(base.CommandBase): subcommands = (Create, Get, Modify, List) def __init__(self, host): - super(Invitation, self).__init__(host, 'invitation', use_profile=False, help=_(u'invitation of user(s) without XMPP account')) + super(Invitation, self).__init__( + host, + "invitation", + use_profile=False, + help=_(u"invitation of user(s) without XMPP account"), + )
--- a/sat_frontends/jp/cmd_merge_request.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_merge_request.py Wed Jun 27 20:14:46 2018 +0200 @@ -30,18 +30,47 @@ class Set(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'set', use_pubsub=True, - pubsub_defaults = {u'service': _(u'auto'), u'node': _(u'auto')}, - help=_(u'publish or update a merge request')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "set", + use_pubsub=True, + pubsub_defaults={u"service": _(u"auto"), u"node": _(u"auto")}, + help=_(u"publish or update a merge request"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("-i", "--item", type=base.unicode_decoder, default=u'', help=_(u"id or URL of the request to update, or nothing for a new one")) - self.parser.add_argument("-r", "--repository", metavar="PATH", type=base.unicode_decoder, default=u'.', help=_(u"path of the repository (DEFAULT: current directory)")) - self.parser.add_argument("-f", "--force", action="store_true", help=_(u"publish merge request without confirmation")) - self.parser.add_argument("-l", "--label", dest="labels", type=base.unicode_decoder, action='append', help=_(u"labels to categorize your request")) + self.parser.add_argument( + "-i", + "--item", + type=base.unicode_decoder, + default=u"", + help=_(u"id or URL of the request to update, or nothing for a new one"), + ) + self.parser.add_argument( + "-r", + "--repository", + metavar="PATH", + type=base.unicode_decoder, + default=u".", + help=_(u"path of the repository (DEFAULT: current directory)"), + ) + self.parser.add_argument( + "-f", + "--force", + action="store_true", + help=_(u"publish merge request without confirmation"), + ) + self.parser.add_argument( + "-l", + "--label", + dest="labels", + type=base.unicode_decoder, + action="append", + help=_(u"labels to categorize your request"), + ) def mergeRequestSetCb(self, published_id): if published_id: @@ -51,45 +80,54 @@ self.host.quit(C.EXIT_OK) def sendRequest(self): - extra = {'update': 'true'} if self.args.item else {} + extra = {"update": "true"} if self.args.item else {} values = {} if self.args.labels is not None: - values[u'labels'] = self.args.labels + values[u"labels"] = self.args.labels self.host.bridge.mergeRequestSet( self.args.service, self.args.node, self.repository, - u'auto', + u"auto", values, - u'', + u"", self.args.item, extra, self.profile, callback=self.mergeRequestSetCb, - errback=partial(self.errback, - msg=_(u"can't create merge request: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't create merge request: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) def askConfirmation(self): if not self.args.force: - message = _(u"You are going to publish your changes to service [{service}], are you sure ?").format( - service=self.args.service) + message = _( + u"You are going to publish your changes to service [{service}], are you sure ?" + ).format(service=self.args.service) self.host.confirmOrQuit(message, _(u"merge request publication cancelled")) self.sendRequest() def start(self): self.repository = os.path.expanduser(os.path.abspath(self.args.repository)) - common.URIFinder(self, self.repository, 'merge requests', self.askConfirmation) + common.URIFinder(self, self.repository, "merge requests", self.askConfirmation) class Get(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'get', use_verbose=True, - use_pubsub=True, pubsub_flags={C.MULTI_ITEMS}, - pubsub_defaults = {u'service': _(u'auto'), u'node': _(u'auto')}, - help=_(u'get a merge request')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "get", + use_verbose=True, + use_pubsub=True, + pubsub_flags={C.MULTI_ITEMS}, + pubsub_defaults={u"service": _(u"auto"), u"node": _(u"auto")}, + help=_(u"get a merge request"), + ) + self.need_loop = True def add_parser_options(self): pass @@ -98,11 +136,11 @@ if self.verbosity >= 1: whitelist = None else: - whitelist = {'id', 'title', 'body'} + whitelist = {"id", "title", "body"} for request_xmlui in requests_data[0]: xmlui = xmlui_manager.create(self.host, request_xmlui, whitelist=whitelist) xmlui.show(values_only=True) - self.disp(u'') + self.disp(u"") self.host.quit(C.EXIT_OK) def getRequests(self): @@ -112,29 +150,45 @@ self.args.node, self.args.max, self.args.items, - u'', + u"", extra, self.profile, callback=self.mergeRequestGetCb, - errback=partial(self.errback, - msg=_(u"can't get merge request: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't get merge request: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) def start(self): - common.URIFinder(self, os.getcwd(), 'merge requests', self.getRequests, meta_map={}) + common.URIFinder( + self, os.getcwd(), "merge requests", self.getRequests, meta_map={} + ) class Import(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'import', - use_pubsub=True, pubsub_flags={C.SINGLE_ITEM, C.ITEM}, - pubsub_defaults = {u'service': _(u'auto'), u'node': _(u'auto')}, - help=_(u'import a merge request')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "import", + use_pubsub=True, + pubsub_flags={C.SINGLE_ITEM, C.ITEM}, + pubsub_defaults={u"service": _(u"auto"), u"node": _(u"auto")}, + help=_(u"import a merge request"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("-r", "--repository", metavar="PATH", type=base.unicode_decoder, default=u'.', help=_(u"path of the repository (DEFAULT: current directory)")) + self.parser.add_argument( + "-r", + "--repository", + metavar="PATH", + type=base.unicode_decoder, + default=u".", + help=_(u"path of the repository (DEFAULT: current directory)"), + ) def mergeRequestImportCb(self): self.host.quit(C.EXIT_OK) @@ -149,17 +203,24 @@ extra, self.profile, callback=self.mergeRequestImportCb, - errback=partial(self.errback, - msg=_(u"can't import merge request: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't import merge request: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) def start(self): self.repository = os.path.expanduser(os.path.abspath(self.args.repository)) - common.URIFinder(self, self.repository, 'merge requests', self.importRequest, meta_map={}) + common.URIFinder( + self, self.repository, "merge requests", self.importRequest, meta_map={} + ) class MergeRequest(base.CommandBase): subcommands = (Set, Get, Import) def __init__(self, host): - super(MergeRequest, self).__init__(host, 'merge-request', use_profile=False, help=_('merge-request management')) + super(MergeRequest, self).__init__( + host, "merge-request", use_profile=False, help=_("merge-request management") + )
--- a/sat_frontends/jp/cmd_message.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_message.py Wed Jun 27 20:14:46 2018 +0200 @@ -27,25 +27,58 @@ class Send(base.CommandBase): - def __init__(self, host): - super(Send, self).__init__(host, 'send', help=_('send a message to a contact')) + super(Send, self).__init__(host, "send", help=_("send a message to a contact")) def add_parser_options(self): - self.parser.add_argument("-l", "--lang", type=str, default='', help=_(u"language of the message")) - self.parser.add_argument("-s", "--separate", action="store_true", help=_(u"separate xmpp messages: send one message per line instead of one message alone.")) - self.parser.add_argument("-n", "--new-line", action="store_true", help=_(u"add a new line at the beginning of the input (usefull for ascii art ;))")) - self.parser.add_argument("-S", "--subject", type=base.unicode_decoder, help=_(u"subject of the message")) - self.parser.add_argument("-L", "--subject_lang", type=str, default='', help=_(u"language of subject")) - self.parser.add_argument("-t", "--type", choices=C.MESS_TYPE_STANDARD + (C.MESS_TYPE_AUTO,), default=C.MESS_TYPE_AUTO, help=_("type of the message")) + self.parser.add_argument( + "-l", "--lang", type=str, default="", help=_(u"language of the message") + ) + self.parser.add_argument( + "-s", + "--separate", + action="store_true", + help=_( + u"separate xmpp messages: send one message per line instead of one message alone." + ), + ) + self.parser.add_argument( + "-n", + "--new-line", + action="store_true", + help=_( + u"add a new line at the beginning of the input (usefull for ascii art ;))" + ), + ) + self.parser.add_argument( + "-S", + "--subject", + type=base.unicode_decoder, + help=_(u"subject of the message"), + ) + self.parser.add_argument( + "-L", "--subject_lang", type=str, default="", help=_(u"language of subject") + ) + self.parser.add_argument( + "-t", + "--type", + choices=C.MESS_TYPE_STANDARD + (C.MESS_TYPE_AUTO,), + default=C.MESS_TYPE_AUTO, + help=_("type of the message"), + ) syntax = self.parser.add_mutually_exclusive_group() syntax.add_argument("-x", "--xhtml", action="store_true", help=_(u"XHTML body")) syntax.add_argument("-r", "--rich", action="store_true", help=_(u"rich body")) - self.parser.add_argument("jid", type=base.unicode_decoder, help=_(u"the destination jid")) + self.parser.add_argument( + "jid", type=base.unicode_decoder, help=_(u"the destination jid") + ) def start(self): if self.args.xhtml and self.args.separate: - self.disp(u"argument -s/--separate is not compatible yet with argument -x/--xhtml", error=True) + self.disp( + u"argument -s/--separate is not compatible yet with argument -x/--xhtml", + error=True, + ) self.host.quit(2) jids = self.host.check_jids([self.args.jid]) @@ -58,7 +91,9 @@ @param dest_jid: destination jid """ header = "\n" if self.args.new_line else "" - stdin_lines = [stream.decode('utf-8','ignore') for stream in sys.stdin.readlines()] + stdin_lines = [ + stream.decode("utf-8", "ignore") for stream in sys.stdin.readlines() + ] extra = {} if self.args.subject is None: subject = {} @@ -72,20 +107,52 @@ extra[key] = clean_ustr(u"".join(stdin_lines)) stdin_lines = [] - if self.args.separate: #we send stdin in several messages + if self.args.separate: # we send stdin in several messages if header: - self.host.bridge.messageSend(dest_jid, {self.args.lang: header}, subject, self.args.type, profile_key=self.profile, callback=lambda: None, errback=lambda ignore: ignore) + self.host.bridge.messageSend( + dest_jid, + {self.args.lang: header}, + subject, + self.args.type, + profile_key=self.profile, + callback=lambda: None, + errback=lambda ignore: ignore, + ) for line in stdin_lines: - self.host.bridge.messageSend(dest_jid, {self.args.lang: line.replace("\n","")}, subject, self.args.type, extra, profile_key=self.host.profile, callback=lambda: None, errback=lambda ignore: ignore) + self.host.bridge.messageSend( + dest_jid, + {self.args.lang: line.replace("\n", "")}, + subject, + self.args.type, + extra, + profile_key=self.host.profile, + callback=lambda: None, + errback=lambda ignore: ignore, + ) else: - msg = {self.args.lang: header + clean_ustr(u"".join(stdin_lines))} if not (self.args.xhtml or self.args.rich) else {} - self.host.bridge.messageSend(dest_jid, msg, subject, self.args.type, extra, profile_key=self.host.profile, callback=lambda: None, errback=lambda ignore: ignore) + msg = ( + {self.args.lang: header + clean_ustr(u"".join(stdin_lines))} + if not (self.args.xhtml or self.args.rich) + else {} + ) + self.host.bridge.messageSend( + dest_jid, + msg, + subject, + self.args.type, + extra, + profile_key=self.host.profile, + callback=lambda: None, + errback=lambda ignore: ignore, + ) class Message(base.CommandBase): - subcommands = (Send, ) + subcommands = (Send,) def __init__(self, host): - super(Message, self).__init__(host, 'message', use_profile=False, help=_('messages handling')) + super(Message, self).__init__( + host, "message", use_profile=False, help=_("messages handling") + )
--- a/sat_frontends/jp/cmd_pipe.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_pipe.py Wed Jun 27 20:14:46 2018 +0200 @@ -23,7 +23,7 @@ import sys from sat.core.i18n import _ from sat_frontends.tools import jid -import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI +import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI from functools import partial import socket import SocketServer @@ -33,18 +33,20 @@ START_PORT = 9999 + class PipeOut(base.CommandBase): - def __init__(self, host): - super(PipeOut, self).__init__(host, 'out', help=_('send a pipe a stream')) + super(PipeOut, self).__init__(host, "out", help=_("send a pipe a stream")) self.need_loop = True def add_parser_options(self): - self.parser.add_argument("jid", type=base.unicode_decoder, help=_("the destination jid")) + self.parser.add_argument( + "jid", type=base.unicode_decoder, help=_("the destination jid") + ) def streamOutCb(self, port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect(('127.0.0.1', int(port))) + s.connect(("127.0.0.1", int(port))) while True: buf = sys.stdin.read(4096) if not buf: @@ -53,7 +55,7 @@ s.sendall(buf) except socket.error as e: if e.errno == errno.EPIPE: - sys.stderr.write(str(e) + '\n') + sys.stderr.write(str(e) + "\n") self.host.quit(1) else: raise e @@ -65,13 +67,15 @@ self.host.get_full_jid(self.args.jid), self.profile, callback=self.streamOutCb, - errback=partial(self.errback, - msg=_(u"can't start stream: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't start stream: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class StreamServer(SocketServer.BaseRequestHandler): - def handle(self): while True: data = self.request.recv(4096) @@ -81,33 +85,37 @@ try: sys.stdout.flush() except IOError as e: - sys.stderr.write(str(e) + '\n') + sys.stderr.write(str(e) + "\n") break - # calling shutdown will do a deadlock as we don't use separate thread + # calling shutdown will do a deadlock as we don't use separate thread # this is a workaround (cf. https://stackoverflow.com/a/36017741) self.server._BaseServer__shutdown_request = True class PipeIn(base.CommandAnswering): - def __init__(self, host): - super(PipeIn, self).__init__(host, 'in', help=_('receive a pipe stream')) + super(PipeIn, self).__init__(host, "in", help=_("receive a pipe stream")) self.action_callbacks = {"STREAM": self.onStreamAction} def add_parser_options(self): - self.parser.add_argument("jids", type=base.unicode_decoder, nargs="*", help=_('Jids accepted (none means "accept everything")')) + self.parser.add_argument( + "jids", + type=base.unicode_decoder, + nargs="*", + help=_('Jids accepted (none means "accept everything")'), + ) def getXmluiId(self, action_data): # FIXME: we temporarily use ElementTree, but a real XMLUI managing module # should be available in the future # TODO: XMLUI module try: - xml_ui = action_data['xmlui'] + xml_ui = action_data["xmlui"] except KeyError: self.disp(_(u"Action has no XMLUI"), 1) else: - ui = ET.fromstring(xml_ui.encode('utf-8')) - xmlui_id = ui.get('submit') + ui = ET.fromstring(xml_ui.encode("utf-8")) + xmlui_id = ui.get("submit") if not xmlui_id: self.disp(_(u"Invalid XMLUI received"), error=True) return xmlui_id @@ -117,7 +125,7 @@ if xmlui_id is None: return self.host.quitFromSignal(1) try: - from_jid = jid.JID(action_data['meta_from_jid']) + from_jid = jid.JID(action_data["meta_from_jid"]) except KeyError: self.disp(_(u"Ignoring action without from_jid data"), 1) return @@ -134,8 +142,7 @@ raise e else: break - xmlui_data = {'answer': C.BOOL_TRUE, - 'port': unicode(port)} + xmlui_data = {"answer": C.BOOL_TRUE, "port": unicode(port)} self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile) server.serve_forever() self.host.quitFromSignal() @@ -148,4 +155,6 @@ subcommands = (PipeOut, PipeIn) def __init__(self, host): - super(Pipe, self).__init__(host, 'pipe', use_profile=False, help=_('stream piping through XMPP')) + super(Pipe, self).__init__( + host, "pipe", use_profile=False, help=_("stream piping through XMPP") + )
--- a/sat_frontends/jp/cmd_pubsub.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_pubsub.py Wed Jun 27 20:14:46 2018 +0200 @@ -38,36 +38,52 @@ PUBSUB_TMP_DIR = u"pubsub" PUBSUB_SCHEMA_TMP_DIR = PUBSUB_TMP_DIR + "_schema" -ALLOWED_SUBSCRIPTIONS_OWNER = ('subscribed', 'pending', 'none') +ALLOWED_SUBSCRIPTIONS_OWNER = ("subscribed", "pending", "none") # TODO: need to split this class in several modules, plugin should handle subcommands class NodeInfo(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'info', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'retrieve node configuration')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "info", + use_output=C.OUTPUT_DICT, + use_pubsub=True, + pubsub_flags={C.NODE}, + help=_(u"retrieve node configuration"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("-k", "--key", type=base.unicode_decoder, action='append', dest='keys', - help=_(u"data key to filter")) + self.parser.add_argument( + "-k", + "--key", + type=base.unicode_decoder, + action="append", + dest="keys", + help=_(u"data key to filter"), + ) def removePrefix(self, key): return key[7:] if key.startswith(u"pubsub#") else key def filterKey(self, key): - return any((key == k or key == u'pubsub#' + k) for k in self.args.keys) + return any((key == k or key == u"pubsub#" + k) for k in self.args.keys) def psNodeConfigurationGetCb(self, config_dict): key_filter = (lambda k: True) if not self.args.keys else self.filterKey - config_dict = {self.removePrefix(k):v for k,v in config_dict.iteritems() if key_filter(k)} + config_dict = { + self.removePrefix(k): v for k, v in config_dict.iteritems() if key_filter(k) + } self.output(config_dict) self.host.quit() def psNodeConfigurationGetEb(self, failure_): - self.disp(u"can't get node configuration: {reason}".format( - reason=failure_), error=True) + self.disp( + u"can't get node configuration: {reason}".format(reason=failure_), error=True + ) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): @@ -76,36 +92,58 @@ self.args.node, self.profile, callback=self.psNodeConfigurationGetCb, - errback=self.psNodeConfigurationGetEb) + errback=self.psNodeConfigurationGetEb, + ) class NodeCreate(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'create', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'create a node')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "create", + use_output=C.OUTPUT_DICT, + use_pubsub=True, + pubsub_flags={C.NODE}, + use_verbose=True, + help=_(u"create a node"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields', - default=[], metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set")) - self.parser.add_argument("-F", "--full-prefix", action="store_true", help=_(u"don't prepend \"pubsub#\" prefix to field names")) + self.parser.add_argument( + "-f", + "--field", + type=base.unicode_decoder, + action="append", + nargs=2, + dest="fields", + default=[], + metavar=(u"KEY", u"VALUE"), + help=_(u"configuration field to set"), + ) + self.parser.add_argument( + "-F", + "--full-prefix", + action="store_true", + help=_(u'don\'t prepend "pubsub#" prefix to field names'), + ) def psNodeCreateCb(self, node_id): if self.host.verbosity: - announce = _(u'node created successfully: ') + announce = _(u"node created successfully: ") else: - announce = u'' + announce = u"" self.disp(announce + node_id) self.host.quit() def psNodeCreateEb(self, failure_): - self.disp(u"can't create: {reason}".format( - reason=failure_), error=True) + self.disp(u"can't create: {reason}".format(reason=failure_), error=True) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): if not self.args.full_prefix: - options = {u'pubsub#' + k: v for k,v in self.args.fields} + options = {u"pubsub#" + k: v for k, v in self.args.fields} else: options = dict(self.args.fields) self.host.bridge.psNodeCreate( @@ -114,32 +152,48 @@ options, self.profile, callback=self.psNodeCreateCb, - errback=partial(self.errback, - msg=_(u"can't create node: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't create node: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class NodeDelete(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'delete', use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'delete a node')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "delete", + use_pubsub=True, + pubsub_flags={C.NODE}, + help=_(u"delete a node"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument('-f', '--force', action='store_true', help=_(u'delete node without confirmation')) + self.parser.add_argument( + "-f", + "--force", + action="store_true", + help=_(u"delete node without confirmation"), + ) def psNodeDeleteCb(self): - self.disp(_(u'node [{node}] deleted successfully').format(node=self.args.node)) + self.disp(_(u"node [{node}] deleted successfully").format(node=self.args.node)) self.host.quit() def start(self): if not self.args.force: if not self.args.service: message = _(u"Are you sure to delete pep node [{node_id}] ?").format( - node_id=self.args.node) + node_id=self.args.node + ) else: - message = _(u"Are you sure to delete node [{node_id}] on service [{service}] ?").format( - node_id=self.args.node, service=self.args.service) + message = _( + u"Are you sure to delete node [{node_id}] on service [{service}] ?" + ).format(node_id=self.args.node, service=self.args.service) self.host.confirmOrQuit(message, _(u"node deletion cancelled")) self.host.bridge.psNodeDelete( @@ -147,33 +201,54 @@ self.args.node, self.profile, callback=self.psNodeDeleteCb, - errback=partial(self.errback, - msg=_(u"can't delete node: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't delete node: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class NodeSet(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'set', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'set node configuration')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "set", + use_output=C.OUTPUT_DICT, + use_pubsub=True, + pubsub_flags={C.NODE}, + use_verbose=True, + help=_(u"set node configuration"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields', - required=True, metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set (required)")) + self.parser.add_argument( + "-f", + "--field", + type=base.unicode_decoder, + action="append", + nargs=2, + dest="fields", + required=True, + metavar=(u"KEY", u"VALUE"), + help=_(u"configuration field to set (required)"), + ) def psNodeConfigurationSetCb(self): - self.disp(_(u'node configuration successful'), 1) + self.disp(_(u"node configuration successful"), 1) self.host.quit() def psNodeConfigurationSetEb(self, failure_): - self.disp(u"can't set node configuration: {reason}".format( - reason=failure_), error=True) + self.disp( + u"can't set node configuration: {reason}".format(reason=failure_), error=True + ) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def getKeyName(self, k): - if not k.startswith(u'pubsub#'): - return u'pubsub#' + k + if not k.startswith(u"pubsub#"): + return u"pubsub#" + k else: return k @@ -181,17 +256,25 @@ self.host.bridge.psNodeConfigurationSet( self.args.service, self.args.node, - {self.getKeyName(k): v for k,v in self.args.fields}, + {self.getKeyName(k): v for k, v in self.args.fields}, self.profile, callback=self.psNodeConfigurationSetCb, - errback=self.psNodeConfigurationSetEb) + errback=self.psNodeConfigurationSetEb, + ) class NodeAffiliationsGet(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'retrieve node affiliations (for node owner)')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "get", + use_output=C.OUTPUT_DICT, + use_pubsub=True, + pubsub_flags={C.NODE}, + help=_(u"retrieve node affiliations (for node owner)"), + ) + self.need_loop = True def add_parser_options(self): pass @@ -201,8 +284,9 @@ self.host.quit() def psNodeAffiliationsGetEb(self, failure_): - self.disp(u"can't get node affiliations: {reason}".format( - reason=failure_), error=True) + self.disp( + u"can't get node affiliations: {reason}".format(reason=failure_), error=True + ) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): @@ -211,35 +295,46 @@ self.args.node, self.profile, callback=self.psNodeAffiliationsGetCb, - errback=self.psNodeAffiliationsGetEb) + errback=self.psNodeAffiliationsGetEb, + ) class NodeAffiliationsSet(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'set affiliations (for node owner)')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "set", + use_pubsub=True, + pubsub_flags={C.NODE}, + use_verbose=True, + help=_(u"set affiliations (for node owner)"), + ) + self.need_loop = True def add_parser_options(self): # XXX: we use optional argument syntax for a required one because list of list of 2 elements # (uses to construct dicts) don't work with positional arguments - self.parser.add_argument("-a", - "--affiliation", - dest="affiliations", - metavar=('JID', 'AFFILIATION'), - required=True, - type=base.unicode_decoder, - action="append", - nargs=2, - help=_(u"entity/affiliation couple(s)")) + self.parser.add_argument( + "-a", + "--affiliation", + dest="affiliations", + metavar=("JID", "AFFILIATION"), + required=True, + type=base.unicode_decoder, + action="append", + nargs=2, + help=_(u"entity/affiliation couple(s)"), + ) def psNodeAffiliationsSetCb(self): self.disp(_(u"affiliations have been set"), 1) self.host.quit() def psNodeAffiliationsSetEb(self, failure_): - self.disp(u"can't set node affiliations: {reason}".format( - reason=failure_), error=True) + self.disp( + u"can't set node affiliations: {reason}".format(reason=failure_), error=True + ) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): @@ -250,21 +345,34 @@ affiliations, self.profile, callback=self.psNodeAffiliationsSetCb, - errback=self.psNodeAffiliationsSetEb) + errback=self.psNodeAffiliationsSetEb, + ) class NodeAffiliations(base.CommandBase): subcommands = (NodeAffiliationsGet, NodeAffiliationsSet) def __init__(self, host): - super(NodeAffiliations, self).__init__(host, 'affiliations', use_profile=False, help=_(u'set or retrieve node affiliations')) + super(NodeAffiliations, self).__init__( + host, + "affiliations", + use_profile=False, + help=_(u"set or retrieve node affiliations"), + ) class NodeSubscriptionsGet(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'retrieve node subscriptions (for node owner)')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "get", + use_output=C.OUTPUT_DICT, + use_pubsub=True, + pubsub_flags={C.NODE}, + help=_(u"retrieve node subscriptions (for node owner)"), + ) + self.need_loop = True def add_parser_options(self): pass @@ -274,8 +382,9 @@ self.host.quit() def psNodeSubscriptionsGetEb(self, failure_): - self.disp(u"can't get node subscriptions: {reason}".format( - reason=failure_), error=True) + self.disp( + u"can't get node subscriptions: {reason}".format(reason=failure_), error=True + ) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): @@ -284,7 +393,8 @@ self.args.node, self.profile, callback=self.psNodeSubscriptionsGetCb, - errback=self.psNodeSubscriptionsGetEb) + errback=self.psNodeSubscriptionsGetEb, + ) class StoreSubscriptionAction(argparse.Action): @@ -301,39 +411,53 @@ try: subscription = values.pop(0) except IndexError: - subscription = 'subscribed' + subscription = "subscribed" if subscription not in ALLOWED_SUBSCRIPTIONS_OWNER: - parser.error(_(u"subscription must be one of {}").format(u', '.join(ALLOWED_SUBSCRIPTIONS_OWNER))) + parser.error( + _(u"subscription must be one of {}").format( + u", ".join(ALLOWED_SUBSCRIPTIONS_OWNER) + ) + ) dest_dict[jid_s] = subscription class NodeSubscriptionsSet(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'set/modify subscriptions (for node owner)')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "set", + use_pubsub=True, + pubsub_flags={C.NODE}, + use_verbose=True, + help=_(u"set/modify subscriptions (for node owner)"), + ) + self.need_loop = True def add_parser_options(self): # XXX: we use optional argument syntax for a required one because list of list of 2 elements # (uses to construct dicts) don't work with positional arguments - self.parser.add_argument("-S", - "--subscription", - dest="subscriptions", - default={}, - nargs='+', - metavar=('JID [SUSBSCRIPTION]'), - required=True, - type=base.unicode_decoder, - action=StoreSubscriptionAction, - help=_(u"entity/subscription couple(s)")) + self.parser.add_argument( + "-S", + "--subscription", + dest="subscriptions", + default={}, + nargs="+", + metavar=("JID [SUSBSCRIPTION]"), + required=True, + type=base.unicode_decoder, + action=StoreSubscriptionAction, + help=_(u"entity/subscription couple(s)"), + ) def psNodeSubscriptionsSetCb(self): self.disp(_(u"subscriptions have been set"), 1) self.host.quit() def psNodeSubscriptionsSetEb(self, failure_): - self.disp(u"can't set node subscriptions: {reason}".format( - reason=failure_), error=True) + self.disp( + u"can't set node subscriptions: {reason}".format(reason=failure_), error=True + ) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): @@ -343,27 +467,40 @@ self.args.subscriptions, self.profile, callback=self.psNodeSubscriptionsSetCb, - errback=self.psNodeSubscriptionsSetEb) + errback=self.psNodeSubscriptionsSetEb, + ) class NodeSubscriptions(base.CommandBase): subcommands = (NodeSubscriptionsGet, NodeSubscriptionsSet) def __init__(self, host): - super(NodeSubscriptions, self).__init__(host, 'subscriptions', use_profile=False, help=_(u'get or modify node subscriptions')) + super(NodeSubscriptions, self).__init__( + host, + "subscriptions", + use_profile=False, + help=_(u"get or modify node subscriptions"), + ) class NodeSchemaSet(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'set/replace a schema')) + base.CommandBase.__init__( + self, + host, + "set", + use_pubsub=True, + pubsub_flags={C.NODE}, + use_verbose=True, + help=_(u"set/replace a schema"), + ) self.need_loop = True def add_parser_options(self): - self.parser.add_argument('schema', help=_(u"schema to set (must be XML)")) + self.parser.add_argument("schema", help=_(u"schema to set (must be XML)")) def psSchemaSetCb(self): - self.disp(_(u'schema has been set'), 1) + self.disp(_(u"schema has been set"), 1) self.host.quit() def start(self): @@ -373,24 +510,36 @@ self.args.schema, self.profile, callback=self.psSchemaSetCb, - errback=partial(self.errback, - msg=_(u"can't set schema: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't set schema: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class NodeSchemaEdit(base.CommandBase, common.BaseEdit): - use_items=False + use_items = False def __init__(self, host): - base.CommandBase.__init__(self, host, 'edit', use_pubsub=True, pubsub_flags={C.NODE}, use_draft=True, use_verbose=True, help=_(u'edit a schema')) + base.CommandBase.__init__( + self, + host, + "edit", + use_pubsub=True, + pubsub_flags={C.NODE}, + use_draft=True, + use_verbose=True, + help=_(u"edit a schema"), + ) common.BaseEdit.__init__(self, self.host, PUBSUB_SCHEMA_TMP_DIR) - self.need_loop=True + self.need_loop = True def add_parser_options(self): pass def psSchemaSetCb(self): - self.disp(_(u'schema has been set'), 1) + self.disp(_(u"schema has been set"), 1) self.host.quit() def publish(self, schema): @@ -400,22 +549,30 @@ schema, self.profile, callback=self.psSchemaSetCb, - errback=partial(self.errback, - msg=_(u"can't set schema: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't set schema: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) def psSchemaGetCb(self, schema): try: from lxml import etree except ImportError: - self.disp(u"lxml module must be installed to use edit, please install it with \"pip install lxml\"", error=True) + self.disp( + u'lxml module must be installed to use edit, please install it with "pip install lxml"', + error=True, + ) self.host.quit(1) content_file_obj, content_file_path = self.getTmpFile() schema = schema.strip() if schema: parser = etree.XMLParser(remove_blank_text=True) schema_elt = etree.fromstring(schema, parser) - content_file_obj.write(etree.tostring(schema_elt, encoding="utf-8", pretty_print=True)) + content_file_obj.write( + etree.tostring(schema_elt, encoding="utf-8", pretty_print=True) + ) content_file_obj.seek(0) self.runEditor("pubsub_schema_editor_args", content_file_path, content_file_obj) @@ -425,23 +582,34 @@ self.args.node, self.profile, callback=self.psSchemaGetCb, - errback=partial(self.errback, - msg=_(u"can't edit schema: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't edit schema: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class NodeSchemaGet(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_XML, use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'get schema')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "get", + use_output=C.OUTPUT_XML, + use_pubsub=True, + pubsub_flags={C.NODE}, + use_verbose=True, + help=_(u"get schema"), + ) + self.need_loop = True def add_parser_options(self): pass def psSchemaGetCb(self, schema): if not schema: - self.disp(_(u'no schema found'), 1) + self.disp(_(u"no schema found"), 1) self.host.quit(1) self.output(schema) self.host.quit() @@ -452,33 +620,60 @@ self.args.node, self.profile, callback=self.psSchemaGetCb, - errback=partial(self.errback, - msg=_(u"can't get schema: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't get schema: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class NodeSchema(base.CommandBase): subcommands = (NodeSchemaSet, NodeSchemaEdit, NodeSchemaGet) def __init__(self, host): - super(NodeSchema, self).__init__(host, 'schema', use_profile=False, help=_(u"data schema manipulation")) + super(NodeSchema, self).__init__( + host, "schema", use_profile=False, help=_(u"data schema manipulation") + ) class Node(base.CommandBase): - subcommands = (NodeInfo, NodeCreate, NodeDelete, NodeSet, NodeAffiliations, NodeSubscriptions, NodeSchema) + subcommands = ( + NodeInfo, + NodeCreate, + NodeDelete, + NodeSet, + NodeAffiliations, + NodeSubscriptions, + NodeSchema, + ) def __init__(self, host): - super(Node, self).__init__(host, 'node', use_profile=False, help=_('node handling')) + super(Node, self).__init__( + host, "node", use_profile=False, help=_("node handling") + ) class Set(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'publish a new item or update an existing one')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "set", + use_pubsub=True, + pubsub_flags={C.NODE}, + help=_(u"publish a new item or update an existing one"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("item", type=base.unicode_decoder, nargs='?', default=u'', help=_(u"id, URL of the item to update, keyword, or nothing for new item")) + self.parser.add_argument( + "item", + type=base.unicode_decoder, + nargs="?", + default=u"", + help=_(u"id, URL of the item to update, keyword, or nothing for new item"), + ) def psItemsSendCb(self, published_id): if published_id: @@ -491,50 +686,71 @@ try: from lxml import etree except ImportError: - self.disp(u"lxml module must be installed to use edit, please install it with \"pip install lxml\"", error=True) + self.disp( + u'lxml module must be installed to use edit, please install it with "pip install lxml"', + error=True, + ) self.host.quit(1) try: element = etree.parse(sys.stdin).getroot() except Exception as e: - self.parser.error(_(u"Can't parse the payload XML in input: {msg}").format(msg=e)) - if element.tag in ('item', '{http://jabber.org/protocol/pubsub}item'): + self.parser.error( + _(u"Can't parse the payload XML in input: {msg}").format(msg=e) + ) + if element.tag in ("item", "{http://jabber.org/protocol/pubsub}item"): if len(element) > 1: - self.parser.error(_(u"<item> can only have one child element (the payload)")) + self.parser.error( + _(u"<item> can only have one child element (the payload)") + ) element = element[0] - payload = etree.tostring(element, encoding='unicode') + payload = etree.tostring(element, encoding="unicode") - self.host.bridge.psItemSend(self.args.service, - self.args.node, - payload, - self.args.item, - {}, - self.profile, - callback=self.psItemsSendCb, - errback=partial(self.errback, - msg=_(u"can't send item: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + self.host.bridge.psItemSend( + self.args.service, + self.args.node, + payload, + self.args.item, + {}, + self.profile, + callback=self.psItemsSendCb, + errback=partial( + self.errback, + msg=_(u"can't send item: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class Get(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_LIST_XML, use_pubsub=True, pubsub_flags={C.NODE, C.MULTI_ITEMS}, help=_(u'get pubsub item(s)')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "get", + use_output=C.OUTPUT_LIST_XML, + use_pubsub=True, + pubsub_flags={C.NODE, C.MULTI_ITEMS}, + help=_(u"get pubsub item(s)"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("-S", "--sub-id", type=base.unicode_decoder, default=u'', - help=_(u"subscription id")) - # TODO: a key(s) argument to select keys to display + self.parser.add_argument( + "-S", + "--sub-id", + type=base.unicode_decoder, + default=u"", + help=_(u"subscription id"), + ) + # TODO: a key(s) argument to select keys to display # TODO: add MAM filters - def psItemsGetCb(self, ps_result): self.output(ps_result[0]) self.host.quit(C.EXIT_OK) def psItemsGetEb(self, failure_): - self.disp(u"can't get pubsub items: {reason}".format( - reason=failure_), error=True) + self.disp(u"can't get pubsub items: {reason}".format(reason=failure_), error=True) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): @@ -547,27 +763,41 @@ {}, self.profile, callback=self.psItemsGetCb, - errback=self.psItemsGetEb) + errback=self.psItemsGetEb, + ) + class Delete(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'delete', use_pubsub=True, pubsub_flags={C.NODE, C.SINGLE_ITEM}, help=_(u'delete an item')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "delete", + use_pubsub=True, + pubsub_flags={C.NODE, C.SINGLE_ITEM}, + help=_(u"delete an item"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("-f", "--force", action='store_true', help=_(u"delete without confirmation")) - self.parser.add_argument("-N", "--notify", action='store_true', help=_(u"notify deletion")) + self.parser.add_argument( + "-f", "--force", action="store_true", help=_(u"delete without confirmation") + ) + self.parser.add_argument( + "-N", "--notify", action="store_true", help=_(u"notify deletion") + ) def psItemsDeleteCb(self): - self.disp(_(u'item {item_id} has been deleted').format(item_id=self.args.item)) + self.disp(_(u"item {item_id} has been deleted").format(item_id=self.args.item)) self.host.quit(C.EXIT_OK) def start(self): if not self.args.item: self.parser.error(_(u"You need to specify an item to delete")) if not self.args.force: - message = _(u"Are you sure to delete item {item_id} ?").format(item_id=self.args.item) + message = _(u"Are you sure to delete item {item_id} ?").format( + item_id=self.args.item + ) self.host.confirmOrQuit(message, _(u"item deletion cancelled")) self.host.bridge.psRetractItem( self.args.service, @@ -576,16 +806,26 @@ self.args.notify, self.profile, callback=self.psItemsDeleteCb, - errback=partial(self.errback, - msg=_(u"can't delete item: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't delete item: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class Edit(base.CommandBase, common.BaseEdit): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'edit', use_verbose=True, use_pubsub=True, - pubsub_flags={C.NODE, C.SINGLE_ITEM}, use_draft=True, help=_(u'edit an existing or new pubsub item')) + base.CommandBase.__init__( + self, + host, + "edit", + use_verbose=True, + use_pubsub=True, + pubsub_flags={C.NODE, C.SINGLE_ITEM}, + use_draft=True, + help=_(u"edit an existing or new pubsub item"), + ) common.BaseEdit.__init__(self, self.host, PUBSUB_TMP_DIR) def add_parser_options(self): @@ -596,7 +836,14 @@ self.runEditor("pubsub_editor_args", content_file_path, content_file_obj) def publish(self, content): - published_id = self.host.bridge.psItemSend(self.pubsub_service, self.pubsub_node, content, self.pubsub_item or '', {}, self.profile) + published_id = self.host.bridge.psItemSend( + self.pubsub_service, + self.pubsub_node, + content, + self.pubsub_item or "", + {}, + self.profile, + ) if published_id: self.disp(u"Item published at {pub_id}".format(pub_id=published_id)) else: @@ -606,38 +853,52 @@ try: from lxml import etree except ImportError: - self.disp(u"lxml module must be installed to use edit, please install it with \"pip install lxml\"", error=True) + self.disp( + u'lxml module must be installed to use edit, please install it with "pip install lxml"', + error=True, + ) self.host.quit(1) items = [item] if item is not None else [] - item_raw = self.host.bridge.psItemsGet(service, node, 1, items, "", {}, self.profile)[0][0] + item_raw = self.host.bridge.psItemsGet( + service, node, 1, items, "", {}, self.profile + )[0][0] parser = etree.XMLParser(remove_blank_text=True) item_elt = etree.fromstring(item_raw, parser) - item_id = item_elt.get('id') + item_id = item_elt.get("id") try: payload = item_elt[0] except IndexError: - self.disp(_(u'Item has not payload'), 1) - return u'' + self.disp(_(u"Item has not payload"), 1) + return u"" return etree.tostring(payload, encoding="unicode", pretty_print=True), item_id def start(self): - self.pubsub_service, self.pubsub_node, self.pubsub_item, content_file_path, content_file_obj = self.getItemPath() + self.pubsub_service, self.pubsub_node, self.pubsub_item, content_file_path, content_file_obj = ( + self.getItemPath() + ) self.edit(content_file_path, content_file_obj) class Subscribe(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'subscribe', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'subscribe to a node')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "subscribe", + use_pubsub=True, + pubsub_flags={C.NODE}, + use_verbose=True, + help=_(u"subscribe to a node"), + ) + self.need_loop = True def add_parser_options(self): pass def psSubscribeCb(self, sub_id): - self.disp(_(u'subscription done'), 1) + self.disp(_(u"subscription done"), 1) if sub_id: - self.disp(_(u'subscription id: {sub_id}').format(sub_id=sub_id)) + self.disp(_(u"subscription id: {sub_id}").format(sub_id=sub_id)) self.host.quit() def start(self): @@ -647,23 +908,34 @@ {}, self.profile, callback=self.psSubscribeCb, - errback=partial(self.errback, - msg=_(u"can't subscribe to node: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't subscribe to node: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class Unsubscribe(base.CommandBase): # TODO: voir pourquoi NodeNotFound sur subscribe juste après unsubscribe def __init__(self, host): - base.CommandBase.__init__(self, host, 'unsubscribe', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'unsubscribe from a node')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "unsubscribe", + use_pubsub=True, + pubsub_flags={C.NODE}, + use_verbose=True, + help=_(u"unsubscribe from a node"), + ) + self.need_loop = True def add_parser_options(self): pass def psUnsubscribeCb(self): - self.disp(_(u'subscription removed'), 1) + self.disp(_(u"subscription removed"), 1) self.host.quit() def start(self): @@ -672,16 +944,25 @@ self.args.node, self.profile, callback=self.psUnsubscribeCb, - errback=partial(self.errback, - msg=_(u"can't unsubscribe from node: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't unsubscribe from node: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class Subscriptions(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'subscriptions', use_output=C.OUTPUT_LIST_DICT, use_pubsub=True, help=_(u'retrieve all subscriptions on a service')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "subscriptions", + use_output=C.OUTPUT_LIST_DICT, + use_pubsub=True, + help=_(u"retrieve all subscriptions on a service"), + ) + self.need_loop = True def add_parser_options(self): pass @@ -696,16 +977,25 @@ self.args.node, self.profile, callback=self.psSubscriptionsGetCb, - errback=partial(self.errback, - msg=_(u"can't retrieve subscriptions: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't retrieve subscriptions: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class Affiliations(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'affiliations', use_output=C.OUTPUT_DICT, use_pubsub=True, help=_(u'retrieve all affiliations on a service')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "affiliations", + use_output=C.OUTPUT_DICT, + use_pubsub=True, + help=_(u"retrieve all affiliations on a service"), + ) + self.need_loop = True def add_parser_options(self): pass @@ -715,8 +1005,9 @@ self.host.quit() def psAffiliationsGetEb(self, failure_): - self.disp(u"can't get node affiliations: {reason}".format( - reason=failure_), error=True) + self.disp( + u"can't get node affiliations: {reason}".format(reason=failure_), error=True + ) self.host.quit(C.EXIT_BRIDGE_ERRBACK) def start(self): @@ -725,24 +1016,35 @@ self.args.node, self.profile, callback=self.psAffiliationsGetCb, - errback=self.psAffiliationsGetEb) + errback=self.psAffiliationsGetEb, + ) class Search(base.CommandBase): """this command to a search without using MAM, i.e. by checking every items if dound by itself, so it may be heavy in resources both for server and client""" + RE_FLAGS = re.MULTILINE | re.UNICODE - EXEC_ACTIONS = (u'exec', u'external') + EXEC_ACTIONS = (u"exec", u"external") def __init__(self, host): - base.CommandBase.__init__(self, host, 'search', use_output=C.OUTPUT_XML, use_pubsub=True, pubsub_flags={C.MULTI_ITEMS, C.NO_MAX}, - use_verbose=True, help=_(u'search items corresponding to filters')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "search", + use_output=C.OUTPUT_XML, + use_pubsub=True, + pubsub_flags={C.MULTI_ITEMS, C.NO_MAX}, + use_verbose=True, + help=_(u"search items corresponding to filters"), + ) + self.need_loop = True @property def etree(self): """load lxml.etree only if needed""" if self._etree is None: from lxml import etree + self._etree = etree return self._etree @@ -755,74 +1057,155 @@ return (type_, value) def add_parser_options(self): - self.parser.add_argument("-D", "--max-depth", type=int, default=0, help=_(u"maximum depth of recursion (will search linked nodes if > 0, default: 0)")) - self.parser.add_argument("-m", "--max", type=int, default=30, help=_(u"maximum number of items to get per node ({} to get all items, default: 30)".format(C.NO_LIMIT))) - self.parser.add_argument("-N", "--namespace", action='append', nargs=2, default=[], - metavar="NAME NAMESPACE", help=_(u"namespace to use for xpath")) + self.parser.add_argument( + "-D", + "--max-depth", + type=int, + default=0, + help=_( + u"maximum depth of recursion (will search linked nodes if > 0, default: 0)" + ), + ) + self.parser.add_argument( + "-m", + "--max", + type=int, + default=30, + help=_( + u"maximum number of items to get per node ({} to get all items, default: 30)".format( + C.NO_LIMIT + ) + ), + ) + self.parser.add_argument( + "-N", + "--namespace", + action="append", + nargs=2, + default=[], + metavar="NAME NAMESPACE", + help=_(u"namespace to use for xpath"), + ) # filters - filter_text = partial(self.filter_opt, type_=u'text') - filter_re = partial(self.filter_opt, type_=u'regex') - filter_xpath = partial(self.filter_opt, type_=u'xpath') - filter_python = partial(self.filter_opt, type_=u'python') - filters = self.parser.add_argument_group(_(u'filters'), _(u'only items corresponding to following filters will be kept')) - filters.add_argument("-t", "--text", - action='append', dest='filters', type=filter_text, - metavar='TEXT', - help=_(u"full text filter, item must contain this string (XML included)")) - filters.add_argument("-r", "--regex", - action='append', dest='filters', type=filter_re, - metavar='EXPRESSION', - help=_(u"like --text but using a regular expression")) - filters.add_argument("-x", "--xpath", - action='append', dest='filters', type=filter_xpath, - metavar='XPATH', - help=_(u"filter items which has elements matching this xpath")) - filters.add_argument("-P", "--python", - action='append', dest='filters', type=filter_python, - metavar='PYTHON_CODE', - help=_(u'Python expression which much return a bool (True to keep item, False to reject it). "item" is raw text item, "item_xml" is lxml\'s etree.Element')) + filter_text = partial(self.filter_opt, type_=u"text") + filter_re = partial(self.filter_opt, type_=u"regex") + filter_xpath = partial(self.filter_opt, type_=u"xpath") + filter_python = partial(self.filter_opt, type_=u"python") + filters = self.parser.add_argument_group( + _(u"filters"), + _(u"only items corresponding to following filters will be kept"), + ) + filters.add_argument( + "-t", + "--text", + action="append", + dest="filters", + type=filter_text, + metavar="TEXT", + help=_(u"full text filter, item must contain this string (XML included)"), + ) + filters.add_argument( + "-r", + "--regex", + action="append", + dest="filters", + type=filter_re, + metavar="EXPRESSION", + help=_(u"like --text but using a regular expression"), + ) + filters.add_argument( + "-x", + "--xpath", + action="append", + dest="filters", + type=filter_xpath, + metavar="XPATH", + help=_(u"filter items which has elements matching this xpath"), + ) + filters.add_argument( + "-P", + "--python", + action="append", + dest="filters", + type=filter_python, + metavar="PYTHON_CODE", + help=_( + u'Python expression which much return a bool (True to keep item, False to reject it). "item" is raw text item, "item_xml" is lxml\'s etree.Element' + ), + ) # filters flags - flag_case = partial(self.filter_flag, type_=u'ignore-case') - flag_invert = partial(self.filter_flag, type_=u'invert') - flag_dotall = partial(self.filter_flag, type_=u'dotall') - flag_matching = partial(self.filter_flag, type_=u'only-matching') - flags = self.parser.add_argument_group(_(u'filters flags'), _(u'filters modifiers (change behaviour of following filters)')) - flags.add_argument("-C", "--ignore-case", - action='append', dest='filters', type=flag_case, - const=('ignore-case', True), nargs='?', - metavar='BOOLEAN', - help=_(u"(don't) ignore case in following filters (default: case sensitive)")) - flags.add_argument("-I", "--invert", - action='append', dest='filters', type=flag_invert, - const=('invert', True), nargs='?', - metavar='BOOLEAN', - help=_(u"(don't) invert effect of following filters (default: don't invert)")) - flags.add_argument("-A", "--dot-all", - action='append', dest='filters', type=flag_dotall, - const=('dotall', True), nargs='?', - metavar='BOOLEAN', - help=_(u"(don't) use DOTALL option for regex (default: don't use)")) - flags.add_argument("-o", "--only-matching", - action='append', dest='filters', type=flag_matching, - const=('only-matching', True), nargs='?', - metavar='BOOLEAN', - help=_(u"keep only the matching part of the item")) + flag_case = partial(self.filter_flag, type_=u"ignore-case") + flag_invert = partial(self.filter_flag, type_=u"invert") + flag_dotall = partial(self.filter_flag, type_=u"dotall") + flag_matching = partial(self.filter_flag, type_=u"only-matching") + flags = self.parser.add_argument_group( + _(u"filters flags"), + _(u"filters modifiers (change behaviour of following filters)"), + ) + flags.add_argument( + "-C", + "--ignore-case", + action="append", + dest="filters", + type=flag_case, + const=("ignore-case", True), + nargs="?", + metavar="BOOLEAN", + help=_(u"(don't) ignore case in following filters (default: case sensitive)"), + ) + flags.add_argument( + "-I", + "--invert", + action="append", + dest="filters", + type=flag_invert, + const=("invert", True), + nargs="?", + metavar="BOOLEAN", + help=_(u"(don't) invert effect of following filters (default: don't invert)"), + ) + flags.add_argument( + "-A", + "--dot-all", + action="append", + dest="filters", + type=flag_dotall, + const=("dotall", True), + nargs="?", + metavar="BOOLEAN", + help=_(u"(don't) use DOTALL option for regex (default: don't use)"), + ) + flags.add_argument( + "-o", + "--only-matching", + action="append", + dest="filters", + type=flag_matching, + const=("only-matching", True), + nargs="?", + metavar="BOOLEAN", + help=_(u"keep only the matching part of the item"), + ) # action - self.parser.add_argument("action", - default="print", - nargs='?', - choices=('print', 'exec', 'external'), - help=_(u"action to do on found items (default: print)")) + self.parser.add_argument( + "action", + default="print", + nargs="?", + choices=("print", "exec", "external"), + help=_(u"action to do on found items (default: print)"), + ) self.parser.add_argument("command", nargs=argparse.REMAINDER) def psItemsGetEb(self, failure_, service, node): - self.disp(u"can't get pubsub items at {service} (node: {node}): {reason}".format( - service=service, - node=node, - reason=failure_), error=True) + self.disp( + u"can't get pubsub items at {service} (node: {node}): {reason}".format( + service=service, node=node, reason=failure_ + ), + error=True, + ) self.to_get -= 1 def getItems(self, depth, service, node, items): @@ -837,8 +1220,8 @@ {}, self.profile, callback=search, - errback=errback - ) + errback=errback, + ) self.to_get += 1 def _checkPubsubURL(self, match, found_nodes): @@ -848,16 +1231,15 @@ this list will be filled while xmpp: URIs are discovered """ url = match.group(0) - if url.startswith(u'xmpp'): + if url.startswith(u"xmpp"): try: url_data = uri.parseXMPPUri(url) except ValueError: return - if url_data[u'type'] == u'pubsub': - found_node = {u'service': url_data[u'path'], - u'node': url_data[u'node']} - if u'item' in url_data: - found_node[u'item'] = url_data[u'item'] + if url_data[u"type"] == u"pubsub": + found_node = {u"service": url_data[u"path"], u"node": url_data[u"node"]} + if u"item" in url_data: + found_node[u"item"] = url_data[u"item"] found_nodes.append(found_node) def getSubNodes(self, item, depth): @@ -866,17 +1248,23 @@ checkURI = partial(self._checkPubsubURL, found_nodes=found_nodes) strings.RE_URL.sub(checkURI, item) for data in found_nodes: - self.getItems(depth+1, - data[u'service'], - data[u'node'], - [data[u'item']] if u'item' in data else [] - ) + self.getItems( + depth + 1, + data[u"service"], + data[u"node"], + [data[u"item"]] if u"item" in data else [], + ) def parseXml(self, item): try: return self.etree.fromstring(item) except self.etree.XMLSyntaxError: - self.disp(_(u"item doesn't looks like XML, you have probably used --only-matching somewhere before and we have no more XML"), error=True) + self.disp( + _( + u"item doesn't looks like XML, you have probably used --only-matching somewhere before and we have no more XML" + ), + error=True, + ) self.host.quit(C.EXIT_BAD_ARG) def filter(self, item): @@ -897,7 +1285,7 @@ ## filters - if type_ == u'text': + if type_ == u"text": if ignore_case: if value.lower() not in item.lower(): keep = False @@ -907,9 +1295,14 @@ if keep and only_matching: # doesn't really make sens to keep a fixed string # so we raise an error - self.host.disp(_(u"--only-matching used with fixed --text string, are you sure?"), error=True) + self.host.disp( + _( + u"--only-matching used with fixed --text string, are you sure?" + ), + error=True, + ) self.host.quit(C.EXIT_BAD_ARG) - elif type_ == u'regex': + elif type_ == u"regex": flags = self.RE_FLAGS if ignore_case: flags |= re.IGNORECASE @@ -920,29 +1313,29 @@ if keep and only_matching: item = match.group() item_xml = None - elif type_ == u'xpath': + elif type_ == u"xpath": if item_xml is None: item_xml = self.parseXml(item) try: elts = item_xml.xpath(value, namespaces=self.args.namespace) except self.etree.XPathEvalError as e: - self.disp(_(u"can't use xpath: {reason}").format(reason=e), error=True) + self.disp( + _(u"can't use xpath: {reason}").format(reason=e), error=True + ) self.host.quit(C.EXIT_BAD_ARG) keep = bool(elts) if keep and only_matching: item_xml = elts[0] try: - item = self.etree.tostring(item_xml, encoding='unicode') + item = self.etree.tostring(item_xml, encoding="unicode") except TypeError: # we have a string only, not an element item = unicode(item_xml) item_xml = None - elif type_ == u'python': + elif type_ == u"python": if item_xml is None: item_xml = self.parseXml(item) - cmd_ns = {u'item': item, - u'item_xml': item_xml - } + cmd_ns = {u"item": item, u"item_xml": item_xml} try: keep = eval(value, cmd_ns) except SyntaxError as e: @@ -951,18 +1344,20 @@ ## flags - elif type_ == u'ignore-case': + elif type_ == u"ignore-case": ignore_case = value - elif type_ == u'invert': + elif type_ == u"invert": invert = value - # we need to continue, else loop would end here + # we need to continue, else loop would end here continue - elif type_ == u'dotall': + elif type_ == u"dotall": dotall = value - elif type_ == u'only-matching': + elif type_ == u"only-matching": only_matching = value else: - raise exceptions.InternalError(_(u"unknown filter type {type}").format(type=type_)) + raise exceptions.InternalError( + _(u"unknown filter type {type}").format(type=type_) + ) if invert: keep = not keep @@ -977,7 +1372,7 @@ @param item(unicode): accepted item """ action = self.args.action - if action == u'print' or self.host.verbosity > 0: + if action == u"print" or self.host.verbosity > 0: try: self.output(item) except self.etree.XMLSyntaxError: @@ -986,34 +1381,43 @@ self.disp(item) if action in self.EXEC_ACTIONS: item_elt = self.parseXml(item) - if action == u'exec': - use = {'service': metadata[u'service'], - 'node': metadata[u'node'], - 'item': item_elt.get('id'), - 'profile': self.profile - } + if action == u"exec": + use = { + "service": metadata[u"service"], + "node": metadata[u"node"], + "item": item_elt.get("id"), + "profile": self.profile, + } # we need to send a copy of self.args.command # else it would be modified - parser_args, use_args = arg_tools.get_use_args(self.host, - self.args.command, - use, - verbose=self.host.verbosity > 1 - ) + parser_args, use_args = arg_tools.get_use_args( + self.host, self.args.command, use, verbose=self.host.verbosity > 1 + ) cmd_args = sys.argv[0:1] + parser_args + use_args else: cmd_args = self.args.command - - self.disp(u'COMMAND: {command}'.format( - command = u' '.join([arg_tools.escape(a) for a in cmd_args])), 2) - if action == u'exec': + self.disp( + u"COMMAND: {command}".format( + command=u" ".join([arg_tools.escape(a) for a in cmd_args]) + ), + 2, + ) + if action == u"exec": ret = subprocess.call(cmd_args) else: p = subprocess.Popen(cmd_args, stdin=subprocess.PIPE) - p.communicate(item.encode('utf-8')) + p.communicate(item.encode("utf-8")) ret = p.wait() if ret != 0: - self.disp(A.color(C.A_FAILURE, _(u"executed command failed with exit code {code}").format(code=ret))) + self.disp( + A.color( + C.A_FAILURE, + _(u"executed command failed with exit code {code}").format( + code=ret + ), + ) + ) def search(self, items_data, depth): """callback of getItems @@ -1033,7 +1437,7 @@ continue self.doItemAction(item, metadata) - # we check if we got all getItems results + # we check if we got all getItems results self.to_get -= 1 if self.to_get == 0: # yes, we can quit @@ -1043,8 +1447,11 @@ def start(self): if self.args.command: if self.args.action not in self.EXEC_ACTIONS: - self.parser.error(_(u"Command can only be used with {actions} actions").format( - actions=u', '.join(self.EXEC_ACTIONS))) + self.parser.error( + _(u"Command can only be used with {actions} actions").format( + actions=u", ".join(self.EXEC_ACTIONS) + ) + ) else: if self.args.action in self.EXEC_ACTIONS: self.parser.error(_(u"you need to specify a command to execute")) @@ -1057,64 +1464,105 @@ self._etree = None if self.args.filters is None: self.args.filters = [] - self.args.namespace = dict(self.args.namespace + [('pubsub', "http://jabber.org/protocol/pubsub")]) + self.args.namespace = dict( + self.args.namespace + [("pubsub", "http://jabber.org/protocol/pubsub")] + ) self.getItems(0, self.args.service, self.args.node, self.args.items) class Uri(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'uri', use_profile=False, use_pubsub=True, pubsub_flags={C.NODE, C.SINGLE_ITEM}, help=_(u'build URI')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "uri", + use_profile=False, + use_pubsub=True, + pubsub_flags={C.NODE, C.SINGLE_ITEM}, + help=_(u"build URI"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("-p", "--profile", type=base.unicode_decoder, default=C.PROF_KEY_DEFAULT, help=_(u"profile (used when no server is specified)")) + self.parser.add_argument( + "-p", + "--profile", + type=base.unicode_decoder, + default=C.PROF_KEY_DEFAULT, + help=_(u"profile (used when no server is specified)"), + ) def display_uri(self, jid_): uri_args = {} if not self.args.service: self.args.service = jid.JID(jid_).bare - for key in ('node', 'service', 'item'): + for key in ("node", "service", "item"): value = getattr(self.args, key) - if key == 'service': - key = 'path' + if key == "service": + key = "path" if value: uri_args[key] = value - self.disp(uri.buildXMPPUri(u'pubsub', **uri_args)) + self.disp(uri.buildXMPPUri(u"pubsub", **uri_args)) self.host.quit() def start(self): if not self.args.service: self.host.bridge.asyncGetParamA( - u'JabberID', - u'Connection', + u"JabberID", + u"Connection", profile_key=self.args.profile, callback=self.display_uri, - errback=partial(self.errback, - msg=_(u"can't retrieve jid: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't retrieve jid: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) else: self.display_uri(None) class HookCreate(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'create', use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'create a Pubsub hook')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "create", + use_pubsub=True, + pubsub_flags={C.NODE}, + help=_(u"create a Pubsub hook"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument('-t', '--type', default=u'python', choices=('python', 'python_file', 'python_code'), help=_(u"hook type")) - self.parser.add_argument('-P', '--persistent', action='store_true', help=_(u"make hook persistent across restarts")) - self.parser.add_argument("hook_arg", type=base.unicode_decoder, help=_(u"argument of the hook (depend of the type)")) + self.parser.add_argument( + "-t", + "--type", + default=u"python", + choices=("python", "python_file", "python_code"), + help=_(u"hook type"), + ) + self.parser.add_argument( + "-P", + "--persistent", + action="store_true", + help=_(u"make hook persistent across restarts"), + ) + self.parser.add_argument( + "hook_arg", + type=base.unicode_decoder, + help=_(u"argument of the hook (depend of the type)"), + ) @staticmethod def checkArgs(self): - if self.args.type == u'python_file': + if self.args.type == u"python_file": self.args.hook_arg = os.path.abspath(self.args.hook_arg) if not os.path.isfile(self.args.hook_arg): - self.parser.error(_(u"{path} is not a file").format(path=self.args.hook_arg)) + self.parser.error( + _(u"{path} is not a file").format(path=self.args.hook_arg) + ) def start(self): self.checkArgs(self) @@ -1126,24 +1574,49 @@ self.args.persistent, self.profile, callback=self.host.quit, - errback=partial(self.errback, - msg=_(u"can't create hook: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't create hook: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class HookDelete(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'delete', use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'delete a Pubsub hook')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "delete", + use_pubsub=True, + pubsub_flags={C.NODE}, + help=_(u"delete a Pubsub hook"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument('-t', '--type', default=u'', choices=('', 'python', 'python_file', 'python_code'), help=_(u"hook type to remove, empty to remove all (default: remove all)")) - self.parser.add_argument('-a', '--arg', dest='hook_arg', type=base.unicode_decoder, default=u'', help=_(u"argument of the hook to remove, empty to remove all (default: remove all)")) + self.parser.add_argument( + "-t", + "--type", + default=u"", + choices=("", "python", "python_file", "python_code"), + help=_(u"hook type to remove, empty to remove all (default: remove all)"), + ) + self.parser.add_argument( + "-a", + "--arg", + dest="hook_arg", + type=base.unicode_decoder, + default=u"", + help=_( + u"argument of the hook to remove, empty to remove all (default: remove all)" + ), + ) def psHookRemoveCb(self, nb_deleted): - self.disp(_(u'{nb_deleted} hook(s) have been deleted').format( - nb_deleted = nb_deleted)) + self.disp( + _(u"{nb_deleted} hook(s) have been deleted").format(nb_deleted=nb_deleted) + ) self.host.quit() def start(self): @@ -1155,15 +1628,23 @@ self.args.hook_arg, self.profile, callback=self.psHookRemoveCb, - errback=partial(self.errback, - msg=_(u"can't delete hook: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't delete hook: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class HookList(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'list', use_output=C.OUTPUT_LIST_DICT, help=_(u'list hooks of a profile')) + base.CommandBase.__init__( + self, + host, + "list", + use_output=C.OUTPUT_LIST_DICT, + help=_(u"list hooks of a profile"), + ) self.need_loop = True def add_parser_options(self): @@ -1171,7 +1652,7 @@ def psHookListCb(self, data): if not data: - self.disp(_(u'No hook found.')) + self.disp(_(u"No hook found.")) self.output(data) self.host.quit() @@ -1179,20 +1660,44 @@ self.host.bridge.psHookList( self.profile, callback=self.psHookListCb, - errback=partial(self.errback, - msg=_(u"can't list hooks: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't list hooks: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class Hook(base.CommandBase): subcommands = (HookCreate, HookDelete, HookList) def __init__(self, host): - super(Hook, self).__init__(host, 'hook', use_profile=False, use_verbose=True, help=_('trigger action on Pubsub notifications')) + super(Hook, self).__init__( + host, + "hook", + use_profile=False, + use_verbose=True, + help=_("trigger action on Pubsub notifications"), + ) class Pubsub(base.CommandBase): - subcommands = (Set, Get, Delete, Edit, Subscribe, Unsubscribe, Subscriptions, Node, Affiliations, Search, Hook, Uri) + subcommands = ( + Set, + Get, + Delete, + Edit, + Subscribe, + Unsubscribe, + Subscriptions, + Node, + Affiliations, + Search, + Hook, + Uri, + ) def __init__(self, host): - super(Pubsub, self).__init__(host, 'pubsub', use_profile=False, help=_('PubSub nodes/items management')) + super(Pubsub, self).__init__( + host, "pubsub", use_profile=False, help=_("PubSub nodes/items management") + )
--- a/sat_frontends/jp/cmd_shell.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_shell.py Wed Jun 27 20:14:46 2018 +0200 @@ -30,19 +30,22 @@ import subprocess __commands__ = ["Shell"] -INTRO = _(u"""Welcome to {app_name} shell, the Salut à Toi shell ! +INTRO = _( + u"""Welcome to {app_name} shell, the Salut à Toi shell ! This enrironment helps you using several {app_name} commands with similar parameters. To quit, just enter "quit" or press C-d. Enter "help" or "?" to know what to do -""").format(app_name = C.APP_NAME) +""" +).format(app_name=C.APP_NAME) class Shell(base.CommandBase, cmd.Cmd): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'shell', help=_(u'launch jp in shell (REPL) mode')) + base.CommandBase.__init__( + self, host, "shell", help=_(u"launch jp in shell (REPL) mode") + ) cmd.Cmd.__init__(self) def parse_args(self, args): @@ -51,18 +54,20 @@ def update_path(self): self._cur_parser = self.host.parser - self.help = u'' + self.help = u"" for idx, path_elt in enumerate(self.path): try: self._cur_parser = arg_tools.get_cmd_choices(path_elt, self._cur_parser) except exceptions.NotFound: - self.disp(_(u'bad command path'), error=True) - self.path=self.path[:idx] + self.disp(_(u"bad command path"), error=True) + self.path = self.path[:idx] break else: self.help = self._cur_parser - self.prompt = A.color(C.A_PROMPT_PATH, u'/'.join(self.path)) + A.color(C.A_PROMPT_SUF, u'> ') + self.prompt = A.color(C.A_PROMPT_PATH, u"/".join(self.path)) + A.color( + C.A_PROMPT_SUF, u"> " + ) try: self.actions = arg_tools.get_cmd_choices(parser=self._cur_parser).keys() except exceptions.NotFound: @@ -94,12 +99,15 @@ # didn't found a nice way to work around it so far # Situation should be better when we'll move away from python-dbus if self.verbose: - self.disp(_(u"COMMAND {external}=> {args}").format( - external = _(u'(external) ') if external else u'', - args=u' '.join(self.format_args(args)))) + self.disp( + _(u"COMMAND {external}=> {args}").format( + external=_(u"(external) ") if external else u"", + args=u" ".join(self.format_args(args)), + ) + ) if not external: args = sys.argv[0:1] + args - ret_code= subprocess.call(args) + ret_code = subprocess.call(args) # XXX: below is a way to launch the command without creating a new process # may be used when a solution to the aforementioned issue is there # try: @@ -113,7 +121,15 @@ # ret_code = 0 if ret_code != 0: - self.disp(A.color(C.A_FAILURE, u'command failed with an error code of {err_no}'.format(err_no=ret_code)), error=True) + self.disp( + A.color( + C.A_FAILURE, + u"command failed with an error code of {err_no}".format( + err_no=ret_code + ), + ), + error=True, + ) return ret_code def default(self, args): @@ -122,19 +138,19 @@ will launch the command with args on the line (i.e. will launch do [args]) """ - if args=='EOF': - self.do_quit('') + if args == "EOF": + self.do_quit("") self.do_do(args) def do_help(self, args): """show help message""" if not args: - self.disp(A.color(C.A_HEADER, _(u'Shell commands:')), no_lf=True) + self.disp(A.color(C.A_HEADER, _(u"Shell commands:")), no_lf=True) super(Shell, self).do_help(args) if not args: - self.disp(A.color(C.A_HEADER, _(u'Action commands:'))) - help_list = self._cur_parser.format_help().split('\n\n') - print('\n\n'.join(help_list[1 if self.path else 2:])) + self.disp(A.color(C.A_HEADER, _(u"Action commands:"))) + help_list = self._cur_parser.format_help().split("\n\n") + print("\n\n".join(help_list[1 if self.path else 2 :])) def do_debug(self, args): """launch internal debugger""" @@ -149,18 +165,21 @@ args = self.parse_args(args) if args: self.verbose = C.bool(args[0]) - self.disp(_(u'verbose mode is {status}').format( - status = _(u'ENABLED') if self.verbose else _(u'DISABLED'))) + self.disp( + _(u"verbose mode is {status}").format( + status=_(u"ENABLED") if self.verbose else _(u"DISABLED") + ) + ) def do_cmd(self, args): """change command path""" - if args == '..': + if args == "..": self.path = self.path[:-1] else: - if not args or args[0] == '/': + if not args or args[0] == "/": self.path = [] - args = '/'.join(args.split()) - for path_elt in args.split('/'): + args = "/".join(args.split()) + for path_elt in args.split("/"): path_elt = path_elt.strip() if not path_elt: continue @@ -170,7 +189,7 @@ def do_version(self, args): """show current SàT/jp version""" try: - self.host.run(['--version']) + self.host.run(["--version"]) except SystemExit: pass @@ -182,28 +201,30 @@ def do_do(self, args): """lauch a command""" args = self.parse_args(args) - if (self._not_default_profile and - not '-p' in args and - not '--profile' in args and - not 'profile' in self.use): + if ( + self._not_default_profile + and not "-p" in args + and not "--profile" in args + and not "profile" in self.use + ): # profile is not specified and we are not using the default profile # so we need to add it in arguments to use current user profile if self.verbose: - self.disp(_(u'arg profile={profile} (logged profile)').format(profile=self.profile)) + self.disp( + _(u"arg profile={profile} (logged profile)").format( + profile=self.profile + ) + ) use = self.use.copy() - use['profile'] = self.profile + use["profile"] = self.profile else: use = self.use - # args may be modified by use_args # to remove subparsers from it - parser_args, use_args = arg_tools.get_use_args(self.host, - args, - use, - verbose=self.verbose, - parser=self._cur_parser - ) + parser_args, use_args = arg_tools.get_use_args( + self.host, args, use, verbose=self.verbose, parser=self._cur_parser + ) cmd_args = self.path + parser_args + use_args self.run_cmd(cmd_args) @@ -212,18 +233,31 @@ args = self.parse_args(args) if not args: if not self.use: - self.disp(_(u'no argument in USE')) + self.disp(_(u"no argument in USE")) else: - self.disp(_(u'arguments in USE:')) + self.disp(_(u"arguments in USE:")) for arg, value in self.use.iteritems(): - self.disp(_(A.color(C.A_SUBHEADER, arg, A.RESET, u' = ', arg_tools.escape(value)))) + self.disp( + _( + A.color( + C.A_SUBHEADER, + arg, + A.RESET, + u" = ", + arg_tools.escape(value), + ) + ) + ) elif len(args) != 2: - self.disp(u'bad syntax, please use:\nuse [arg] [value]', error=True) + self.disp(u"bad syntax, please use:\nuse [arg] [value]", error=True) else: - self.use[args[0]] = u' '.join(args[1:]) + self.use[args[0]] = u" ".join(args[1:]) if self.verbose: - self.disp('set {name} = {value}'.format( - name = args[0], value=arg_tools.escape(args[1]))) + self.disp( + "set {name} = {value}".format( + name=args[0], value=arg_tools.escape(args[1]) + ) + ) def do_use_clear(self, args): """unset one or many argument(s) in USE, or all of them if no arg is specified""" @@ -235,10 +269,15 @@ try: del self.use[arg] except KeyError: - self.disp(A.color(C.A_FAILURE, _(u'argument {name} not found').format(name=arg)), error=True) + self.disp( + A.color( + C.A_FAILURE, _(u"argument {name} not found").format(name=arg) + ), + error=True, + ) else: if self.verbose: - self.disp(_(u'argument {name} removed').format(name=arg)) + self.disp(_(u"argument {name} removed").format(name=arg)) def do_whoami(self, args): u"""print profile currently used""" @@ -246,7 +285,7 @@ def do_quit(self, args): u"""quit the shell""" - self.disp(_(u'good bye!')) + self.disp(_(u"good bye!")) self.host.quit() def do_exit(self, args): @@ -261,4 +300,4 @@ self.use = {} self.verbose = False self.update_path() - self.cmdloop(INTRO.encode('utf-8')) + self.cmdloop(INTRO.encode("utf-8"))
--- a/sat_frontends/jp/cmd_ticket.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_ticket.py Wed Jun 27 20:14:46 2018 +0200 @@ -28,17 +28,23 @@ __commands__ = ["Ticket"] -FIELDS_MAP = u'mapping' +FIELDS_MAP = u"mapping" class Get(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'get', use_verbose=True, - use_pubsub=True, pubsub_flags={C.MULTI_ITEMS}, - pubsub_defaults = {u'service': _(u'auto'), u'node': _(u'auto')}, - use_output=C.OUTPUT_LIST_XMLUI, help=_(u'get tickets')) - self.need_loop=True + base.CommandBase.__init__( + self, + host, + "get", + use_verbose=True, + use_pubsub=True, + pubsub_flags={C.MULTI_ITEMS}, + pubsub_defaults={u"service": _(u"auto"), u"node": _(u"auto")}, + use_output=C.OUTPUT_LIST_XMLUI, + help=_(u"get tickets"), + ) + self.need_loop = True def add_parser_options(self): pass @@ -53,67 +59,140 @@ self.args.node, self.args.max, self.args.items, - u'', + u"", {}, self.profile, callback=self.ticketsGetCb, - errback=partial(self.errback, - msg=_(u"can't get tickets: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.errback, + msg=_(u"can't get tickets: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) def start(self): - common.URIFinder(self, os.getcwd(), 'tickets', self.getTickets, meta_map={}) + common.URIFinder(self, os.getcwd(), "tickets", self.getTickets, meta_map={}) class Import(base.CommandAnswering): # TODO: factorize with blog/import def __init__(self, host): - super(Import, self).__init__(host, 'import', use_progress=True, help=_(u'import tickets from external software/dataset')) - self.need_loop=True + super(Import, self).__init__( + host, + "import", + use_progress=True, + help=_(u"import tickets from external software/dataset"), + ) + self.need_loop = True def add_parser_options(self): - self.parser.add_argument("importer", type=base.unicode_decoder, nargs='?', help=_(u"importer name, nothing to display importers list")) - self.parser.add_argument('-o', '--option', action='append', nargs=2, default=[], metavar=(u'NAME', u'VALUE'), - help=_(u"importer specific options (see importer description)")) - self.parser.add_argument('-m', '--map', action='append', nargs=2, default=[], metavar=(u'IMPORTED_FIELD', u'DEST_FIELD'), - help=_(u"specified field in import data will be put in dest field (default: use same field name, or ignore if it doesn't exist)")) - self.parser.add_argument('-s', '--service', type=base.unicode_decoder, default=u'', metavar=u'PUBSUB_SERVICE', - help=_(u"PubSub service where the items must be uploaded (default: server)")) - self.parser.add_argument('-n', '--node', type=base.unicode_decoder, default=u'', metavar=u'PUBSUB_NODE', - help=_(u"PubSub node where the items must be uploaded (default: tickets' defaults)")) - self.parser.add_argument("location", type=base.unicode_decoder, nargs='?', - help=_(u"importer data location (see importer description), nothing to show importer description")) + self.parser.add_argument( + "importer", + type=base.unicode_decoder, + nargs="?", + help=_(u"importer name, nothing to display importers list"), + ) + self.parser.add_argument( + "-o", + "--option", + action="append", + nargs=2, + default=[], + metavar=(u"NAME", u"VALUE"), + help=_(u"importer specific options (see importer description)"), + ) + self.parser.add_argument( + "-m", + "--map", + action="append", + nargs=2, + default=[], + metavar=(u"IMPORTED_FIELD", u"DEST_FIELD"), + help=_( + u"specified field in import data will be put in dest field (default: use same field name, or ignore if it doesn't exist)" + ), + ) + self.parser.add_argument( + "-s", + "--service", + type=base.unicode_decoder, + default=u"", + metavar=u"PUBSUB_SERVICE", + help=_(u"PubSub service where the items must be uploaded (default: server)"), + ) + self.parser.add_argument( + "-n", + "--node", + type=base.unicode_decoder, + default=u"", + metavar=u"PUBSUB_NODE", + help=_( + u"PubSub node where the items must be uploaded (default: tickets' defaults)" + ), + ) + self.parser.add_argument( + "location", + type=base.unicode_decoder, + nargs="?", + help=_( + u"importer data location (see importer description), nothing to show importer description" + ), + ) def onProgressStarted(self, metadata): - self.disp(_(u'Tickets upload started'),2) + self.disp(_(u"Tickets upload started"), 2) def onProgressFinished(self, metadata): - self.disp(_(u'Tickets uploaded successfully'),2) + self.disp(_(u"Tickets uploaded successfully"), 2) def onProgressError(self, error_msg): - self.disp(_(u'Error while uploading tickets: {}').format(error_msg),error=True) + self.disp(_(u"Error while uploading tickets: {}").format(error_msg), error=True) def error(self, failure): - self.disp(_("Error while trying to upload tickets: {reason}").format(reason=failure), error=True) + self.disp( + _("Error while trying to upload tickets: {reason}").format(reason=failure), + error=True, + ) self.host.quit(1) def start(self): if self.args.location is None: - for name in ('option', 'service', 'node'): + for name in ("option", "service", "node"): if getattr(self.args, name): - self.parser.error(_(u"{name} argument can't be used without location argument").format(name=name)) + self.parser.error( + _( + u"{name} argument can't be used without location argument" + ).format(name=name) + ) if self.args.importer is None: - self.disp(u'\n'.join([u'{}: {}'.format(name, desc) for name, desc in self.host.bridge.ticketsImportList()])) + self.disp( + u"\n".join( + [ + u"{}: {}".format(name, desc) + for name, desc in self.host.bridge.ticketsImportList() + ] + ) + ) else: try: - short_desc, long_desc = self.host.bridge.ticketsImportDesc(self.args.importer) + short_desc, long_desc = self.host.bridge.ticketsImportDesc( + self.args.importer + ) except Exception as e: - msg = [l for l in unicode(e).split('\n') if l][-1] # we only keep the last line + msg = [l for l in unicode(e).split("\n") if l][ + -1 + ] # we only keep the last line self.disp(msg) self.host.quit(1) else: - self.disp(u"{name}: {short_desc}\n\n{long_desc}".format(name=self.args.importer, short_desc=short_desc, long_desc=long_desc)) + self.disp( + u"{name}: {short_desc}\n\n{long_desc}".format( + name=self.args.importer, + short_desc=short_desc, + long_desc=long_desc, + ) + ) self.host.quit() else: # we have a location, an import is requested @@ -121,16 +200,32 @@ fields_map = dict(self.args.map) if fields_map: if FIELDS_MAP in options: - self.parser.error(_(u"fields_map must be specified either preencoded in --option or using --map, but not both at the same time")) + self.parser.error( + _( + u"fields_map must be specified either preencoded in --option or using --map, but not both at the same time" + ) + ) options[FIELDS_MAP] = json.dumps(fields_map) + def gotId(id_): self.progress_id = id_ - self.host.bridge.ticketsImport(self.args.importer, self.args.location, options, self.args.service, self.args.node, self.profile, - callback=gotId, errback=self.error) + + self.host.bridge.ticketsImport( + self.args.importer, + self.args.location, + options, + self.args.service, + self.args.node, + self.profile, + callback=gotId, + errback=self.error, + ) class Ticket(base.CommandBase): subcommands = (Get, Import) def __init__(self, host): - super(Ticket, self).__init__(host, 'ticket', use_profile=False, help=_('tickets handling')) + super(Ticket, self).__init__( + host, "ticket", use_profile=False, help=_("tickets handling") + )
--- a/sat_frontends/jp/cmd_uri.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/cmd_uri.py Wed Jun 27 20:14:46 2018 +0200 @@ -27,33 +27,44 @@ class Parse(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'parse', use_profile=False, use_output=C.OUTPUT_DICT, help=_(u'parse URI')) + base.CommandBase.__init__( + self, + host, + "parse", + use_profile=False, + use_output=C.OUTPUT_DICT, + help=_(u"parse URI"), + ) def add_parser_options(self): - self.parser.add_argument("uri", type=base.unicode_decoder, help=_(u"XMPP URI to parse")) + self.parser.add_argument( + "uri", type=base.unicode_decoder, help=_(u"XMPP URI to parse") + ) def start(self): self.output(uri.parseXMPPUri(self.args.uri)) class Build(base.CommandBase): - def __init__(self, host): - base.CommandBase.__init__(self, host, 'build', use_profile=False, help=_(u'build URI')) + base.CommandBase.__init__( + self, host, "build", use_profile=False, help=_(u"build URI") + ) def add_parser_options(self): self.parser.add_argument("type", type=base.unicode_decoder, help=_(u"URI type")) self.parser.add_argument("path", type=base.unicode_decoder, help=_(u"URI path")) - self.parser.add_argument("-f", + self.parser.add_argument( + "-f", "--field", type=base.unicode_decoder, - action='append', + action="append", nargs=2, - dest='fields', + dest="fields", metavar=(u"KEY", u"VALUE"), - help=_(u"URI fields")) + help=_(u"URI fields"), + ) def start(self): fields = dict(self.args.fields) if self.args.fields else {} @@ -64,4 +75,6 @@ subcommands = (Parse, Build) def __init__(self, host): - super(Uri, self).__init__(host, 'uri', use_profile=False, help=_('XMPP URI parsing/generation')) + super(Uri, self).__init__( + host, "uri", use_profile=False, help=_("XMPP URI parsing/generation") + )
--- a/sat_frontends/jp/common.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/common.py Wed Jun 27 20:14:46 2018 +0200 @@ -40,36 +40,36 @@ VIM_SPLIT_ARGS = "-c 'set nospr|vsplit|wincmd w|next|wincmd w'" EMACS_SPLIT_ARGS = '--eval "(split-window-horizontally)"' EDITOR_ARGS_MAGIC = { - 'vim': VIM_SPLIT_ARGS + ' {content_file} {metadata_file}', - 'gvim': VIM_SPLIT_ARGS + ' --nofork {content_file} {metadata_file}', - 'emacs': EMACS_SPLIT_ARGS + ' {content_file} {metadata_file}', - 'xemacs': EMACS_SPLIT_ARGS + ' {content_file} {metadata_file}', - 'nano': ' -F {content_file} {metadata_file}', - } + "vim": VIM_SPLIT_ARGS + " {content_file} {metadata_file}", + "gvim": VIM_SPLIT_ARGS + " --nofork {content_file} {metadata_file}", + "emacs": EMACS_SPLIT_ARGS + " {content_file} {metadata_file}", + "xemacs": EMACS_SPLIT_ARGS + " {content_file} {metadata_file}", + "nano": " -F {content_file} {metadata_file}", +} SECURE_UNLINK_MAX = 10 SECURE_UNLINK_DIR = ".backup" -METADATA_SUFF = '_metadata.json' +METADATA_SUFF = "_metadata.json" def ansi_ljust(s, width): """ljust method handling ANSI escape codes""" cleaned = regex.ansiRemove(s) - return s + u' ' * (width - len(cleaned)) + return s + u" " * (width - len(cleaned)) def ansi_center(s, width): """ljust method handling ANSI escape codes""" cleaned = regex.ansiRemove(s) diff = width - len(cleaned) - half = diff/2 - return half * u' ' + s + (half + diff % 2) * u' ' + half = diff / 2 + return half * u" " + s + (half + diff % 2) * u" " def ansi_rjust(s, width): """ljust method handling ANSI escape codes""" cleaned = regex.ansiRemove(s) - return u' ' * (width - len(cleaned)) + s + return u" " * (width - len(cleaned)) + s def getTmpDir(sat_conf, cat_dir, sub_dir=None): @@ -83,8 +83,8 @@ initial str) @return (str): path to the dir """ - local_dir = config.getConfig(sat_conf, '', 'local_dir', Exception) - path = [local_dir.encode('utf-8'), cat_dir.encode('utf-8')] + local_dir = config.getConfig(sat_conf, "", "local_dir", Exception) + path = [local_dir.encode("utf-8"), cat_dir.encode("utf-8")] if sub_dir is not None: path.append(regex.pathEscape(sub_dir)) return os.path.join(*path) @@ -102,7 +102,9 @@ # we split arguments first to avoid escaping issues in file names return [a.format(**format_kw) for a in shlex.split(cmd_line)] except ValueError as e: - host.disp(u"Couldn't parse editor cmd [{cmd}]: {reason}".format(cmd=cmd_line, reason=e)) + host.disp( + u"Couldn't parse editor cmd [{cmd}]: {reason}".format(cmd=cmd_line, reason=e) + ) return [] @@ -124,7 +126,7 @@ """ self.host = host self.sat_conf = config.parseMainConf() - self.cat_dir_str = cat_dir.encode('utf-8') + self.cat_dir_str = cat_dir.encode("utf-8") self.use_metadata = use_metadata def secureUnlink(self, path): @@ -138,7 +140,12 @@ if not os.path.isfile(path): raise OSError(u"path must link to a regular file") if not path.startswith(getTmpDir(self.sat_conf, self.cat_dir_str)): - self.disp(u"File {} is not in SàT temporary hierarchy, we do not remove it".format(path.decode('utf-8')), 2) + self.disp( + u"File {} is not in SàT temporary hierarchy, we do not remove it".format( + path.decode("utf-8") + ), + 2, + ) return # we have 2 files per draft with use_metadata, so we double max unlink_max = SECURE_UNLINK_MAX * 2 if self.use_metadata else SECURE_UNLINK_MAX @@ -148,19 +155,29 @@ filename = os.path.basename(path) backup_path = os.path.join(backup_dir, filename) # we move file to backup dir - self.host.disp(u"Backuping file {src} to {dst}".format( - src=path.decode('utf-8'), dst=backup_path.decode('utf-8')), 1) + self.host.disp( + u"Backuping file {src} to {dst}".format( + src=path.decode("utf-8"), dst=backup_path.decode("utf-8") + ), + 1, + ) os.rename(path, backup_path) # and if we exceeded the limit, we remove older file backup_files = [os.path.join(backup_dir, f) for f in os.listdir(backup_dir)] if len(backup_files) > unlink_max: backup_files.sort(key=lambda path: os.stat(path).st_mtime) - for path in backup_files[:len(backup_files) - unlink_max]: - self.host.disp(u"Purging backup file {}".format(path.decode('utf-8')), 2) + for path in backup_files[: len(backup_files) - unlink_max]: + self.host.disp(u"Purging backup file {}".format(path.decode("utf-8")), 2) os.unlink(path) - def runEditor(self, editor_args_opt, content_file_path, - content_file_obj, meta_file_path=None, meta_ori=None): + def runEditor( + self, + editor_args_opt, + content_file_path, + content_file_obj, + meta_file_path=None, + meta_ori=None, + ): """run editor to edit content and metadata @param editor_args_opt(unicode): option in [jp] section in configuration for @@ -178,24 +195,29 @@ # we calculate hashes to check for modifications import hashlib + content_file_obj.seek(0) tmp_ori_hash = hashlib.sha1(content_file_obj.read()).digest() content_file_obj.close() # we prepare arguments - editor = config.getConfig(self.sat_conf, 'jp', 'editor') or os.getenv('EDITOR', 'vi') + editor = config.getConfig(self.sat_conf, "jp", "editor") or os.getenv( + "EDITOR", "vi" + ) try: # is there custom arguments in sat.conf ? - editor_args = config.getConfig(self.sat_conf, 'jp', editor_args_opt, Exception) + editor_args = config.getConfig( + self.sat_conf, "jp", editor_args_opt, Exception + ) except (NoOptionError, NoSectionError): # no, we check if we know the editor and have special arguments if self.use_metadata: - editor_args = EDITOR_ARGS_MAGIC.get(os.path.basename(editor), '') + editor_args = EDITOR_ARGS_MAGIC.get(os.path.basename(editor), "") else: - editor_args = '' - parse_kwargs = {'content_file': content_file_path} + editor_args = "" + parse_kwargs = {"content_file": content_file_path} if self.use_metadata: - parse_kwargs['metadata_file'] = meta_file_path + parse_kwargs["metadata_file"] = meta_file_path args = parse_args(self.host, editor_args, **parse_kwargs) if not args: args = [content_file_path] @@ -205,61 +227,86 @@ # edition will now be checked, and data will be sent if it was a success if editor_exit != 0: - self.disp(u"Editor exited with an error code, so temporary file has not be deleted, and item is not published.\nYou can find temporary file at {path}".format( - path=content_file_path), error=True) + self.disp( + u"Editor exited with an error code, so temporary file has not be deleted, and item is not published.\nYou can find temporary file at {path}".format( + path=content_file_path + ), + error=True, + ) else: # main content try: - with open(content_file_path, 'rb') as f: + with open(content_file_path, "rb") as f: content = f.read() except (OSError, IOError): - self.disp(u"Can read file at {content_path}, have it been deleted?\nCancelling edition".format( - content_path=content_file_path), error=True) + self.disp( + u"Can read file at {content_path}, have it been deleted?\nCancelling edition".format( + content_path=content_file_path + ), + error=True, + ) self.host.quit(C.EXIT_NOT_FOUND) # metadata if self.use_metadata: try: - with open(meta_file_path, 'rb') as f: + with open(meta_file_path, "rb") as f: metadata = json.load(f) except (OSError, IOError): - self.disp(u"Can read file at {meta_file_path}, have it been deleted?\nCancelling edition".format( - content_path=content_file_path, meta_path=meta_file_path), error=True) + self.disp( + u"Can read file at {meta_file_path}, have it been deleted?\nCancelling edition".format( + content_path=content_file_path, meta_path=meta_file_path + ), + error=True, + ) self.host.quit(C.EXIT_NOT_FOUND) except ValueError: - self.disp(u"Can't parse metadata, please check it is correct JSON format. Cancelling edition.\n" + - "You can find tmp file at {content_path} and temporary meta file at {meta_path}.".format( - content_path=content_file_path, - meta_path=meta_file_path), error=True) + self.disp( + u"Can't parse metadata, please check it is correct JSON format. Cancelling edition.\n" + + "You can find tmp file at {content_path} and temporary meta file at {meta_path}.".format( + content_path=content_file_path, meta_path=meta_file_path + ), + error=True, + ) self.host.quit(C.EXIT_DATA_ERROR) - if self.use_metadata and not C.bool(metadata.get('publish', "true")): - self.disp(u'Publication blocked by "publish" key in metadata, cancelling edition.\n\n' + - "temporary file path:\t{content_path}\nmetadata file path:\t{meta_path}".format( - content_path=content_file_path, meta_path=meta_file_path), error=True) + if self.use_metadata and not C.bool(metadata.get("publish", "true")): + self.disp( + u'Publication blocked by "publish" key in metadata, cancelling edition.\n\n' + + "temporary file path:\t{content_path}\nmetadata file path:\t{meta_path}".format( + content_path=content_file_path, meta_path=meta_file_path + ), + error=True, + ) self.host.quit() if len(content) == 0: self.disp(u"Content is empty, cancelling the edition") - if not content_file_path.startswith(getTmpDir(self.sat_conf, self.cat_dir_str)): - self.disp(u"File are not in SàT temporary hierarchy, we do not remove them", 2) + if not content_file_path.startswith( + getTmpDir(self.sat_conf, self.cat_dir_str) + ): + self.disp( + u"File are not in SàT temporary hierarchy, we do not remove them", + 2, + ) self.host.quit() - self.disp(u"Deletion of {}".format(content_file_path.decode('utf-8')), 2) + self.disp(u"Deletion of {}".format(content_file_path.decode("utf-8")), 2) os.unlink(content_file_path) if self.use_metadata: - self.disp(u"Deletion of {}".format(meta_file_path.decode('utf-8')), 2) + self.disp(u"Deletion of {}".format(meta_file_path.decode("utf-8")), 2) os.unlink(meta_file_path) self.host.quit() # time to re-check the hash - elif (tmp_ori_hash == hashlib.sha1(content).digest() and - (not self.use_metadata or meta_ori == metadata)): + elif tmp_ori_hash == hashlib.sha1(content).digest() and ( + not self.use_metadata or meta_ori == metadata + ): self.disp(u"The content has not been modified, cancelling the edition") self.host.quit() else: # we can now send the item - content = content.decode('utf-8-sig') # we use utf-8-sig to avoid BOM + content = content.decode("utf-8-sig") # we use utf-8-sig to avoid BOM try: if self.use_metadata: self.publish(content, metadata) @@ -267,11 +314,21 @@ self.publish(content) except Exception as e: if self.use_metadata: - self.disp(u"Error while sending your item, the temporary files have been kept at {content_path} and {meta_path}: {reason}".format( - content_path=content_file_path, meta_path=meta_file_path, reason=e), error=True) + self.disp( + u"Error while sending your item, the temporary files have been kept at {content_path} and {meta_path}: {reason}".format( + content_path=content_file_path, + meta_path=meta_file_path, + reason=e, + ), + error=True, + ) else: - self.disp(u"Error while sending your item, the temporary file has been kept at {content_path}: {reason}".format( - content_path=content_file_path, reason=e), error=True) + self.disp( + u"Error while sending your item, the temporary file has been kept at {content_path}: {reason}".format( + content_path=content_file_path, reason=e + ), + error=True, + ) self.host.quit(1) self.secureUnlink(content_file_path) @@ -288,23 +345,32 @@ @param suff (str): suffix to use for the filename @return (tuple(file, str)): opened (w+b) file object and file path """ - suff = '.' + self.getTmpSuff() + suff = "." + self.getTmpSuff() cat_dir_str = self.cat_dir_str - tmp_dir = getTmpDir(self.sat_conf, self.cat_dir_str, self.profile.encode('utf-8')) + tmp_dir = getTmpDir(self.sat_conf, self.cat_dir_str, self.profile.encode("utf-8")) if not os.path.exists(tmp_dir): try: os.makedirs(tmp_dir) except OSError as e: - self.disp(u"Can't create {path} directory: {reason}".format( - path=tmp_dir, reason=e), error=True) + self.disp( + u"Can't create {path} directory: {reason}".format( + path=tmp_dir, reason=e + ), + error=True, + ) self.host.quit(1) try: - fd, path = tempfile.mkstemp(suffix=suff.encode('utf-8'), - prefix=time.strftime(cat_dir_str + '_%Y-%m-%d_%H:%M:%S_'), - dir=tmp_dir, text=True) - return os.fdopen(fd, 'w+b'), path + fd, path = tempfile.mkstemp( + suffix=suff.encode("utf-8"), + prefix=time.strftime(cat_dir_str + "_%Y-%m-%d_%H:%M:%S_"), + dir=tmp_dir, + text=True, + ) + return os.fdopen(fd, "w+b"), path except OSError as e: - self.disp(u"Can't create temporary file: {reason}".format(reason=e), error=True) + self.disp( + u"Can't create temporary file: {reason}".format(reason=e), error=True + ) self.host.quit(1) def getCurrentFile(self, profile): @@ -317,10 +383,17 @@ # the most recent file corresponding to temp file pattern # in tmp_dir, excluding metadata files cat_dir_str = self.cat_dir_str - tmp_dir = getTmpDir(self.sat_conf, self.cat_dir_str, profile.encode('utf-8')) - available = [path for path in glob.glob(os.path.join(tmp_dir, cat_dir_str + '_*')) if not path.endswith(METADATA_SUFF)] + tmp_dir = getTmpDir(self.sat_conf, self.cat_dir_str, profile.encode("utf-8")) + available = [ + path + for path in glob.glob(os.path.join(tmp_dir, cat_dir_str + "_*")) + if not path.endswith(METADATA_SUFF) + ] if not available: - self.disp(u"Could not find any content draft in {path}".format(path=tmp_dir), error=True) + self.disp( + u"Could not find any content draft in {path}".format(path=tmp_dir), + error=True, + ) self.host.quit(1) return max(available, key=lambda path: os.stat(path).st_mtime) @@ -330,7 +403,7 @@ def getTmpSuff(self): """return suffix used for content file""" - return u'xml' + return u"xml" def getItemPath(self): """retrieve item path (i.e. service and node) from item argument @@ -345,8 +418,8 @@ if self.args.current: # user wants to continue current draft content_file_path = self.getCurrentFile(self.profile) - self.disp(u'Continuing edition of current draft', 2) - content_file_obj = open(content_file_path, 'r+b') + self.disp(u"Continuing edition of current draft", 2) + content_file_obj = open(content_file_path, "r+b") # we seek at the end of file in case of an item already exist # this will write content of the existing item at the end of the draft. # This way no data should be lost. @@ -354,7 +427,7 @@ elif self.args.draft_path: # there is an existing draft that we use content_file_path = os.path.expanduser(self.args.item) - content_file_obj = open(content_file_path, 'r+b') + content_file_obj = open(content_file_path, "r+b") # we seek at the end for the same reason as above content_file_obj.seek(0, os.SEEK_END) else: @@ -362,7 +435,7 @@ content_file_obj, content_file_path = self.getTmpFile() if item or last_item: - self.disp(u'Editing requested published item', 2) + self.disp(u"Editing requested published item", 2) try: if self.use_metadata: content, metadata, item = self.getItemData(service, node, item) @@ -370,13 +443,18 @@ content, item = self.getItemData(service, node, item) except Exception as e: # FIXME: ugly but we have not good may to check errors in bridge - if u'item-not-found' in unicode(e): - # item doesn't exist, we create a new one with requested id + if u"item-not-found" in unicode(e): + # item doesn't exist, we create a new one with requested id metadata = None if last_item: - self.disp(_(u'no item found at all, we create a new one'), 2) + self.disp(_(u"no item found at all, we create a new one"), 2) else: - self.disp(_(u'item "{item_id}" not found, we create a new item with this id').format(item_id=item), 2) + self.disp( + _( + u'item "{item_id}" not found, we create a new item with this id' + ).format(item_id=item), + 2, + ) content_file_obj.seek(0) else: self.disp(u"Error while retrieving item: {}".format(e)) @@ -386,12 +464,14 @@ if content_file_obj.tell() != 0: # we already have a draft, # we copy item content after it and add an indicator - content_file_obj.write('\n*****\n') - content_file_obj.write(content.encode('utf-8')) + content_file_obj.write("\n*****\n") + content_file_obj.write(content.encode("utf-8")) content_file_obj.seek(0) - self.disp(_(u'item "{item_id}" found, we edit it').format(item_id=item), 2) + self.disp( + _(u'item "{item_id}" found, we edit it').format(item_id=item), 2 + ) else: - self.disp(u'Editing a new item', 2) + self.disp(u"Editing a new item", 2) if self.use_metadata: metadata = None @@ -402,7 +482,6 @@ class Table(object): - def __init__(self, host, data, headers=None, filters=None, use_buffer=False): """ @param data(iterable[list]): table data @@ -421,17 +500,17 @@ """ self.host = host self._buffer = [] if use_buffer else None - # headers are columns names/titles, can be None + # headers are columns names/titles, can be None self.headers = headers - # sizes fof columns without headers, + # sizes fof columns without headers, # headers may be larger self.sizes = [] - # rows countains one list per row with columns values + # rows countains one list per row with columns values self.rows = [] size = None if headers: - row_cls = namedtuple('RowData', headers) + row_cls = namedtuple("RowData", headers) else: row_cls = tuple @@ -462,21 +541,21 @@ if size is None: size = len(new_row) if headers is not None and len(headers) != size: - raise exceptions.DataError(u'headers size is not coherent with rows') + raise exceptions.DataError(u"headers size is not coherent with rows") else: if len(new_row) != size: - raise exceptions.DataError(u'rows size is not coherent') + raise exceptions.DataError(u"rows size is not coherent") self.rows.append(new_row) if not data and headers is not None: - # the table is empty, we print headers at their lenght + # the table is empty, we print headers at their lenght self.sizes = [len(h) for h in headers] @property def string(self): if self._buffer is None: - raise exceptions.InternalError(u'buffer must be used to get a string') - return u'\n'.join(self._buffer) + raise exceptions.InternalError(u"buffer must be used to get a string") + return u"\n".join(self._buffer) @staticmethod def readDictValues(data, keys, defaults=None): @@ -510,15 +589,17 @@ """ if keys is None and headers is not None: # FIXME: keys are not needed with OrderedDict, - raise exceptions.DataError(u'You must specify keys order to used headers') + raise exceptions.DataError(u"You must specify keys order to used headers") if keys is None: keys = data[0].keys() if headers is None: headers = keys filters = [filters.get(k) for k in keys] - return cls(host, (cls.readDictValues(d, keys, defaults) for d in data), headers, filters) + return cls( + host, (cls.readDictValues(d, keys, defaults) for d in data), headers, filters + ) - def _headers(self, head_sep, headers, sizes, alignment=u'left', style=None): + def _headers(self, head_sep, headers, sizes, alignment=u"left", style=None): """Render headers @param head_sep(unicode): sequence to use as separator @@ -532,14 +613,14 @@ style = [style] for idx, header in enumerate(headers): size = sizes[idx] - if alignment == u'left': + if alignment == u"left": rendered = header[:size].ljust(size) - elif alignment == u'center': + elif alignment == u"center": rendered = header[:size].center(size) - elif alignment == u'right': + elif alignment == u"right": rendered = header[:size].rjust(size) else: - raise exceptions.InternalError(u'bad alignment argument') + raise exceptions.InternalError(u"bad alignment argument") if style: args = style + [rendered] rendered = A.color(*args) @@ -553,30 +634,31 @@ else: self.host.disp(data) - def display(self, - head_alignment = u'left', - columns_alignment = u'left', - head_style = None, - show_header=True, - show_borders=True, - hide_cols=None, - col_sep=u' │ ', - top_left=u'┌', - top=u'─', - top_sep=u'─┬─', - top_right=u'┐', - left=u'│', - right=None, - head_sep=None, - head_line=u'┄', - head_line_left=u'├', - head_line_sep=u'┄┼┄', - head_line_right=u'┤', - bottom_left=u'└', - bottom=None, - bottom_sep=u'─┴─', - bottom_right=u'┘', - ): + def display( + self, + head_alignment=u"left", + columns_alignment=u"left", + head_style=None, + show_header=True, + show_borders=True, + hide_cols=None, + col_sep=u" │ ", + top_left=u"┌", + top=u"─", + top_sep=u"─┬─", + top_right=u"┐", + left=u"│", + right=None, + head_sep=None, + head_line=u"┄", + head_line_left=u"├", + head_line_sep=u"┄┼┄", + head_line_right=u"┤", + bottom_left=u"└", + bottom=None, + bottom_sep=u"─┴─", + bottom_right=u"┘", + ): """Print the table @param show_header(bool): True if header need no be shown @@ -618,14 +700,12 @@ if bottom_sep is None: bottom_sep = col_sep_size * bottom if not show_borders: - left = right = head_line_left = head_line_right = u'' + left = right = head_line_left = head_line_right = u"" # top border if show_borders: self._disp( - top_left - + top_sep.join([top*size for size in sizes]) - + top_right - ) + top_left + top_sep.join([top * size for size in sizes]) + top_right + ) # headers if show_header: @@ -633,42 +713,46 @@ left + self._headers(head_sep, headers, sizes, head_alignment, head_style) + right - ) + ) # header line self._disp( head_line_left - + head_line_sep.join([head_line*size for size in sizes]) + + head_line_sep.join([head_line * size for size in sizes]) + head_line_right - ) + ) # content - if columns_alignment == u'left': + if columns_alignment == u"left": alignment = lambda idx, s: ansi_ljust(s, sizes[idx]) - elif columns_alignment == u'center': + elif columns_alignment == u"center": alignment = lambda idx, s: ansi_center(s, sizes[idx]) - elif columns_alignment == u'right': + elif columns_alignment == u"right": alignment = lambda idx, s: ansi_rjust(s, sizes[idx]) else: - raise exceptions.InternalError(u'bad columns alignment argument') + raise exceptions.InternalError(u"bad columns alignment argument") for row in self.rows: if hide_cols: - row = [v for idx,v in enumerate(row) if idx not in ignore_idx] - self._disp(left + col_sep.join([alignment(idx,c) for idx,c in enumerate(row)]) + right) + row = [v for idx, v in enumerate(row) if idx not in ignore_idx] + self._disp( + left + + col_sep.join([alignment(idx, c) for idx, c in enumerate(row)]) + + right + ) if show_borders: # bottom border self._disp( bottom_left - + bottom_sep.join([bottom*size for size in sizes]) + + bottom_sep.join([bottom * size for size in sizes]) + bottom_right - ) - # we return self so string can be used after display (table.display().string) + ) + # we return self so string can be used after display (table.display().string) return self def display_blank(self, **kwargs): """Display table without visible borders""" - kwargs_ = {'col_sep':u' ', 'head_line_sep':u' ', 'show_borders':False} + kwargs_ = {"col_sep": u" ", "head_line_sep": u" ", "show_borders": False} kwargs_.update(kwargs) return self.display(**kwargs_) @@ -696,12 +780,16 @@ self.key = key self.callback = callback self.meta_map = meta_map - self.host.bridge.URIFind(path, - [key], - callback=self.URIFindCb, - errback=partial(command.errback, - msg=_(u"can't find " + key + u" URI: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + self.host.bridge.URIFind( + path, + [key], + callback=self.URIFindCb, + errback=partial( + command.errback, + msg=_(u"can't find " + key + u" URI: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) else: callback() @@ -723,29 +811,34 @@ try: values = getattr(self.args, key) except AttributeError: - raise exceptions.InternalError(u'there is no "{key}" arguments'.format( - key=key)) + raise exceptions.InternalError( + u'there is no "{key}" arguments'.format(key=key) + ) else: if values is None: values = [] values.extend(json.loads(new_values_json)) setattr(self.args, dest, values) - def URIFindCb(self, uris_data): try: uri_data = uris_data[self.key] except KeyError: - self.host.disp(_(u"No {key} URI specified for this project, please specify service and node").format(key=self.key), error=True) + self.host.disp( + _( + u"No {key} URI specified for this project, please specify service and node" + ).format(key=self.key), + error=True, + ) self.host.quit(C.EXIT_NOT_FOUND) else: - uri = uri_data[u'uri'] + uri = uri_data[u"uri"] - self.setMetadataList(uri_data, u'labels') + self.setMetadataList(uri_data, u"labels") parsed_uri = xmpp_uri.parseXMPPUri(uri) try: - self.args.service = parsed_uri[u'path'] - self.args.node = parsed_uri[u'node'] + self.args.service = parsed_uri[u"path"] + self.args.node = parsed_uri[u"node"] except KeyError: self.host.disp(_(u"Invalid URI found: {uri}").format(uri=uri), error=True) self.host.quit(C.EXIT_DATA_ERROR)
--- a/sat_frontends/jp/constants.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/constants.py Wed Jun 27 20:14:46 2018 +0200 @@ -26,26 +26,36 @@ APP_NAME = u"jp" PLUGIN_CMD = u"commands" PLUGIN_OUTPUT = u"outputs" - OUTPUT_TEXT = u'text' # blob of unicode text - OUTPUT_DICT = u'dict' # simple key/value dictionary - OUTPUT_LIST = u'list' - OUTPUT_LIST_DICT = u'list_dict' # list of dictionaries - OUTPUT_DICT_DICT = u'dict_dict' # dict of nested dictionaries - OUTPUT_COMPLEX = u'complex' # complex data (e.g. multi-level dictionary) - OUTPUT_XML = u'xml' # XML node (as unicode string) - OUTPUT_LIST_XML = u'list_xml' # list of XML nodes (as unicode strings) - OUTPUT_XMLUI = u'xmlui' # XMLUI as unicode string - OUTPUT_LIST_XMLUI = u'list_xmlui' # list of XMLUI (as unicode strings) - OUTPUT_TYPES = (OUTPUT_TEXT, OUTPUT_DICT, OUTPUT_LIST, OUTPUT_LIST_DICT, OUTPUT_DICT_DICT, - OUTPUT_COMPLEX, OUTPUT_XML, OUTPUT_LIST_XML, OUTPUT_XMLUI, OUTPUT_LIST_XMLUI) + OUTPUT_TEXT = u"text" # blob of unicode text + OUTPUT_DICT = u"dict" # simple key/value dictionary + OUTPUT_LIST = u"list" + OUTPUT_LIST_DICT = u"list_dict" # list of dictionaries + OUTPUT_DICT_DICT = u"dict_dict" # dict of nested dictionaries + OUTPUT_COMPLEX = u"complex" # complex data (e.g. multi-level dictionary) + OUTPUT_XML = u"xml" # XML node (as unicode string) + OUTPUT_LIST_XML = u"list_xml" # list of XML nodes (as unicode strings) + OUTPUT_XMLUI = u"xmlui" # XMLUI as unicode string + OUTPUT_LIST_XMLUI = u"list_xmlui" # list of XMLUI (as unicode strings) + OUTPUT_TYPES = ( + OUTPUT_TEXT, + OUTPUT_DICT, + OUTPUT_LIST, + OUTPUT_LIST_DICT, + OUTPUT_DICT_DICT, + OUTPUT_COMPLEX, + OUTPUT_XML, + OUTPUT_LIST_XML, + OUTPUT_XMLUI, + OUTPUT_LIST_XMLUI, + ) # Pubsub options flags - SERVICE = u'service' # service required - NODE = u'node' # node required - ITEM = u'item' # item required - SINGLE_ITEM = u'single_item' # only one item is allowed - MULTI_ITEMS = u'multi_items' # multiple items are allowed - NO_MAX = u'no_max' # don't add --max option for multi items + SERVICE = u"service" # service required + NODE = u"node" # node required + ITEM = u"item" # item required + SINGLE_ITEM = u"single_item" # only one item is allowed + MULTI_ITEMS = u"multi_items" # multiple items are allowed + NO_MAX = u"no_max" # don't add --max option for multi items # ANSI A_HEADER = A.BOLD + A.FG_YELLOW @@ -54,7 +64,7 @@ A_LEVEL_COLORS = (A_HEADER, A.BOLD + A.FG_BLUE, A.FG_MAGENTA, A.FG_CYAN) A_SUCCESS = A.BOLD + A.FG_GREEN A_FAILURE = A.BOLD + A.FG_RED - # A_PROMPT_* is for shell + # A_PROMPT_* is for shell A_PROMPT_PATH = A.BOLD + A.FG_CYAN A_PROMPT_SUF = A.BOLD # Files @@ -68,8 +78,10 @@ EXIT_BRIDGE_ERROR = 3 # can't connect to bridge EXIT_BRIDGE_ERRBACK = 4 # something went wrong when calling a bridge method EXIT_NOT_FOUND = 16 # an item required by a command was not found - EXIT_DATA_ERROR = 17 # data needed for a command is invalid + EXIT_DATA_ERROR = 17 # data needed for a command is invalid EXIT_USER_CANCELLED = 20 # user cancelled action - EXIT_FILE_NOT_EXE = 126 # a file to be executed was found, but it was not an executable utility (cf. man 1 exit) + EXIT_FILE_NOT_EXE = ( + 126 + ) # a file to be executed was found, but it was not an executable utility (cf. man 1 exit) EXIT_CMD_NOT_FOUND = 127 # a utility to be executed was not found (cf. man 1 exit) EXIT_SIGNAL_INT = 128 # a command was interrupted by a signal (cf. man 1 exit)
--- a/sat_frontends/jp/output_std.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/output_std.py Wed Jun 27 20:14:46 2018 +0200 @@ -24,9 +24,9 @@ import json __outputs__ = ["Simple", "Json"] -SIMPLE = u'simple' -JSON = u'json' -JSON_RAW = u'json_raw' +SIMPLE = u"simple" +JSON = u"json" +JSON_RAW = u"json_raw" class Simple(object): @@ -45,27 +45,30 @@ self.host.disp(unicode(data)) def list(self, data): - self.host.disp(u'\n'.join(data)) + self.host.disp(u"\n".join(data)) def dict(self, data, indent=0, header_color=C.A_HEADER): options = self.host.parse_output_options() - self.host.check_output_options({u'no-header'}, options) - show_header = not u'no-header' in options + self.host.check_output_options({u"no-header"}, options) + show_header = not u"no-header" in options for k, v in data.iteritems(): if show_header: - header = A.color(header_color, k) + u': ' + header = A.color(header_color, k) + u": " else: - header = u'' + header = u"" - self.host.disp((u'{indent}{header}{value}'.format( - indent=indent*u' ', - header=header, - value=v))) + self.host.disp( + ( + u"{indent}{header}{value}".format( + indent=indent * u" ", header=header, value=v + ) + ) + ) def list_dict(self, data): for idx, datum in enumerate(data): if idx: - self.host.disp(u'\n') + self.host.disp(u"\n") self.dict(datum) def dict_dict(self, data):
--- a/sat_frontends/jp/output_template.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/output_template.py Wed Jun 27 20:14:46 2018 +0200 @@ -27,8 +27,8 @@ import os.path __outputs__ = ["Template"] -TEMPLATE = u'template' -OPTIONS = {u'template', u'browser', u'inline-css'} +TEMPLATE = u"template" +OPTIONS = {u"template", u"browser", u"inline-css"} class Template(object): @@ -49,28 +49,29 @@ data to a dict usable by the template. """ # media_dir is needed for the template - self.host.media_dir = self.host.bridge.getConfig('', 'media_dir') + self.host.media_dir = self.host.bridge.getConfig("", "media_dir") cmd = self.host.command try: template_path = cmd.TEMPLATE except AttributeError: - if not 'template' in cmd.args.output_opts: - self.host.disp(u'no default template set for this command, ' - u'you need to specify a template using --oo template=[path/to/template.html]', - error=True) + if not "template" in cmd.args.output_opts: + self.host.disp( + u"no default template set for this command, " + u"you need to specify a template using --oo template=[path/to/template.html]", + error=True, + ) self.host.quit(C.EXIT_BAD_ARG) options = self.host.parse_output_options() self.host.check_output_options(OPTIONS, options) self.renderer = template.Renderer(self.host) try: - template_path = options['template'] + template_path = options["template"] except KeyError: # template is not specified, we use default one pass if template_path is None: - self.host.disp(u"Can't parse template, please check its syntax", - error=True) + self.host.disp(u"Can't parse template, please check its syntax", error=True) self.host.quit(C.EXIT_BAD_ARG) try: @@ -80,22 +81,29 @@ else: kwargs = mapping_cb(data) - css_inline = u'inline-css' in options + css_inline = u"inline-css" in options rendered = self.renderer.render(template_path, css_inline=css_inline, **kwargs) - if 'browser' in options: + if "browser" in options: template_name = os.path.basename(template_path) tmp_dir = tempfile.mkdtemp() - self.host.disp(_(u"Browser opening requested.\nTemporary files are put in the following directory, " - u"you'll have to delete it yourself once finished viewing: {}").format(tmp_dir)) + self.host.disp( + _( + u"Browser opening requested.\nTemporary files are put in the following directory, " + u"you'll have to delete it yourself once finished viewing: {}" + ).format(tmp_dir) + ) tmp_file = os.path.join(tmp_dir, template_name) - with open(tmp_file, 'w') as f: - f.write(rendered.encode('utf-8')) + with open(tmp_file, "w") as f: + f.write(rendered.encode("utf-8")) theme, theme_root_path = self.renderer.getThemeAndRoot(template_path) static_dir = os.path.join(theme_root_path, C.TEMPLATE_STATIC_DIR) if os.path.exists(static_dir): import shutil - shutil.copytree(static_dir, os.path.join(tmp_dir, theme, C.TEMPLATE_STATIC_DIR)) + + shutil.copytree( + static_dir, os.path.join(tmp_dir, theme, C.TEMPLATE_STATIC_DIR) + ) webbrowser.open(tmp_file) else: self.host.disp(rendered)
--- a/sat_frontends/jp/output_xml.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/output_xml.py Wed Jun 27 20:14:46 2018 +0200 @@ -23,8 +23,10 @@ from sat.core.i18n import _ from lxml import etree from sat.core.log import getLogger + log = getLogger(__name__) import sys + try: import pygments from pygments.lexers.html import XmlLexer @@ -34,8 +36,8 @@ __outputs__ = ["XML"] -RAW = u'xml_raw' -PRETTY = u'xml_pretty' +RAW = u"xml_raw" +PRETTY = u"xml_pretty" class XML(object): @@ -50,18 +52,23 @@ def colorize(self, xml): if pygments is None: - self.host.disp(_(u'Pygments is not available, syntax highlighting is not possible. Please install if from http://pygments.org or with pip install pygments'), error=True) + self.host.disp( + _( + u"Pygments is not available, syntax highlighting is not possible. Please install if from http://pygments.org or with pip install pygments" + ), + error=True, + ) return xml if not sys.stdout.isatty(): return xml - lexer = XmlLexer(encoding='utf-8') - formatter = TerminalFormatter(bg=u'dark') + lexer = XmlLexer(encoding="utf-8") + formatter = TerminalFormatter(bg=u"dark") return pygments.highlight(xml, lexer, formatter) def format(self, data, pretty=True): parser = etree.XMLParser(remove_blank_text=True) tree = etree.fromstring(data, parser) - xml = etree.tostring(tree, encoding='unicode', pretty_print=pretty) + xml = etree.tostring(tree, encoding="unicode", pretty_print=pretty) return self.colorize(xml) def format_no_pretty(self, data): @@ -70,13 +77,13 @@ def pretty(self, data): self.host.disp(self.format(data)) - def pretty_list(self, data, separator=u'\n'): + def pretty_list(self, data, separator=u"\n"): list_pretty = map(self.format, data) self.host.disp(separator.join(list_pretty)) def raw(self, data): self.host.disp(self.format_no_pretty(data)) - def list_raw(self, data, separator=u'\n'): + def list_raw(self, data, separator=u"\n"): list_no_pretty = map(self.format_no_pretty, data) self.host.disp(separator.join(list_no_pretty))
--- a/sat_frontends/jp/output_xmlui.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/output_xmlui.py Wed Jun 27 20:14:46 2018 +0200 @@ -22,6 +22,7 @@ from sat_frontends.jp.constants import Const as C from sat_frontends.jp import xmlui_manager from sat.core.log import getLogger + log = getLogger(__name__) @@ -34,12 +35,14 @@ def __init__(self, host): self.host = host host.register_output(C.OUTPUT_XMLUI, u"simple", self.xmlui, default=True) - host.register_output(C.OUTPUT_LIST_XMLUI, u"simple", self.xmlui_list, default=True) + host.register_output( + C.OUTPUT_LIST_XMLUI, u"simple", self.xmlui_list, default=True + ) def xmlui(self, data): xmlui = xmlui_manager.create(self.host, data) xmlui.show(values_only=True) - self.host.disp(u'') + self.host.disp(u"") def xmlui_list(self, data): for d in data:
--- a/sat_frontends/jp/xmlui_manager.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/jp/xmlui_manager.py Wed Jun 27 20:14:46 2018 +0200 @@ -18,6 +18,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.core.log import getLogger + log = getLogger(__name__) from sat_frontends.tools import xmlui as xmlui_manager from sat_frontends.jp.constants import Const as C @@ -27,14 +28,15 @@ # workflow constants -SUBMIT = 'SUBMIT' # submit form - +SUBMIT = "SUBMIT" # submit form ## Widgets ## + class Base(object): """Base for Widget and Container""" + type = None _root = None @@ -58,7 +60,7 @@ class Widget(Base): - category = u'widget' + category = u"widget" enabled = True @property @@ -85,16 +87,19 @@ if value is None: value = self.name if self.host.verbosity: - to_disp = [A.FG_MAGENTA, - u' ' if elems else u'', - u'({})'.format(value), A.RESET] + to_disp = [ + A.FG_MAGENTA, + u" " if elems else u"", + u"({})".format(value), + A.RESET, + ] if elems is None: self.host.disp(A.color(*to_disp)) else: elems.extend(to_disp) + class ValueWidget(Widget): - def __init__(self, xmlui_parent, value): super(ValueWidget, self).__init__(xmlui_parent) self.value = value @@ -105,7 +110,6 @@ class InputWidget(ValueWidget): - def __init__(self, xmlui_parent, value, read_only=False): super(InputWidget, self).__init__(xmlui_parent, value) self.read_only = read_only @@ -115,7 +119,6 @@ class OptionsWidget(Widget): - def __init__(self, xmlui_parent, options, selected, style): super(OptionsWidget, self).__init__(xmlui_parent) self.options = options @@ -157,21 +160,20 @@ def items(self): """return suitable items, according to style""" no_select = self.no_select - for value,label in self.options: + for value, label in self.options: if no_select or value in self.selected: - yield value,label + yield value, label @property def inline(self): - return u'inline' in self.style + return u"inline" in self.style @property def no_select(self): - return u'noselect' in self.style + return u"noselect" in self.style class EmptyWidget(xmlui_manager.EmptyWidget, Widget): - def __init__(self, _xmlui_parent): Widget.__init__(self) @@ -193,7 +195,7 @@ except AttributeError: return None - def show(self, no_lf=False, ansi=u''): + def show(self, no_lf=False, ansi=u""): """show label @param no_lf(bool): same as for [JP.disp] @@ -212,18 +214,17 @@ elems = [] self.verboseName(elems) if self.value: - elems.append(_(u'(enter: {default})').format(default=self.value)) - elems.extend([C.A_HEADER, u'> ']) + elems.append(_(u"(enter: {default})").format(default=self.value)) + elems.extend([C.A_HEADER, u"> "]) value = raw_input(A.color(*elems)) if value: - # TODO: empty value should be possible + # TODO: empty value should be possible # an escape key should be used for default instead of enter with empty value self.value = value - class JidInputWidget(xmlui_manager.JidInputWidget, StringWidget): - type = u'jid_input' + type = u"jid_input" class TextBoxWidget(xmlui_manager.TextWidget, StringWidget): @@ -231,7 +232,7 @@ class ListWidget(xmlui_manager.ListWidget, OptionsWidget): - type = u'list' + type = u"list" # TODO: handle flags, notably multi def show(self): @@ -248,7 +249,7 @@ for idx, (value, label) in enumerate(self.options): elems = [] if not self.root.read_only: - elems.extend([C.A_SUBHEADER, unicode(idx), A.RESET, u': ']) + elems.extend([C.A_SUBHEADER, unicode(idx), A.RESET, u": "]) elems.append(label) self.verboseName(elems, value) self.disp(A.color(*elems)) @@ -261,45 +262,49 @@ self.value = self.options[0][0] return - # we ask use to choose an option + # we ask use to choose an option choice = None - limit_max = len(self.options)-1 - while choice is None or choice<0 or choice>limit_max: - choice = raw_input(A.color(C.A_HEADER, _(u'your choice (0-{max}): ').format(max=limit_max))) + limit_max = len(self.options) - 1 + while choice is None or choice < 0 or choice > limit_max: + choice = raw_input( + A.color(C.A_HEADER, _(u"your choice (0-{max}): ").format(max=limit_max)) + ) try: choice = int(choice) except ValueError: choice = None self.value = self.options[choice][0] - self.disp('') + self.disp("") class BoolWidget(xmlui_manager.BoolWidget, InputWidget): - type = u'bool' + type = u"bool" def show(self): - disp_true = A.color(A.FG_GREEN, u'TRUE') - disp_false = A.color(A.FG_RED,u'FALSE') + disp_true = A.color(A.FG_GREEN, u"TRUE") + disp_false = A.color(A.FG_RED, u"FALSE") if self.read_only: self.disp(disp_true if self.value else disp_false) else: - self.disp(A.color(C.A_HEADER, u'0: ', disp_false)) - self.disp(A.color(C.A_HEADER, u'1: ', disp_true)) + self.disp(A.color(C.A_HEADER, u"0: ", disp_false)) + self.disp(A.color(C.A_HEADER, u"1: ", disp_true)) choice = None - while choice not in ('0', '1'): - elems = [C.A_HEADER, _(u'your choice (0,1): ')] + while choice not in ("0", "1"): + elems = [C.A_HEADER, _(u"your choice (0,1): ")] self.verboseName(elems) choice = raw_input(A.color(*elems)) self.value = bool(int(choice)) - self.disp('') + self.disp("") def _xmluiGetValue(self): return C.boolConst(self.value) + ## Containers ## + class Container(Base): - category = u'container' + category = u"container" def __init__(self, xmlui_parent): super(Container, self).__init__(xmlui_parent) @@ -320,39 +325,43 @@ class VerticalContainer(xmlui_manager.VerticalContainer, Container): - type = u'vertical' + type = u"vertical" class PairsContainer(xmlui_manager.PairsContainer, Container): - type = u'pairs' + type = u"pairs" class LabelContainer(xmlui_manager.PairsContainer, Container): - type = u'label' + type = u"label" def show(self): for child in self.children: no_lf = False # we check linked widget type # to see if we want the label on the same line or not - if child.type == u'label': + if child.type == u"label": for_name = child.for_name if for_name is not None: for_widget = self.root.widgets[for_name] wid_type = for_widget.type - if self.root.values_only or wid_type in ('text', 'string', 'jid_input'): + if self.root.values_only or wid_type in ( + "text", + "string", + "jid_input", + ): no_lf = True - elif wid_type == 'bool' and for_widget.read_only: + elif wid_type == "bool" and for_widget.read_only: no_lf = True child.show(no_lf=no_lf, ansi=A.FG_CYAN) else: child.show() + ## Dialogs ## class Dialog(object): - def __init__(self, xmlui_parent): self.xmlui_parent = xmlui_parent self.host = self.xmlui_parent.host @@ -369,7 +378,6 @@ class NoteDialog(xmlui_manager.NoteDialog, Dialog): - def show(self): # TODO: handle title and level self.disp(self.message) @@ -379,11 +387,11 @@ xmlui_manager.NoteDialog.__init__(self, _xmlui_parent) self.title, self.message, self.level = title, message, level + ## Factory ## class WidgetFactory(object): - def __getattr__(self, attr): if attr.startswith("create"): cls = globals()[attr[6:]] @@ -398,15 +406,27 @@ workflow = None _submit_cb = None - def __init__(self, host, parsed_dom, title=None, flags=None, callback=None, ignore=None, whitelist=None, profile=None): - xmlui_manager.XMLUIPanel.__init__(self, - host, - parsed_dom, - title = title, - flags = flags, - ignore = ignore, - whitelist = whitelist, - profile=host.profile) + def __init__( + self, + host, + parsed_dom, + title=None, + flags=None, + callback=None, + ignore=None, + whitelist=None, + profile=None, + ): + xmlui_manager.XMLUIPanel.__init__( + self, + host, + parsed_dom, + title=title, + flags=flags, + ignore=ignore, + whitelist=whitelist, + profile=host.profile, + ) self.submitted = False @property @@ -455,7 +475,7 @@ elif isinstance(cmd, list): name, value = cmd widget = self.widgets[name] - if widget.type == 'bool': + if widget.type == "bool": value = C.bool(value) widget.value = value self.show() @@ -465,7 +485,7 @@ self.onFormSubmitted() def onFormSubmitted(self, ignore=None): - # self.submitted is a Q&D workaround to avoid + # self.submitted is a Q&D workaround to avoid # double submit when a workflow is set if self.submitted: return @@ -478,8 +498,8 @@ def _launchActionCb(self, data): XMLUIPanel._actions -= 1 assert XMLUIPanel._actions >= 0 - if u'xmlui' in data: - xmlui_raw = data['xmlui'] + if u"xmlui" in data: + xmlui_raw = data["xmlui"] xmlui = xmlui_manager.create(self.host, xmlui_raw) xmlui.show() if xmlui.submit_id: @@ -498,13 +518,16 @@ data, self.profile, callback=self._launchActionCb, - errback=partial(self.command.errback, - msg=_(u"can't launch XMLUI action: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK)) + errback=partial( + self.command.errback, + msg=_(u"can't launch XMLUI action: {}"), + exit_code=C.EXIT_BRIDGE_ERRBACK, + ), + ) class XMLUIDialog(xmlui_manager.XMLUIDialog): - type = 'dialog' + type = "dialog" dialog_factory = WidgetFactory() read_only = False
--- a/sat_frontends/primitivus/chat.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/primitivus/chat.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,6 +19,7 @@ from sat.core.i18n import _ from sat.core import log as logging + log = logging.getLogger(__name__) import urwid from urwid_satext import sat_widgets @@ -36,8 +37,8 @@ OCCUPANTS_FOOTER = _(u"{} occupants") + class MessageWidget(urwid.WidgetWrap): - def __init__(self, mess_data): """ @param mess_data(quick_chat.Message, None): message data @@ -49,7 +50,11 @@ @property def markup(self): - return self._generateInfoMarkup() if self.mess_data.type == C.MESS_TYPE_INFO else self._generateMarkup() + return ( + self._generateInfoMarkup() + if self.mess_data.type == C.MESS_TYPE_INFO + else self._generateMarkup() + ) @property def info_type(self): @@ -66,7 +71,7 @@ @message.setter def message(self, value): - self.mess_data.message = {'':value} + self.mess_data.message = {"": value} self.redraw() @property @@ -98,7 +103,7 @@ return canvas def _generateInfoMarkup(self): - return ('info_msg', self.message) + return ("info_msg", self.message) def _generateMarkup(self): """Generate text markup according to message data and Widget options""" @@ -108,25 +113,29 @@ # message status if d.status is None: - markup.append(u' ') + markup.append(u" ") elif d.status == "delivered": - markup.append(('msg_status_received', u'✔')) + markup.append(("msg_status_received", u"✔")) else: log.warning(u"Unknown status: {}".format(d.status)) # timestamp if self.parent.show_timestamp: - attr = 'msg_mention' if mention else 'date' + attr = "msg_mention" if mention else "date" markup.append((attr, u"[{}]".format(d.time_text))) else: if mention: - markup.append(('msg_mention', '[*]')) + markup.append(("msg_mention", "[*]")) # nickname if self.parent.show_short_nick: - markup.append(('my_nick' if d.own_mess else 'other_nick', "**" if d.own_mess else "*")) + markup.append( + ("my_nick" if d.own_mess else "other_nick", "**" if d.own_mess else "*") + ) else: - markup.append(('my_nick' if d.own_mess else 'other_nick', u"[{}] ".format(d.nick or ''))) + markup.append( + ("my_nick" if d.own_mess else "other_nick", u"[{}] ".format(d.nick or "")) + ) msg = self.message # needed to generate self.selected_lang @@ -146,18 +155,20 @@ """ self.redraw() + @total_ordering class OccupantWidget(urwid.WidgetWrap): - def __init__(self, occupant_data): self.occupant_data = occupant_data occupant_data.widgets.add(self) markup = self._generateMarkup() text = sat_widgets.ClickableText(markup) - urwid.connect_signal(text, - 'click', + urwid.connect_signal( + text, + "click", self.occupant_data.parent._occupantsClicked, - user_args=[self.occupant_data]) + user_args=[self.occupant_data], + ) super(OccupantWidget, self).__init__(text) def __eq__(self, other): @@ -206,13 +217,12 @@ # should be more intuitive and themable o = self.occupant_data markup = [] - markup.append(('info_msg', u'{}{} '.format( - o.role[0].upper(), - o.affiliation[0].upper(), - ))) + markup.append( + ("info_msg", u"{}{} ".format(o.role[0].upper(), o.affiliation[0].upper())) + ) markup.append(o.nick) if o.state is not None: - markup.append(u' {}'.format(C.CHAT_STATE_ICON[o.state])) + markup.append(u" {}".format(C.CHAT_STATE_ICON[o.state])) return markup # events @@ -221,15 +231,16 @@ class OccupantsWidget(urwid.WidgetWrap): - def __init__(self, parent): self.parent = parent self.occupants_walker = urwid.SimpleListWalker([]) - self.occupants_footer = urwid.Text('', align='center') + self.occupants_footer = urwid.Text("", align="center") self.updateFooter() - occupants_widget = urwid.Frame(urwid.ListBox(self.occupants_walker), footer=self.occupants_footer) + occupants_widget = urwid.Frame( + urwid.ListBox(self.occupants_walker), footer=self.occupants_footer + ) super(OccupantsWidget, self).__init__(occupants_widget) - occupants_list = sorted(self.parent.occupants.keys(), key=lambda o:o.lower()) + occupants_list = sorted(self.parent.occupants.keys(), key=lambda o: o.lower()) for occupant in occupants_list: occupant_data = self.parent.occupants[occupant] self.occupants_walker.append(OccupantWidget(occupant_data)) @@ -239,12 +250,16 @@ txt = OCCUPANTS_FOOTER.format(len(self.parent.occupants)) self.occupants_footer.set_text(txt) - def getNicks(self, start=u''): + def getNicks(self, start=u""): """Return nicks of all occupants @param start(unicode): only return nicknames which start with this text """ - return [w.nick for w in self.occupants_walker if isinstance(w, OccupantWidget) and w.nick.startswith(start)] + return [ + w.nick + for w in self.occupants_walker + if isinstance(w, OccupantWidget) and w.nick.startswith(start) + ] def addUser(self, occupant_data): """add a user to the list""" @@ -261,14 +276,24 @@ class Chat(PrimitivusWidget, quick_chat.QuickChat): - - def __init__(self, host, target, type_=C.CHAT_ONE2ONE, nick=None, occupants=None, subject=None, profiles=None): - quick_chat.QuickChat.__init__(self, host, target, type_, nick, occupants, subject, profiles=profiles) + def __init__( + self, + host, + target, + type_=C.CHAT_ONE2ONE, + nick=None, + occupants=None, + subject=None, + profiles=None, + ): + quick_chat.QuickChat.__init__( + self, host, target, type_, nick, occupants, subject, profiles=profiles + ) self.filters = [] # list of filter callbacks to apply self.mess_walker = urwid.SimpleListWalker([]) self.mess_widgets = urwid.ListBox(self.mess_walker) self.chat_widget = urwid.Frame(self.mess_widgets) - self.chat_colums = urwid.Columns([('weight', 8, self.chat_widget)]) + self.chat_colums = urwid.Columns([("weight", 8, self.chat_widget)]) self.pile = urwid.Pile([self.chat_colums]) PrimitivusWidget.__init__(self, self.pile, self.target) @@ -276,9 +301,11 @@ if type_ == C.CHAT_GROUP: if len(self.chat_colums.contents) == 1: self.occupants_widget = OccupantsWidget(self) - self.occupants_panel = sat_widgets.VerticalSeparator(self.occupants_widget) + self.occupants_panel = sat_widgets.VerticalSeparator( + self.occupants_widget + ) self._appendOccupantsPanel() - self.host.addListener('presence', self.presenceListener, [profiles]) + self.host.addListener("presence", self.presenceListener, [profiles]) # focus marker is a separator indicated last visible message before focus was lost self.focus_marker = None # link to current marker @@ -289,30 +316,32 @@ self.postInit() def keypress(self, size, key): - if key == a_key['OCCUPANTS_HIDE']: # user wants to (un)hide the occupants panel + if key == a_key["OCCUPANTS_HIDE"]: # user wants to (un)hide the occupants panel if self.type == C.CHAT_GROUP: widgets = [widget for (widget, options) in self.chat_colums.contents] if self.occupants_panel in widgets: self._removeOccupantsPanel() else: self._appendOccupantsPanel() - elif key == a_key['TIMESTAMP_HIDE']: # user wants to (un)hide timestamp + elif key == a_key["TIMESTAMP_HIDE"]: # user wants to (un)hide timestamp self.show_timestamp = not self.show_timestamp self.redraw() - elif key == a_key['SHORT_NICKNAME']: # user wants to (not) use short nick + elif key == a_key["SHORT_NICKNAME"]: # user wants to (not) use short nick self.show_short_nick = not self.show_short_nick self.redraw() - elif key == a_key['SUBJECT_SWITCH']: # user wants to (un)hide group's subject or change its apperance + elif ( + key == a_key["SUBJECT_SWITCH"] + ): # user wants to (un)hide group's subject or change its apperance if self.subject: self.show_title = (self.show_title + 1) % 3 if self.show_title == 0: - self.setSubject(self.subject, 'clip') + self.setSubject(self.subject, "clip") elif self.show_title == 1: - self.setSubject(self.subject, 'space') + self.setSubject(self.subject, "space") elif self.show_title == 2: self.chat_widget.header = None self._invalidate() - elif key == a_key['GOTO_BOTTOM']: # user wants to focus last message + elif key == a_key["GOTO_BOTTOM"]: # user wants to focus last message self.mess_widgets.focus_position = len(self.mess_walker) - 1 return super(Chat, self).keypress(size, key) @@ -326,25 +355,25 @@ return text space = text.rfind(" ") - start = text[space + 1:] + start = text[space + 1 :] words = self.occupants_widget.getNicks(start) if not words: return text try: - word_idx = words.index(completion_data['last_word']) + 1 + word_idx = words.index(completion_data["last_word"]) + 1 except (KeyError, ValueError): word_idx = 0 else: if word_idx == len(words): word_idx = 0 - word = completion_data['last_word'] = words[word_idx] - return u"{}{}{}".format(text[:space + 1], word, ': ' if space < 0 else '') + word = completion_data["last_word"] = words[word_idx] + return u"{}{}{}".format(text[: space + 1], word, ": " if space < 0 else "") def getMenu(self): """Return Menu bar""" menu = sat_widgets.Menu(self.host.loop) if self.type == C.CHAT_GROUP: - self.host.addMenus(menu, C.MENU_ROOM, {'room_jid': self.target.bare}) + self.host.addMenus(menu, C.MENU_ROOM, {"room_jid": self.target.bare}) game = _("Game") menu.addMenu(game, "Tarot", self.onTarotRequest) elif self.type == C.CHAT_ONE2ONE: @@ -354,7 +383,7 @@ full_jid = contact_list.getFullJid(self.target) else: full_jid = self.target - self.host.addMenus(menu, C.MENU_SINGLE, {'jid': full_jid}) + self.host.addMenus(menu, C.MENU_SINGLE, {"jid": full_jid}) return menu def setFilter(self, args): @@ -399,7 +428,7 @@ if message.type != C.MESS_TYPE_INFO: return False try: - info_type = message.extra['info_type'] + info_type = message.extra["info_type"] except KeyError: return False else: @@ -429,25 +458,33 @@ continue if wid.mess_data.type != C.MESS_TYPE_INFO: break - if wid.info_type in quick_chat.ROOM_USER_MOVED and wid.mess_data.nick == message.nick: + if ( + wid.info_type in quick_chat.ROOM_USER_MOVED + and wid.mess_data.nick == message.nick + ): try: count = wid.reentered_count except AttributeError: count = wid.reentered_count = 1 nick = wid.mess_data.nick if message.info_type == quick_chat.ROOM_USER_LEFT: - wid.message = _(u"<= {nick} has left the room ({count})").format(nick=nick, count=count) + wid.message = _(u"<= {nick} has left the room ({count})").format( + nick=nick, count=count + ) else: - wid.message = _(u"<=> {nick} re-entered the room ({count})") .format(nick=nick, count=count) - wid.reentered_count+=1 + wid.message = _( + u"<=> {nick} re-entered the room ({count})" + ).format(nick=nick, count=count) + wid.reentered_count += 1 return - if ((self.host.selected_widget != self or not self.host.x_notify.hasFocus()) - and self.focus_marker_set is not None): + if ( + self.host.selected_widget != self or not self.host.x_notify.hasFocus() + ) and self.focus_marker_set is not None: if not self.focus_marker_set and not self._locked and self.mess_walker: if self.focus_marker is not None: self.mess_walker.remove(self.focus_marker) - self.focus_marker = urwid.Divider('—') + self.focus_marker = urwid.Divider("—") self.mess_walker.append(self.focus_marker) self.focus_marker_set = True self._scrollDown() @@ -473,20 +510,24 @@ if wid.mess_data.mention: from_jid = wid.mess_data.from_jid - msg = _(u'You have been mentioned by {nick} in {room}'.format( - nick=wid.mess_data.nick, - room=self.target, - )) - self.host.notify(C.NOTIFY_MENTION, from_jid, msg, widget=self, profile=self.profile) + msg = _( + u"You have been mentioned by {nick} in {room}".format( + nick=wid.mess_data.nick, room=self.target + ) + ) + self.host.notify( + C.NOTIFY_MENTION, from_jid, msg, widget=self, profile=self.profile + ) elif self.type == C.CHAT_ONE2ONE: from_jid = wid.mess_data.from_jid - msg = _(u'{entity} is talking to you'.format( - entity=from_jid, - )) - self.host.notify(C.NOTIFY_MESSAGE, from_jid, msg, widget=self, profile=self.profile) + msg = _(u"{entity} is talking to you".format(entity=from_jid)) + self.host.notify( + C.NOTIFY_MESSAGE, from_jid, msg, widget=self, profile=self.profile + ) else: - self.host.notify(C.NOTIFY_MESSAGE, self.target, widget=self, profile=self.profile) - + self.host.notify( + C.NOTIFY_MESSAGE, self.target, widget=self, profile=self.profile + ) def addUser(self, nick): occupant = super(Chat, self).addUser(nick) @@ -505,11 +546,13 @@ self.getOrCreatePrivateWidget(occupant.jid) # now we select the new window - for contact_list in self.host.widgets.getWidgets(ContactList, profiles=(self.profile,)): + for contact_list in self.host.widgets.getWidgets( + ContactList, profiles=(self.profile,) + ): contact_list.setFocus(occupant.jid, True) def _appendOccupantsPanel(self): - self.chat_colums.contents.append((self.occupants_panel, ('weight', 2, False))) + self.chat_colums.contents.append((self.occupants_panel, ("weight", 2, False))) def _removeOccupantsPanel(self): for widget, options in self.chat_colums.contents: @@ -522,9 +565,9 @@ @param widget (Widget): the game panel """ - assert (len(self.pile.contents) == 1) - self.pile.contents.insert(0, (widget, ('weight', 1))) - self.pile.contents.insert(1, (urwid.Filler(urwid.Divider('-'), ('fixed', 1)))) + assert len(self.pile.contents) == 1 + self.pile.contents.insert(0, (widget, ("weight", 1))) + self.pile.contents.insert(1, (urwid.Filler(urwid.Divider("-"), ("fixed", 1)))) self.host.redraw() def removeGamePanel(self, widget): @@ -532,16 +575,19 @@ @param widget (Widget): the game panel """ - assert (len(self.pile.contents) == 3) + assert len(self.pile.contents) == 3 del self.pile.contents[0] self.host.redraw() - def setSubject(self, subject, wrap='space'): + def setSubject(self, subject, wrap="space"): """Set title for a group chat""" quick_chat.QuickChat.setSubject(self, subject) - self.subj_wid = urwid.Text(unicode(subject.replace('\n', '|') if wrap == 'clip' else subject), - align='left' if wrap == 'clip' else 'center', wrap=wrap) - self.chat_widget.header = urwid.AttrMap(self.subj_wid, 'title') + self.subj_wid = urwid.Text( + unicode(subject.replace("\n", "|") if wrap == "clip" else subject), + align="left" if wrap == "clip" else "center", + wrap=wrap, + ) + self.chat_widget.header = urwid.AttrMap(self.subj_wid, "title") self.host.redraw() ## Messages @@ -564,11 +610,19 @@ except AttributeError: pass - def updateHistory(self, size=C.HISTORY_LIMIT_DEFAULT, filters=None, profile='@NONE@'): + def updateHistory(self, size=C.HISTORY_LIMIT_DEFAULT, filters=None, profile="@NONE@"): del self.mess_walker[:] - if filters and 'search' in filters: - self.mess_walker.append(urwid.Text(_(u"Results for searching the globbing pattern: {}").format(filters['search']))) - self.mess_walker.append(urwid.Text(_(u"Type ':history <lines>' to reset the chat history"))) + if filters and "search" in filters: + self.mess_walker.append( + urwid.Text( + _(u"Results for searching the globbing pattern: {}").format( + filters["search"] + ) + ) + ) + self.mess_walker.append( + urwid.Text(_(u"Type ':history <lines>' to reset the chat history")) + ) super(Chat, self).updateHistory(size, filters, profile) def _onHistoryPrinted(self): @@ -577,7 +631,9 @@ super(Chat, self)._onHistoryPrinted() def onPrivateCreated(self, widget): - self.host.contact_lists[widget.profile].setSpecial(widget.target, C.CONTACT_SPECIAL_GROUP) + self.host.contact_lists[widget.profile].setSpecial( + widget.target, C.CONTACT_SPECIAL_GROUP + ) def onSelected(self): self.focus_marker_set = False @@ -598,17 +654,32 @@ self.host.redraw() if not self.host.x_notify.hasFocus(): if self.type == C.CHAT_ONE2ONE: - self.host.x_notify.sendNotification(_("Primitivus: %s is talking to you") % contact) + self.host.x_notify.sendNotification( + _("Primitivus: %s is talking to you") % contact + ) elif self.nick is not None and self.nick.lower() in msg.lower(): - self.host.x_notify.sendNotification(_("Primitivus: %(user)s mentioned you in room '%(room)s'") % {'user': contact, 'room': self.target}) + self.host.x_notify.sendNotification( + _("Primitivus: %(user)s mentioned you in room '%(room)s'") + % {"user": contact, "room": self.target} + ) # MENU EVENTS # def onTarotRequest(self, menu): # TODO: move this to plugin_misc_tarot with dynamic menu if len(self.occupants) != 4: - self.host.showPopUp(sat_widgets.Alert(_("Can't start game"), _("You need to be exactly 4 peoples in the room to start a Tarot game"), ok_cb=self.host.removePopUp)) + self.host.showPopUp( + sat_widgets.Alert( + _("Can't start game"), + _( + "You need to be exactly 4 peoples in the room to start a Tarot game" + ), + ok_cb=self.host.removePopUp, + ) + ) else: - self.host.bridge.tarotGameCreate(self.target, list(self.occupants), self.profile) + self.host.bridge.tarotGameCreate( + self.target, list(self.occupants), self.profile + ) # MISC EVENTS # @@ -616,7 +687,7 @@ # FIXME: to be checked after refactoring super(Chat, self).onDelete() if self.type == C.CHAT_GROUP: - self.host.removeListener('presence', self.presenceListener) + self.host.removeListener("presence", self.presenceListener) def onChatState(self, from_jid, state, profile): super(Chat, self).onChatState(from_jid, state, profile) @@ -630,12 +701,14 @@ def onSubjectDialog(self, new_subject=None): dialog = sat_widgets.InputDialog( - _(u'Change title'), - _(u'Enter the new title'), - default_txt=new_subject if new_subject is not None else self.subject) - dialog.setCallback('ok', self._onSubjectDialogCb, dialog) - dialog.setCallback('cancel', lambda dummy: self.host.removePopUp(dialog)) + _(u"Change title"), + _(u"Enter the new title"), + default_txt=new_subject if new_subject is not None else self.subject, + ) + dialog.setCallback("ok", self._onSubjectDialogCb, dialog) + dialog.setCallback("cancel", lambda dummy: self.host.removePopUp(dialog)) self.host.showPopUp(dialog) + quick_widgets.register(quick_chat.QuickChat, Chat) quick_widgets.register(quick_games.Tarot, game_tarot.TarotGame)
--- a/sat_frontends/primitivus/config.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/primitivus/config.py Wed Jun 27 20:14:46 2018 +0200 @@ -38,7 +38,7 @@ shortcuts = {} for name, value in options: if name.startswith(C.CONFIG_OPT_KEY_PREFIX.lower()): - action = name[len(C.CONFIG_OPT_KEY_PREFIX):].upper() + action = name[len(C.CONFIG_OPT_KEY_PREFIX) :].upper() shortcut = value if not action or not shortcut: raise ValueError("Bad option: {} = {}".format(name, value))
--- a/sat_frontends/primitivus/constants.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/primitivus/constants.py Wed Jun 27 20:14:46 2018 +0200 @@ -25,73 +25,76 @@ APP_NAME = "Primitivus" SECTION_NAME = APP_NAME.lower() PALETTE = [ - ('title', 'black', 'light gray', 'standout,underline'), - ('title_focus', 'white,bold', 'light gray', 'standout,underline'), - ('selected', 'default', 'dark red'), - ('selected_focus', 'default,bold', 'dark red'), - ('default', 'default', 'default'), - ('default_focus', 'default,bold', 'default'), - ('cl_notifs', 'yellow', 'default'), - ('cl_notifs_focus', 'yellow,bold', 'default'), - ('cl_mention', 'dark red', 'default'), - ('cl_mention_focus', 'dark red,bold', 'default'), - # Messages - ('date', 'light gray', 'default'), - ('my_nick', 'dark red,bold', 'default'), - ('other_nick', 'dark cyan,bold', 'default'), - ('info_msg', 'yellow', 'default', 'bold'), - ('msg_lang', 'dark cyan', 'default'), - ('msg_mention', 'dark red, bold', 'default'), - ('msg_status_received', 'light green, bold', 'default'), - - ('menubar', 'light gray,bold', 'dark red'), - ('menubar_focus', 'light gray,bold', 'dark green'), - ('selected_menu', 'light gray,bold', 'dark green'), - ('menuitem', 'light gray,bold', 'dark red'), - ('menuitem_focus', 'light gray,bold', 'dark green'), - ('notifs', 'black,bold', 'yellow'), - ('notifs_focus', 'dark red', 'yellow'), - ('card_neutral', 'dark gray', 'white', 'standout,underline'), - ('card_neutral_selected', 'dark gray', 'dark green', 'standout,underline'), - ('card_special', 'brown', 'white', 'standout,underline'), - ('card_special_selected', 'brown', 'dark green', 'standout,underline'), - ('card_red', 'dark red', 'white', 'standout,underline'), - ('card_red_selected', 'dark red', 'dark green', 'standout,underline'), - ('card_black', 'black', 'white', 'standout,underline'), - ('card_black_selected', 'black', 'dark green', 'standout,underline'), - ('directory', 'dark cyan, bold', 'default'), - ('directory_focus', 'dark cyan, bold', 'dark green'), - ('separator', 'brown', 'default'), - ('warning', 'light red', 'default'), - ('progress_normal', 'default', 'brown'), - ('progress_complete', 'default', 'dark green'), - ('show_disconnected', 'dark gray', 'default'), - ('show_normal', 'default', 'default'), - ('show_normal_focus', 'default, bold', 'default'), - ('show_chat', 'dark green', 'default'), - ('show_chat_focus', 'dark green, bold', 'default'), - ('show_away', 'brown', 'default'), - ('show_away_focus', 'brown, bold', 'default'), - ('show_dnd', 'dark red', 'default'), - ('show_dnd_focus', 'dark red, bold', 'default'), - ('show_xa', 'dark red', 'default'), - ('show_xa_focus', 'dark red, bold', 'default'), - ('resource', 'light blue', 'default'), - ('resource_main', 'dark blue', 'default'), - ('status', 'yellow', 'default'), - ('status_focus', 'yellow, bold', 'default'), - ('param_selected','default, bold', 'dark red'), - ('table_selected','default, bold', 'default'), - ] - PRESENCE = {"unavailable": (u'⨯', "show_disconnected"), - "": (u'✔', "show_normal"), - "chat": (u'✆', "show_chat"), - "away": (u'✈', "show_away"), - "dnd": (u'✖', "show_dnd"), - "xa": (u'☄', "show_xa") - } + ("title", "black", "light gray", "standout,underline"), + ("title_focus", "white,bold", "light gray", "standout,underline"), + ("selected", "default", "dark red"), + ("selected_focus", "default,bold", "dark red"), + ("default", "default", "default"), + ("default_focus", "default,bold", "default"), + ("cl_notifs", "yellow", "default"), + ("cl_notifs_focus", "yellow,bold", "default"), + ("cl_mention", "dark red", "default"), + ("cl_mention_focus", "dark red,bold", "default"), + # Messages + ("date", "light gray", "default"), + ("my_nick", "dark red,bold", "default"), + ("other_nick", "dark cyan,bold", "default"), + ("info_msg", "yellow", "default", "bold"), + ("msg_lang", "dark cyan", "default"), + ("msg_mention", "dark red, bold", "default"), + ("msg_status_received", "light green, bold", "default"), + ("menubar", "light gray,bold", "dark red"), + ("menubar_focus", "light gray,bold", "dark green"), + ("selected_menu", "light gray,bold", "dark green"), + ("menuitem", "light gray,bold", "dark red"), + ("menuitem_focus", "light gray,bold", "dark green"), + ("notifs", "black,bold", "yellow"), + ("notifs_focus", "dark red", "yellow"), + ("card_neutral", "dark gray", "white", "standout,underline"), + ("card_neutral_selected", "dark gray", "dark green", "standout,underline"), + ("card_special", "brown", "white", "standout,underline"), + ("card_special_selected", "brown", "dark green", "standout,underline"), + ("card_red", "dark red", "white", "standout,underline"), + ("card_red_selected", "dark red", "dark green", "standout,underline"), + ("card_black", "black", "white", "standout,underline"), + ("card_black_selected", "black", "dark green", "standout,underline"), + ("directory", "dark cyan, bold", "default"), + ("directory_focus", "dark cyan, bold", "dark green"), + ("separator", "brown", "default"), + ("warning", "light red", "default"), + ("progress_normal", "default", "brown"), + ("progress_complete", "default", "dark green"), + ("show_disconnected", "dark gray", "default"), + ("show_normal", "default", "default"), + ("show_normal_focus", "default, bold", "default"), + ("show_chat", "dark green", "default"), + ("show_chat_focus", "dark green, bold", "default"), + ("show_away", "brown", "default"), + ("show_away_focus", "brown, bold", "default"), + ("show_dnd", "dark red", "default"), + ("show_dnd_focus", "dark red, bold", "default"), + ("show_xa", "dark red", "default"), + ("show_xa_focus", "dark red, bold", "default"), + ("resource", "light blue", "default"), + ("resource_main", "dark blue", "default"), + ("status", "yellow", "default"), + ("status_focus", "yellow, bold", "default"), + ("param_selected", "default, bold", "dark red"), + ("table_selected", "default, bold", "default"), + ] + PRESENCE = { + "unavailable": (u"⨯", "show_disconnected"), + "": (u"✔", "show_normal"), + "chat": (u"✆", "show_chat"), + "away": (u"✈", "show_away"), + "dnd": (u"✖", "show_dnd"), + "xa": (u"☄", "show_xa"), + } LOG_OPT_SECTION = APP_NAME.lower() - LOG_OPT_OUTPUT = ('output', constants.Const.LOG_OPT_OUTPUT_SEP + constants.Const.LOG_OPT_OUTPUT_MEMORY) + LOG_OPT_OUTPUT = ( + "output", + constants.Const.LOG_OPT_OUTPUT_SEP + constants.Const.LOG_OPT_OUTPUT_MEMORY, + ) CONFIG_SECTION = APP_NAME.lower() CONFIG_OPT_KEY_PREFIX = "KEY_" @@ -99,8 +102,8 @@ MENU_ID_MAIN = "MAIN_MENU" MENU_ID_WIDGET = "WIDGET_MENU" - MODE_NORMAL = 'NORMAL' - MODE_INSERTION = 'INSERTION' - MODE_COMMAND = 'COMMAND' + MODE_NORMAL = "NORMAL" + MODE_INSERTION = "INSERTION" + MODE_COMMAND = "COMMAND" - GROUP_DATA_FOLDED = 'folded' + GROUP_DATA_FOLDED = "folded"
--- a/sat_frontends/primitivus/contact_list.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/primitivus/contact_list.py Wed Jun 27 20:14:46 2018 +0200 @@ -27,30 +27,33 @@ from sat_frontends.primitivus.widget import PrimitivusWidget from sat_frontends.tools import jid from sat.core import log as logging + log = logging.getLogger(__name__) from sat_frontends.quick_frontend import quick_widgets class ContactList(PrimitivusWidget, QuickContactList): - PROFILES_MULTIPLE=False - PROFILES_ALLOW_NONE=False - signals = ['click','change'] + PROFILES_MULTIPLE = False + PROFILES_ALLOW_NONE = False + signals = ["click", "change"] # FIXME: Only single profile is managed so far - def __init__(self, host, target, on_click=None, on_change=None, user_data=None, profiles=None): + def __init__( + self, host, target, on_click=None, on_change=None, user_data=None, profiles=None + ): QuickContactList.__init__(self, host, profiles) self.contact_list = self.host.contact_lists[self.profile] - #we now build the widget + # we now build the widget self.status_bar = StatusBar(host) self.frame = sat_widgets.FocusFrame(self._buildList(), None, self.status_bar) - PrimitivusWidget.__init__(self, self.frame, _(u'Contacts')) + PrimitivusWidget.__init__(self, self.frame, _(u"Contacts")) if on_click: - urwid.connect_signal(self, 'click', on_click, user_data) + urwid.connect_signal(self, "click", on_click, user_data) if on_change: - urwid.connect_signal(self, 'change', on_change, user_data) - self.host.addListener('notification', self.onNotification, [self.profile]) - self.host.addListener('notificationsClear', self.onNotification, [self.profile]) + urwid.connect_signal(self, "change", on_change, user_data) + self.host.addListener("notification", self.onNotification, [self.profile]) + self.host.addListener("notificationsClear", self.onNotification, [self.profile]) self.postInit() def update(self, entities=None, type_=None, profile=None): @@ -64,21 +67,31 @@ except IndexError: pass self._invalidate() - self.host.redraw() # FIXME: check if can be avoided + self.host.redraw() # FIXME: check if can be avoided def keypress(self, size, key): # FIXME: we have a temporary behaviour here: FOCUS_SWITCH change focus globally in the parent, # and FOCUS_UP/DOWN is transwmitter to parent if we are respectively on the first or last element if key in sat_widgets.FOCUS_KEYS: - if (key == a_key['FOCUS_SWITCH'] or (key == a_key['FOCUS_UP'] and self.frame.focus_position == 'body') or - (key == a_key['FOCUS_DOWN'] and self.frame.focus_position == 'footer')): + if ( + key == a_key["FOCUS_SWITCH"] + or (key == a_key["FOCUS_UP"] and self.frame.focus_position == "body") + or (key == a_key["FOCUS_DOWN"] and self.frame.focus_position == "footer") + ): return key - if key == a_key['STATUS_HIDE']: #user wants to (un)hide contacts' statuses + if key == a_key["STATUS_HIDE"]: # user wants to (un)hide contacts' statuses self.contact_list.show_status = not self.contact_list.show_status self.update() - elif key == a_key['DISCONNECTED_HIDE']: #user wants to (un)hide disconnected contacts - self.host.bridge.setParam(C.SHOW_OFFLINE_CONTACTS, C.boolConst(not self.contact_list.show_disconnected), "General", profile_key=self.profile) - elif key == a_key['RESOURCES_HIDE']: #user wants to (un)hide contacts resources + elif ( + key == a_key["DISCONNECTED_HIDE"] + ): # user wants to (un)hide disconnected contacts + self.host.bridge.setParam( + C.SHOW_OFFLINE_CONTACTS, + C.boolConst(not self.contact_list.show_disconnected), + "General", + profile_key=self.profile, + ) + elif key == a_key["RESOURCES_HIDE"]: # user wants to (un)hide contacts resources self.contact_list.showResources(not self.contact_list.show_resources) self.update() return super(ContactList, self).keypress(size, key) @@ -128,7 +141,7 @@ def _groupClicked(self, group_wid): group = group_wid.getValue() data = self.contact_list.getGroupData(group) - data[C.GROUP_DATA_FOLDED] = not data.setdefault(C.GROUP_DATA_FOLDED, False) + data[C.GROUP_DATA_FOLDED] = not data.setdefault(C.GROUP_DATA_FOLDED, False) self.setFocus(group) self.update() @@ -141,7 +154,7 @@ """ entity = contact_wid.data self.host.modeHint(C.MODE_INSERTION) - self._emit('click', entity) + self._emit("click", entity) def onNotification(self, entity, notif, profile): notifs = list(self.host.getNotifs(C.ENTITY_ALL, profile=self.profile)) @@ -153,7 +166,17 @@ # Methods to build the widget - def _buildEntityWidget(self, entity, keys=None, use_bare_jid=False, with_notifs=True, with_show_attr=True, markup_prepend=None, markup_append=None, special=False): + def _buildEntityWidget( + self, + entity, + keys=None, + use_bare_jid=False, + with_notifs=True, + with_show_attr=True, + markup_prepend=None, + markup_append=None, + special=False, + ): """Build one contact markup data @param entity (jid.JID): entity to build @@ -180,7 +203,7 @@ else: cache = self.contact_list.getCache(entity) for key in keys: - if key.startswith('cache_'): + if key.startswith("cache_"): entity_txt = cache.get(key[6:]) else: entity_txt = getattr(entity, key) @@ -193,18 +216,22 @@ show = self.contact_list.getCache(entity, C.PRESENCE_SHOW) if show is None: show = C.PRESENCE_UNAVAILABLE - show_icon, entity_attr = C.PRESENCE.get(show, ('', 'default')) + show_icon, entity_attr = C.PRESENCE.get(show, ("", "default")) markup.insert(0, u"{} ".format(show_icon)) else: - entity_attr = 'default' + entity_attr = "default" - notifs = list(self.host.getNotifs(entity, exact_jid=special, profile=self.profile)) + notifs = list( + self.host.getNotifs(entity, exact_jid=special, profile=self.profile) + ) if notifs: - header = [('cl_notifs', u'({})'.format(len(notifs))), u' '] - if list(self.host.getNotifs(entity.bare, C.NOTIFY_MENTION, profile=self.profile)): - header = ('cl_mention', header) + header = [("cl_notifs", u"({})".format(len(notifs))), u" "] + if list( + self.host.getNotifs(entity.bare, C.NOTIFY_MENTION, profile=self.profile) + ): + header = ("cl_mention", header) else: - header = u'' + header = u"" markup.append((entity_attr, entity_txt)) if markup_prepend: @@ -212,12 +239,14 @@ if markup_append: markup.extend(markup_append) - widget = sat_widgets.SelectableText(markup, - selected = entity in selected, - header = header) + widget = sat_widgets.SelectableText( + markup, selected=entity in selected, header=header + ) widget.data = entity - widget.comp = entity_txt.lower() # value to use for sorting - urwid.connect_signal(widget, 'change', self._contactClicked, user_args=[use_bare_jid]) + widget.comp = entity_txt.lower() # value to use for sorting + urwid.connect_signal( + widget, "change", self._contactClicked, user_args=[use_bare_jid] + ) return widget def _buildEntities(self, content, entities): @@ -231,25 +260,40 @@ widgets = [] # list of built widgets for entity in entities: - if entity in self.contact_list._specials or not self.contact_list.entityVisible(entity): + if ( + entity in self.contact_list._specials + or not self.contact_list.entityVisible(entity) + ): continue markup_extra = [] if self.contact_list.show_resources: for resource in self.contact_list.getCache(entity, C.CONTACT_RESOURCES): - resource_disp = ('resource_main' if resource == self.contact_list.getCache(entity, C.CONTACT_MAIN_RESOURCE) else 'resource', "\n " + resource) + resource_disp = ( + "resource_main" + if resource + == self.contact_list.getCache(entity, C.CONTACT_MAIN_RESOURCE) + else "resource", + "\n " + resource, + ) markup_extra.append(resource_disp) if self.contact_list.show_status: - status = self.contact_list.getCache(jid.JID('%s/%s' % (entity, resource)), 'status') - status_disp = ('status', "\n " + status) if status else "" + status = self.contact_list.getCache( + jid.JID("%s/%s" % (entity, resource)), "status" + ) + status_disp = ("status", "\n " + status) if status else "" markup_extra.append(status_disp) - else: if self.contact_list.show_status: - status = self.contact_list.getCache(entity, 'status') - status_disp = ('status', "\n " + status) if status else "" + status = self.contact_list.getCache(entity, "status") + status_disp = ("status", "\n " + status) if status else "" markup_extra.append(status_disp) - widget = self._buildEntityWidget(entity, ('cache_nick', 'cache_name', 'node'), use_bare_jid=True, markup_append=markup_extra) + widget = self._buildEntityWidget( + entity, + ("cache_nick", "cache_name", "node"), + use_bare_jid=True, + markup_append=markup_extra, + ) widgets.append(widget) widgets.sort(key=lambda widget: widget.comp) @@ -264,13 +308,20 @@ for entity in specials: if current is not None and current.bare == entity.bare: # nested entity (e.g. MUC private conversations) - widget = self._buildEntityWidget(entity, ('resource',), markup_prepend=' ', special=True) + widget = self._buildEntityWidget( + entity, ("resource",), markup_prepend=" ", special=True + ) else: # the special widgets if entity.resource: - widget = self._buildEntityWidget(entity, ('resource',), special=True) + widget = self._buildEntityWidget(entity, ("resource",), special=True) else: - widget = self._buildEntityWidget(entity, ('cache_nick', 'cache_name', 'node'), with_show_attr=False, special=True) + widget = self._buildEntityWidget( + entity, + ("cache_nick", "cache_name", "node"), + with_show_attr=False, + special=True, + ) content.append(widget) def _buildList(self): @@ -279,26 +330,35 @@ self._buildSpecials(content) if self.contact_list._specials: - content.append(urwid.Divider('=')) + content.append(urwid.Divider("=")) groups = list(self.contact_list._groups) groups.sort(key=lambda x: x.lower() if x else x) for group in groups: data = self.contact_list.getGroupData(group) folded = data.get(C.GROUP_DATA_FOLDED, False) - jids = list(data['jids']) - if group is not None and (self.contact_list.anyEntityVisible(jids) or self.contact_list.show_empty_groups): - header = '[-]' if not folded else '[+]' - widget = sat_widgets.ClickableText(group, header=header + ' ') + jids = list(data["jids"]) + if group is not None and ( + self.contact_list.anyEntityVisible(jids) + or self.contact_list.show_empty_groups + ): + header = "[-]" if not folded else "[+]" + widget = sat_widgets.ClickableText(group, header=header + " ") content.append(widget) - urwid.connect_signal(widget, 'click', self._groupClicked) + urwid.connect_signal(widget, "click", self._groupClicked) if not folded: self._buildEntities(content, jids) - not_in_roster = set(self.contact_list._cache).difference(self.contact_list._roster).difference(self.contact_list._specials).difference((self.contact_list.whoami.bare,)) + not_in_roster = ( + set(self.contact_list._cache) + .difference(self.contact_list._roster) + .difference(self.contact_list._specials) + .difference((self.contact_list.whoami.bare,)) + ) if not_in_roster: - content.append(urwid.Divider('-')) + content.append(urwid.Divider("-")) self._buildEntities(content, not_in_roster) return urwid.ListBox(content) + quick_widgets.register(QuickContactList, ContactList)
--- a/sat_frontends/primitivus/game_tarot.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/primitivus/game_tarot.py Wed Jun 27 20:14:46 2018 +0200 @@ -28,7 +28,8 @@ class CardDisplayer(urwid.Text): """Show a card""" - signals = ['click'] + + signals = ["click"] def __init__(self, card): self.__selected = False @@ -39,15 +40,15 @@ return True def keypress(self, size, key): - if key == a_key['CARD_SELECT']: + if key == a_key["CARD_SELECT"]: self.select(not self.__selected) - self._emit('click') + self._emit("click") return key def mouse_event(self, size, event, button, x, y, focus): if urwid.is_mouse_event(event) and button == 1: self.select(not self.__selected) - self._emit('click') + self._emit("click") return True return False @@ -56,7 +57,7 @@ self.__selected = state attr, txt = self.card.getAttrText() if self.__selected: - attr += '_selected' + attr += "_selected" self.set_text((attr, txt)) self._invalidate() @@ -75,14 +76,15 @@ class Hand(urwid.WidgetWrap): """Used to display several cards, and manage a hand""" - signals = ['click'] + + signals = ["click"] def __init__(self, hand=[], selectable=False, on_click=None, user_data=None): """@param hand: list of Card""" self.__selectable = selectable self.columns = urwid.Columns([], dividechars=1) if on_click: - urwid.connect_signal(self, 'click', on_click, user_data) + urwid.connect_signal(self, "click", on_click, user_data) if hand: self.update(hand) urwid.WidgetWrap.__init__(self, self.columns) @@ -95,9 +97,9 @@ if CardDisplayer in [wid.__class__ for wid in self.columns.widget_list]: return self.columns.keypress(size, key) else: - #No card displayed, we still have to manage the clicks - if key == a_key['CARD_SELECT']: - self._emit('click', None) + # No card displayed, we still have to manage the clicks + if key == a_key["CARD_SELECT"]: + self._emit("click", None) return key def getSelected(self): @@ -116,22 +118,23 @@ del self.columns.column_types[:] except IndexError: pass - self.columns.contents.append((urwid.Text(''), ('weight', 1, False))) + self.columns.contents.append((urwid.Text(""), ("weight", 1, False))) for card in hand: widget = CardDisplayer(card) self.columns.widget_list.append(widget) - self.columns.column_types.append(('fixed', 3)) - urwid.connect_signal(widget, 'click', self.__onClick) - self.columns.contents.append((urwid.Text(''), ('weight', 1, False))) + self.columns.column_types.append(("fixed", 3)) + urwid.connect_signal(widget, "click", self.__onClick) + self.columns.contents.append((urwid.Text(""), ("weight", 1, False))) self.columns.focus_position = 1 def __onClick(self, card_wid): - self._emit('click', card_wid) + self._emit("click", card_wid) class Card(TarotCard): """This class is used to represent a card, logically and give a text representation with attributes""" + SIZE = 3 # size of a displayed card def __init__(self, suit, value): @@ -146,25 +149,25 @@ value = self.value[0].upper() + self.value[1] if self.suit == "atout": if self.value == "excuse": - suit = 'c' + suit = "c" else: - suit = 'A' - color = 'neutral' + suit = "A" + color = "neutral" elif self.suit == "pique": - suit = u'♠' - color = 'black' + suit = u"♠" + color = "black" elif self.suit == "trefle": - suit = u'♣' - color = 'black' + suit = u"♣" + color = "black" elif self.suit == "coeur": - suit = u'♥' - color = 'red' + suit = u"♥" + color = "red" elif self.suit == "carreau": - suit = u'♦' - color = 'red' + suit = u"♦" + color = "red" if self.bout: - color = 'special' - return ('card_%s' % color, u"%s%s" % (value, suit)) + color = "special" + return ("card_%s" % color, u"%s%s" % (value, suit)) def getWidget(self): """Return a widget representing the card""" @@ -181,10 +184,12 @@ """Put a card on the table @param location: where to put the card (top, left, bottom or right) @param card: Card to play or None""" - assert location in ['top', 'left', 'bottom', 'right'] + assert location in ["top", "left", "bottom", "right"] assert isinstance(card, Card) or card == None - if [getattr(self, place) for place in ['top', 'left', 'bottom', 'right']].count(None) == 0: - #If the table is full of card, we remove them + if [getattr(self, place) for place in ["top", "left", "bottom", "right"]].count( + None + ) == 0: + # If the table is full of card, we remove them self.top = self.left = self.bottom = self.right = None setattr(self, location, card) self._invalidate() @@ -199,14 +204,16 @@ cards = {} max_col, = size separator = " - " - margin = max((max_col - Card.SIZE) / 2, 0) * ' ' - margin_center = max((max_col - Card.SIZE * 2 - len(separator)) / 2, 0) * ' ' - for location in ['top', 'left', 'bottom', 'right']: + margin = max((max_col - Card.SIZE) / 2, 0) * " " + margin_center = max((max_col - Card.SIZE * 2 - len(separator)) / 2, 0) * " " + for location in ["top", "left", "bottom", "right"]: card = getattr(self, location) - cards[location] = card.getAttrText() if card else Card.SIZE * ' ' - render_wid = [urwid.Text([margin, cards['top']]), - urwid.Text([margin_center, cards['left'], separator, cards['right']]), - urwid.Text([margin, cards['bottom']])] + cards[location] = card.getAttrText() if card else Card.SIZE * " " + render_wid = [ + urwid.Text([margin, cards["top"]]), + urwid.Text([margin_center, cards["left"], separator, cards["right"]]), + urwid.Text([margin, cards["bottom"]]), + ] return urwid.Pile(render_wid) @@ -216,13 +223,20 @@ def __init__(self, parent, referee, players): QuickTarotGame.__init__(self, parent, referee, players) self.loadCards() - self.top = urwid.Pile([urwid.Padding(urwid.Text(self.top_nick), 'center')]) - #self.parent.host.debug() + self.top = urwid.Pile([urwid.Padding(urwid.Text(self.top_nick), "center")]) + # self.parent.host.debug() self.table = Table() - self.center = urwid.Columns([('fixed', len(self.left_nick), urwid.Filler(urwid.Text(self.left_nick))), - urwid.Filler(self.table), - ('fixed', len(self.right_nick), urwid.Filler(urwid.Text(self.right_nick))) - ]) + self.center = urwid.Columns( + [ + ("fixed", len(self.left_nick), urwid.Filler(urwid.Text(self.left_nick))), + urwid.Filler(self.table), + ( + "fixed", + len(self.right_nick), + urwid.Filler(urwid.Text(self.right_nick)), + ), + ] + ) """urwid.Pile([urwid.Padding(self.top_card_wid,'center'), urwid.Columns([('fixed',len(self.left_nick),urwid.Text(self.left_nick)), urwid.Padding(self.center_cards_wid,'center'), @@ -231,15 +245,19 @@ urwid.Padding(self.bottom_card_wid,'center') ])""" self.hand_wid = Hand(selectable=True, on_click=self.onClick) - self.main_frame = urwid.Frame(self.center, header=self.top, footer=self.hand_wid, focus_part='footer') + self.main_frame = urwid.Frame( + self.center, header=self.top, footer=self.hand_wid, focus_part="footer" + ) urwid.WidgetWrap.__init__(self, self.main_frame) - self.parent.host.bridge.tarotGameReady(self.player_nick, referee, self.parent.profile) + self.parent.host.bridge.tarotGameReady( + self.player_nick, referee, self.parent.profile + ) def loadCards(self): """Load all the cards in memory""" QuickTarotGame.loadCards(self) - for value in map(str, range(1, 22)) + ['excuse']: - card = Card('atout', value) + for value in map(str, range(1, 22)) + ["excuse"]: + card = Card("atout", value) self.cards[card.suit, card.value] = card self.deck.append(card) for suit in ["pique", "coeur", "carreau", "trefle"]: @@ -252,10 +270,12 @@ """Start a new game, with given hand""" if hand is []: # reset the display after the scores have been showed self.resetRound() - for location in ['top', 'left', 'bottom', 'right']: + for location in ["top", "left", "bottom", "right"]: self.table.putCard(location, None) self.parent.host.redraw() - self.parent.host.bridge.tarotGameReady(self.player_nick, self.referee, self.parent.profile) + self.parent.host.bridge.tarotGameReady( + self.player_nick, self.referee, self.parent.profile + ) return QuickTarotGame.tarotGameNewHandler(self, hand) self.hand_wid.update(self.hand) @@ -264,8 +284,14 @@ def tarotGameChooseContratHandler(self, xml_data): """Called when the player has to select his contrat @param xml_data: SàT xml representation of the form""" - form = xmlui.create(self.parent.host, xml_data, title=_('Please choose your contrat'), flags=['NO_CANCEL'], profile=self.parent.profile) - form.show(valign='top') + form = xmlui.create( + self.parent.host, + xml_data, + title=_("Please choose your contrat"), + flags=["NO_CANCEL"], + profile=self.parent.profile, + ) + form.show(valign="top") def tarotGameShowCardsHandler(self, game_stage, cards, data): """Display cards in the middle of the game (to show for e.g. chien ou poignée)""" @@ -282,8 +308,14 @@ if not winners and not loosers: title = _("Draw game") else: - title = _('You win \o/') if self.player_nick in winners else _('You loose :(') - form = xmlui.create(self.parent.host, xml_data, title=title, flags=['NO_CANCEL'], profile=self.parent.profile) + title = _("You win \o/") if self.player_nick in winners else _("You loose :(") + form = xmlui.create( + self.parent.host, + xml_data, + title=title, + flags=["NO_CANCEL"], + profile=self.parent.profile, + ) form.show() def tarotGameInvalidCardsHandler(self, phase, played_cards, invalid_cards): @@ -291,10 +323,12 @@ @param phase: phase of the game @param played_cards: all the cards played @param invalid_cards: cards which are invalid""" - QuickTarotGame.tarotGameInvalidCardsHandler(self, phase, played_cards, invalid_cards) + QuickTarotGame.tarotGameInvalidCardsHandler( + self, phase, played_cards, invalid_cards + ) self.hand_wid.update(self.hand) if self._autoplay == None: # No dialog if there is autoplay - self.parent.host.barNotify(_('Cards played are invalid !')) + self.parent.host.barNotify(_("Cards played are invalid !")) self.parent.host.redraw() def tarotGameCardsPlayedHandler(self, player, cards): @@ -305,8 +339,12 @@ self.parent.host.redraw() def _checkState(self): - if isinstance(self.center.widget_list[1].original_widget, Hand): # if we have a hand displayed - self.center.widget_list[1] = urwid.Filler(self.table) # we show again the table + if isinstance( + self.center.widget_list[1].original_widget, Hand + ): # if we have a hand displayed + self.center.widget_list[1] = urwid.Filler( + self.table + ) # we show again the table if self.state == "chien": self.to_show = [] self.state = "wait" @@ -320,18 +358,27 @@ ##EVENTS## def onClick(self, hand, card_wid): """Called when user do an action on the hand""" - if not self.state in ['play', 'ecart', 'wait_for_ecart']: - #it's not our turn, we ignore the click + if not self.state in ["play", "ecart", "wait_for_ecart"]: + # it's not our turn, we ignore the click card_wid.select(False) return self._checkState() if self.state == "ecart": if len(self.hand_wid.getSelected()) == 6: - pop_up_widget = sat_widgets.ConfirmDialog(_("Do you put these cards in chien ?"), yes_cb=self.onEcartDone, no_cb=self.parent.host.removePopUp) + pop_up_widget = sat_widgets.ConfirmDialog( + _("Do you put these cards in chien ?"), + yes_cb=self.onEcartDone, + no_cb=self.parent.host.removePopUp, + ) self.parent.host.showPopUp(pop_up_widget) elif self.state == "play": card = card_wid.getCard() - self.parent.host.bridge.tarotGamePlayCards(self.player_nick, self.referee, [(card.suit, card.value)], self.parent.profile) + self.parent.host.bridge.tarotGamePlayCards( + self.player_nick, + self.referee, + [(card.suit, card.value)], + self.parent.profile, + ) self.hand.remove(card) self.hand_wid.update(self.hand) self.state = "wait" @@ -343,6 +390,8 @@ ecart.append((card.suit, card.value)) self.hand.remove(card) self.hand_wid.update(self.hand) - self.parent.host.bridge.tarotGamePlayCards(self.player_nick, self.referee, ecart, self.parent.profile) + self.parent.host.bridge.tarotGamePlayCards( + self.player_nick, self.referee, ecart, self.parent.profile + ) self.state = "wait" self.parent.host.removePopUp()
--- a/sat_frontends/primitivus/keys.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/primitivus/keys.py Wed Jun 27 20:14:46 2018 +0200 @@ -23,47 +23,44 @@ action_key_map.update( - { + { # Edit bar ("edit", "MODE_INSERTION"): "i", ("edit", "MODE_COMMAND"): ":", ("edit", "HISTORY_PREV"): "up", ("edit", "HISTORY_NEXT"): "down", - # global - ("global", "MENU_HIDE"): 'meta m', - ("global", "NOTIFICATION_NEXT"): 'ctrl n', - ("global", "OVERLAY_HIDE"): 'ctrl s', - ("global", "DEBUG"): 'ctrl d', - ("global", "CONTACTS_HIDE"): 'f2', - ('global', "REFRESH_SCREEN"): "ctrl l", # ctrl l is used by Urwid to refresh screen - + ("global", "MENU_HIDE"): "meta m", + ("global", "NOTIFICATION_NEXT"): "ctrl n", + ("global", "OVERLAY_HIDE"): "ctrl s", + ("global", "DEBUG"): "ctrl d", + ("global", "CONTACTS_HIDE"): "f2", + ( + "global", + "REFRESH_SCREEN", + ): "ctrl l", # ctrl l is used by Urwid to refresh screen # global menu - ("menu_global", "APP_QUIT"): 'ctrl x', - ("menu_global", "ROOM_JOIN"): 'meta j', - + ("menu_global", "APP_QUIT"): "ctrl x", + ("menu_global", "ROOM_JOIN"): "meta j", # primitivus widgets ("primitivus_widget", "DECORATION_HIDE"): "meta l", - # contact list ("contact_list", "STATUS_HIDE"): "meta s", ("contact_list", "DISCONNECTED_HIDE"): "meta d", ("contact_list", "RESOURCES_HIDE"): "meta r", - # chat panel ("chat_panel", "OCCUPANTS_HIDE"): "meta p", ("chat_panel", "TIMESTAMP_HIDE"): "meta t", ("chat_panel", "SHORT_NICKNAME"): "meta n", ("chat_panel", "SUBJECT_SWITCH"): "meta s", ("chat_panel", "GOTO_BOTTOM"): "G", - - #card game - ("card_game", "CARD_SELECT"): ' ', - - #focus + # card game + ("card_game", "CARD_SELECT"): " ", + # focus ("focus", "FOCUS_EXTRA"): "ctrl f", - }) + } +) -action_key_map.set_close_namespaces(tuple(), ('global', 'focus', 'menu_global')) +action_key_map.set_close_namespaces(tuple(), ("global", "focus", "menu_global")) action_key_map.check_namespaces()
--- a/sat_frontends/primitivus/notify.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/primitivus/notify.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,27 +19,35 @@ import dbus + class Notify(object): """Used to send notification and detect if we have focus""" def __init__(self): - #X11 stuff + # X11 stuff self.display = None self.X11_id = -1 try: from Xlib import display as X_display + self.display = X_display.Display() self.X11_id = self.getFocus() except: pass - #Now we try to connect to Freedesktop D-Bus API + # Now we try to connect to Freedesktop D-Bus API try: bus = dbus.SessionBus() - db_object = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications', follow_name_owner_changes=True) - self.freedesktop_int = dbus.Interface(db_object, dbus_interface='org.freedesktop.Notifications') + db_object = bus.get_object( + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + follow_name_owner_changes=True, + ) + self.freedesktop_int = dbus.Interface( + db_object, dbus_interface="org.freedesktop.Notifications" + ) except: self.freedesktop_int = None @@ -56,7 +64,7 @@ def sendNotification(self, summ_mess, body_mess=""): """Send notification to the user if possible""" - #TODO: check options before sending notifications + # TODO: check options before sending notifications if self.freedesktop_int: self.sendFDNotification(summ_mess, body_mess) @@ -68,10 +76,17 @@ app_icon = "" summary = summ_mess body = body_mess - actions = dbus.Array(signature='s') - hints = dbus.Dictionary(signature='sv') + actions = dbus.Array(signature="s") + hints = dbus.Dictionary(signature="sv") expire_timeout = -1 - self.freedesktop_int.Notify(app_name, replaces_id, app_icon, - summary, body, actions, - hints, expire_timeout) + self.freedesktop_int.Notify( + app_name, + replaces_id, + app_icon, + summary, + body, + actions, + hints, + expire_timeout, + )
--- a/sat_frontends/primitivus/profile_manager.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/primitivus/profile_manager.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,6 +19,7 @@ from sat.core.i18n import _ from sat.core import log as logging + log = logging.getLogger(__name__) from sat_frontends.quick_frontend.quick_profile_manager import QuickProfileManager from sat_frontends.primitivus.constants import Const as C @@ -28,46 +29,68 @@ class ProfileManager(QuickProfileManager, urwid.WidgetWrap): - def __init__(self, host, autoconnect=None): QuickProfileManager.__init__(self, host, autoconnect) - #login & password box must be created before list because of onProfileChange - self.login_wid = sat_widgets.AdvancedEdit(_('Login:'), align='center') - self.pass_wid = sat_widgets.Password(_('Password:'), align='center') + # login & password box must be created before list because of onProfileChange + self.login_wid = sat_widgets.AdvancedEdit(_("Login:"), align="center") + self.pass_wid = sat_widgets.Password(_("Password:"), align="center") - style = ['no_first_select'] + style = ["no_first_select"] profiles = host.bridge.profilesListGet() profiles.sort() - self.list_profile = sat_widgets.List(profiles, style=style, align='center', on_change=self.onProfileChange) + self.list_profile = sat_widgets.List( + profiles, style=style, align="center", on_change=self.onProfileChange + ) - #new & delete buttons - buttons = [urwid.Button(_("New"), self.onNewProfile), - urwid.Button(_("Delete"), self.onDeleteProfile)] - buttons_flow = urwid.GridFlow(buttons, max([len(button.get_label()) for button in buttons])+4, 1, 1, 'center') + # new & delete buttons + buttons = [ + urwid.Button(_("New"), self.onNewProfile), + urwid.Button(_("Delete"), self.onDeleteProfile), + ] + buttons_flow = urwid.GridFlow( + buttons, + max([len(button.get_label()) for button in buttons]) + 4, + 1, + 1, + "center", + ) + + # second part: login information: + divider = urwid.Divider("-") - #second part: login information: - divider = urwid.Divider('-') + # connect button + connect_button = sat_widgets.CustomButton( + _("Connect"), self.onConnectProfiles, align="center" + ) - #connect button - connect_button = sat_widgets.CustomButton(_("Connect"), self.onConnectProfiles, align='center') - - #we now build the widget - list_walker = urwid.SimpleFocusListWalker([buttons_flow,self.list_profile, divider, self.login_wid, self.pass_wid, connect_button]) + # we now build the widget + list_walker = urwid.SimpleFocusListWalker( + [ + buttons_flow, + self.list_profile, + divider, + self.login_wid, + self.pass_wid, + connect_button, + ] + ) frame_body = urwid.ListBox(list_walker) - frame = urwid.Frame(frame_body,urwid.AttrMap(urwid.Text(_("Profile Manager"),align='center'),'title')) + frame = urwid.Frame( + frame_body, + urwid.AttrMap(urwid.Text(_("Profile Manager"), align="center"), "title"), + ) self.main_widget = urwid.LineBox(frame) urwid.WidgetWrap.__init__(self, self.main_widget) self.go(autoconnect) - def keypress(self, size, key): - if key == a_key['APP_QUIT']: + if key == a_key["APP_QUIT"]: self.host.onExit() raise urwid.ExitMainLoop() - elif key in (a_key['FOCUS_UP'], a_key['FOCUS_DOWN']): - focus_diff = 1 if key==a_key['FOCUS_DOWN'] else -1 + elif key in (a_key["FOCUS_UP"], a_key["FOCUS_DOWN"]): + focus_diff = 1 if key == a_key["FOCUS_DOWN"] else -1 list_box = self.main_widget.base_widget.body current_focus = list_box.body.get_focus()[1] if current_focus is None: @@ -77,7 +100,9 @@ if current_focus < 0 or current_focus >= len(list_box.body): break if list_box.body[current_focus].selectable(): - list_box.set_focus(current_focus, 'above' if focus_diff == 1 else 'below') + list_box.set_focus( + current_focus, "above" if focus_diff == 1 else "below" + ) list_box._invalidate() return return super(ProfileManager, self).keypress(size, key) @@ -88,17 +113,26 @@ def newProfile(self, button, edit): """Create the profile""" name = edit.get_edit_text() - self.host.bridge.profileCreate(name, callback=lambda: self.newProfileCreated(name), errback=self.profileCreationFailure) + self.host.bridge.profileCreate( + name, + callback=lambda: self.newProfileCreated(name), + errback=self.profileCreationFailure, + ) def newProfileCreated(self, profile): # new profile will be selected, and a selected profile assume the session is started - self.host.bridge.profileStartSession('', profile, callback=lambda dummy: self.newProfileSessionStarted(profile), errback=self.profileCreationFailure) + self.host.bridge.profileStartSession( + "", + profile, + callback=lambda dummy: self.newProfileSessionStarted(profile), + errback=self.profileCreationFailure, + ) def newProfileSessionStarted(self, profile): self.host.removePopUp() self.refillProfiles() self.list_profile.selectValue(profile) - self.current.profile=profile + self.current.profile = profile self.getConnectionParams(profile) self.host.redraw() @@ -112,12 +146,23 @@ self.host.removePopUp() def onNewProfile(self, e): - pop_up_widget = sat_widgets.InputDialog(_("New profile"), _("Please enter a new profile name"), cancel_cb=self.cancelDialog, ok_cb=self.newProfile) + pop_up_widget = sat_widgets.InputDialog( + _("New profile"), + _("Please enter a new profile name"), + cancel_cb=self.cancelDialog, + ok_cb=self.newProfile, + ) self.host.showPopUp(pop_up_widget) def onDeleteProfile(self, e): if self.current.profile: - pop_up_widget = sat_widgets.ConfirmDialog(_("Are you sure you want to delete the profile {} ?").format(self.current.profile), no_cb=self.cancelDialog, yes_cb=self.deleteProfile) + pop_up_widget = sat_widgets.ConfirmDialog( + _("Are you sure you want to delete the profile {} ?").format( + self.current.profile + ), + no_cb=self.cancelDialog, + yes_cb=self.deleteProfile, + ) self.host.showPopUp(pop_up_widget) def onConnectProfiles(self, button): @@ -149,7 +194,7 @@ def setJID(self, jid_): self.login_wid.set_edit_text(jid_) self.current.login = jid_ - self.host.redraw() # FIXME: redraw should be avoided + self.host.redraw() # FIXME: redraw should be avoided def setPassword(self, password): self.pass_wid.set_edit_text(password) @@ -164,16 +209,20 @@ self.updateConnectionParams() focused = list_wid.focus selected = focused.getState() if focused is not None else False - if not selected: # profile was just unselected + if not selected: # profile was just unselected return - focused.setState(False, invisible=True) # we don't want the widget to be selected until we are sure we can access it + focused.setState( + False, invisible=True + ) # we don't want the widget to be selected until we are sure we can access it + def authenticate_cb(data, cb_id, profile): - if C.bool(data.pop('validated', C.BOOL_FALSE)): + if C.bool(data.pop("validated", C.BOOL_FALSE)): self.current.profile = profile focused.setState(True, invisible=True) self.getConnectionParams(profile) self.host.redraw() self.host.actionManager(data, callback=authenticate_cb, profile=profile) - self.host.launchAction(C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=focused.text) - + self.host.launchAction( + C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=focused.text + )
--- a/sat_frontends/primitivus/progress.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/primitivus/progress.py Wed Jun 27 20:14:46 2018 +0200 @@ -34,54 +34,58 @@ self.progress_dict = {} listbox = urwid.ListBox(self.progress_list) buttons = [] - buttons.append(sat_widgets.CustomButton(_('Clear progress list'), self._onClear)) + buttons.append(sat_widgets.CustomButton(_("Clear progress list"), self._onClear)) max_len = max([button.getSize() for button in buttons]) - buttons_wid = urwid.GridFlow(buttons,max_len,1,0,'center') + buttons_wid = urwid.GridFlow(buttons, max_len, 1, 0, "center") main_wid = sat_widgets.FocusFrame(listbox, footer=buttons_wid) urwid.WidgetWrap.__init__(self, main_wid) def add(self, progress_id, message, profile): mess_wid = urwid.Text(message) - progr_wid = urwid.ProgressBar('progress_normal', 'progress_complete') + progr_wid = urwid.ProgressBar("progress_normal", "progress_complete") column = urwid.Columns([mess_wid, progr_wid]) - self.progress_dict[(progress_id, profile)] = {'full':column,'progress':progr_wid,'state':'init'} + self.progress_dict[(progress_id, profile)] = { + "full": column, + "progress": progr_wid, + "state": "init", + } self.progress_list.append(column) self.progressCB(self.host.loop, (progress_id, message, profile)) def progressCB(self, loop, data): progress_id, message, profile = data data = self.host.bridge.progressGet(progress_id, profile) - pbar = self.progress_dict[(progress_id, profile)]['progress'] + pbar = self.progress_dict[(progress_id, profile)]["progress"] if data: - if self.progress_dict[(progress_id, profile)]['state'] == 'init': - #first answer, we must construct the bar - self.progress_dict[(progress_id, profile)]['state'] = 'progress' - pbar.done = float(data['size']) + if self.progress_dict[(progress_id, profile)]["state"] == "init": + # first answer, we must construct the bar + self.progress_dict[(progress_id, profile)]["state"] = "progress" + pbar.done = float(data["size"]) - pbar.set_completion(float(data['position'])) + pbar.set_completion(float(data["position"])) self.updateNotBar() else: - if self.progress_dict[(progress_id, profile)]['state'] == 'progress': - self.progress_dict[(progress_id, profile)]['state'] = 'done' + if self.progress_dict[(progress_id, profile)]["state"] == "progress": + self.progress_dict[(progress_id, profile)]["state"] = "done" pbar.set_completion(pbar.done) self.updateNotBar() return - loop.set_alarm_in(0.2,self.progressCB, (progress_id, message, profile)) + loop.set_alarm_in(0.2, self.progressCB, (progress_id, message, profile)) def _removeBar(self, progress_id, profile): - wid = self.progress_dict[(progress_id, profile)]['full'] + wid = self.progress_dict[(progress_id, profile)]["full"] self.progress_list.remove(wid) - del(self.progress_dict[(progress_id, profile)]) + del (self.progress_dict[(progress_id, profile)]) def _onClear(self, button): - to_remove = [] - for progress_id, profile in self.progress_dict: - if self.progress_dict[(progress_id, profile)]['state'] == 'done': - to_remove.append((progress_id, profile)) - for progress_id, profile in to_remove: - self._removeBar(progress_id, profile) - self.updateNotBar() + to_remove = [] + for progress_id, profile in self.progress_dict: + if self.progress_dict[(progress_id, profile)]["state"] == "done": + to_remove.append((progress_id, profile)) + for progress_id, profile in to_remove: + self._removeBar(progress_id, profile) + self.updateNotBar() def updateNotBar(self): if not self.progress_dict: @@ -90,9 +94,8 @@ progress = 0 nb_bars = 0 for progress_id, profile in self.progress_dict: - pbar = self.progress_dict[(progress_id, profile)]['progress'] - progress += pbar.current/pbar.done*100 - nb_bars+=1 - av_progress = progress/float(nb_bars) + pbar = self.progress_dict[(progress_id, profile)]["progress"] + progress += pbar.current / pbar.done * 100 + nb_bars += 1 + av_progress = progress / float(nb_bars) self.host.setProgress(av_progress) -
--- a/sat_frontends/primitivus/status.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/primitivus/status.py Wed Jun 27 20:14:46 2018 +0200 @@ -25,51 +25,90 @@ class StatusBar(urwid.Columns): - def __init__(self, host): self.host = host - self.presence = sat_widgets.ClickableText('') - status_prefix = urwid.Text('[') - status_suffix = urwid.Text(']') - self.status = sat_widgets.ClickableText('') - self.setPresenceStatus(C.PRESENCE_UNAVAILABLE, '') - urwid.Columns.__init__(self, [('weight', 1, self.presence), ('weight', 1, status_prefix), - ('weight', 9, self.status), ('weight', 1, status_suffix)]) - urwid.connect_signal(self.presence, 'click', self.onPresenceClick) - urwid.connect_signal(self.status, 'click', self.onStatusClick) + self.presence = sat_widgets.ClickableText("") + status_prefix = urwid.Text("[") + status_suffix = urwid.Text("]") + self.status = sat_widgets.ClickableText("") + self.setPresenceStatus(C.PRESENCE_UNAVAILABLE, "") + urwid.Columns.__init__( + self, + [ + ("weight", 1, self.presence), + ("weight", 1, status_prefix), + ("weight", 9, self.status), + ("weight", 1, status_suffix), + ], + ) + urwid.connect_signal(self.presence, "click", self.onPresenceClick) + urwid.connect_signal(self.status, "click", self.onStatusClick) def onPresenceClick(self, sender=None): - if not self.host.bridge.isConnected(self.host.current_profile): # FIXME: manage multi-profiles + if not self.host.bridge.isConnected( + self.host.current_profile + ): # FIXME: manage multi-profiles return options = [commonConst.PRESENCE[presence] for presence in commonConst.PRESENCE] - list_widget = sat_widgets.GenericList(options=options, option_type=sat_widgets.ClickableText, on_click=self.onChange) - decorated = sat_widgets.LabelLine(list_widget, sat_widgets.SurroundedText(_('Set your presence'))) + list_widget = sat_widgets.GenericList( + options=options, option_type=sat_widgets.ClickableText, on_click=self.onChange + ) + decorated = sat_widgets.LabelLine( + list_widget, sat_widgets.SurroundedText(_("Set your presence")) + ) self.host.showPopUp(decorated) def onStatusClick(self, sender=None): - if not self.host.bridge.isConnected(self.host.current_profile): # FIXME: manage multi-profiles + if not self.host.bridge.isConnected( + self.host.current_profile + ): # FIXME: manage multi-profiles return - pop_up_widget = sat_widgets.InputDialog(_('Set your status'), _('New status'), default_txt=self.status.get_text(), - cancel_cb=self.host.removePopUp, ok_cb=self.onChange) + pop_up_widget = sat_widgets.InputDialog( + _("Set your status"), + _("New status"), + default_txt=self.status.get_text(), + cancel_cb=self.host.removePopUp, + ok_cb=self.onChange, + ) self.host.showPopUp(pop_up_widget) def onChange(self, sender=None, user_data=None): new_value = user_data.get_text() - previous = ([key for key in C.PRESENCE if C.PRESENCE[key][0] == self.presence.get_text()][0], self.status.get_text()) + previous = ( + [key for key in C.PRESENCE if C.PRESENCE[key][0] == self.presence.get_text()][ + 0 + ], + self.status.get_text(), + ) if isinstance(user_data, sat_widgets.ClickableText): - new = ([key for key in commonConst.PRESENCE if commonConst.PRESENCE[key] == new_value][0], previous[1]) + new = ( + [ + key + for key in commonConst.PRESENCE + if commonConst.PRESENCE[key] == new_value + ][0], + previous[1], + ) elif isinstance(user_data, sat_widgets.AdvancedEdit): new = (previous[0], new_value[0]) if new != previous: - statuses = {C.PRESENCE_STATUSES_DEFAULT: new[1]} # FIXME: manage multilingual statuses - for profile in self.host.profiles: # FIXME: for now all the profiles share the same status - self.host.bridge.setPresence(show=new[0], statuses=statuses, profile_key=profile) + statuses = { + C.PRESENCE_STATUSES_DEFAULT: new[1] + } # FIXME: manage multilingual statuses + for ( + profile + ) in ( + self.host.profiles + ): # FIXME: for now all the profiles share the same status + self.host.bridge.setPresence( + show=new[0], statuses=statuses, profile_key=profile + ) self.setPresenceStatus(new[0], new[1]) self.host.removePopUp() def setPresenceStatus(self, show, status): show_icon, show_attr = C.PRESENCE.get(show) - self.presence.set_text(('show_normal', show_icon)) + self.presence.set_text(("show_normal", show_icon)) if status is not None: self.status.set_text((show_attr, status)) self.host.redraw()
--- a/sat_frontends/primitivus/widget.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/primitivus/widget.py Wed Jun 27 20:14:46 2018 +0200 @@ -18,6 +18,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.core import log as logging + log = logging.getLogger(__name__) import urwid from urwid_satext import sat_widgets @@ -27,7 +28,7 @@ class PrimitivusWidget(urwid.WidgetWrap): """Base widget for Primitivus""" - def __init__(self, w, title=''): + def __init__(self, w, title=""): self._title = title self._title_dynamic = None self._original_widget = w @@ -57,9 +58,9 @@ title_elts.append(self._title) if self._title_dynamic: title_elts.append(self._title_dynamic) - if len(all_profiles)>1 and profiles: - title_elts.append(u'[{}]'.format(u', '.join(profiles))) - return sat_widgets.SurroundedText(u' '.join(title_elts)) + if len(all_profiles) > 1 and profiles: + title_elts.append(u"[{}]".format(u", ".join(profiles))) + return sat_widgets.SurroundedText(u" ".join(title_elts)) @title.setter def title(self, value): @@ -83,9 +84,8 @@ """True if the decoration is visible""" return isinstance(self._w, sat_widgets.LabelLine) - def keypress(self, size, key): - if key == a_key['DECORATION_HIDE']: #user wants to (un)hide widget decoration + if key == a_key["DECORATION_HIDE"]: # user wants to (un)hide widget decoration show = not self.decorationVisible self.showDecoration(show) else: @@ -96,7 +96,9 @@ def showDecoration(self, show=True): """Show/Hide the decoration around the window""" - self._w = self._getDecoration(self._original_widget) if show else self._original_widget + self._w = ( + self._getDecoration(self._original_widget) if show else self._original_widget + ) def getMenu(self): raise NotImplementedError
--- a/sat_frontends/primitivus/xmlui.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/primitivus/xmlui.py Wed Jun 27 20:14:46 2018 +0200 @@ -24,6 +24,7 @@ from urwid_satext import sat_widgets from urwid_satext import files_management from sat.core.log import getLogger + log = getLogger(__name__) from sat_frontends.primitivus.constants import Const as C from sat_frontends.primitivus.widget import PrimitivusWidget @@ -39,25 +40,22 @@ def _xmluiOnChange(self, callback): """ Call callback with widget as only argument """ - urwid.connect_signal(self, 'change', self._event_callback, callback) + urwid.connect_signal(self, "change", self._event_callback, callback) class PrimitivusEmptyWidget(xmlui.EmptyWidget, urwid.Text): - def __init__(self, _xmlui_parent): - urwid.Text.__init__(self, '') + urwid.Text.__init__(self, "") class PrimitivusTextWidget(xmlui.TextWidget, urwid.Text): - def __init__(self, _xmlui_parent, value, read_only=False): urwid.Text.__init__(self, value) class PrimitivusLabelWidget(xmlui.LabelWidget, PrimitivusTextWidget): - def __init__(self, _xmlui_parent, value): - super(PrimitivusLabelWidget, self).__init__(_xmlui_parent, value+": ") + super(PrimitivusLabelWidget, self).__init__(_xmlui_parent, value + ": ") class PrimitivusJidWidget(xmlui.JidWidget, PrimitivusTextWidget): @@ -65,27 +63,27 @@ class PrimitivusDividerWidget(xmlui.DividerWidget, urwid.Divider): - - def __init__(self, _xmlui_parent, style='line'): - if style == 'line': - div_char = u'─' - elif style == 'dot': - div_char = u'·' - elif style == 'dash': - div_char = u'-' - elif style == 'plain': - div_char = u'█' - elif style == 'blank': - div_char = ' ' + def __init__(self, _xmlui_parent, style="line"): + if style == "line": + div_char = u"─" + elif style == "dot": + div_char = u"·" + elif style == "dash": + div_char = u"-" + elif style == "plain": + div_char = u"█" + elif style == "blank": + div_char = " " else: log.warning(_("Unknown div_char")) - div_char = u'─' + div_char = u"─" urwid.Divider.__init__(self, div_char) -class PrimitivusStringWidget(xmlui.StringWidget, sat_widgets.AdvancedEdit, PrimitivusEvents): - +class PrimitivusStringWidget( + xmlui.StringWidget, sat_widgets.AdvancedEdit, PrimitivusEvents +): def __init__(self, _xmlui_parent, value, read_only=False): sat_widgets.AdvancedEdit.__init__(self, edit_text=value) self.read_only = read_only @@ -106,8 +104,9 @@ pass -class PrimitivusPasswordWidget(xmlui.PasswordWidget, sat_widgets.Password, PrimitivusEvents): - +class PrimitivusPasswordWidget( + xmlui.PasswordWidget, sat_widgets.Password, PrimitivusEvents +): def __init__(self, _xmlui_parent, value, read_only=False): sat_widgets.Password.__init__(self, edit_text=value) self.read_only = read_only @@ -124,8 +123,9 @@ return self.get_edit_text() -class PrimitivusTextBoxWidget(xmlui.TextBoxWidget, sat_widgets.AdvancedEdit, PrimitivusEvents): - +class PrimitivusTextBoxWidget( + xmlui.TextBoxWidget, sat_widgets.AdvancedEdit, PrimitivusEvents +): def __init__(self, _xmlui_parent, value, read_only=False): sat_widgets.AdvancedEdit.__init__(self, edit_text=value, multiline=True) self.read_only = read_only @@ -143,9 +143,8 @@ class PrimitivusBoolWidget(xmlui.BoolWidget, urwid.CheckBox, PrimitivusEvents): - def __init__(self, _xmlui_parent, state, read_only=False): - urwid.CheckBox.__init__(self, '', state=state) + urwid.CheckBox.__init__(self, "", state=state) self.read_only = read_only def selectable(self): @@ -161,7 +160,6 @@ class PrimitivusIntWidget(xmlui.IntWidget, sat_widgets.AdvancedEdit, PrimitivusEvents): - def __init__(self, _xmlui_parent, value, read_only=False): sat_widgets.AdvancedEdit.__init__(self, edit_text=value) self.read_only = read_only @@ -178,17 +176,17 @@ return self.get_edit_text() -class PrimitivusButtonWidget(xmlui.ButtonWidget, sat_widgets.CustomButton, PrimitivusEvents): - +class PrimitivusButtonWidget( + xmlui.ButtonWidget, sat_widgets.CustomButton, PrimitivusEvents +): def __init__(self, _xmlui_parent, value, click_callback): sat_widgets.CustomButton.__init__(self, value, on_press=click_callback) def _xmluiOnClick(self, callback): - urwid.connect_signal(self, 'click', callback) + urwid.connect_signal(self, "click", callback) class PrimitivusListWidget(xmlui.ListWidget, sat_widgets.List, PrimitivusEvents): - def __init__(self, _xmlui_parent, options, selected, flags): sat_widgets.List.__init__(self, options=options, style=flags) self._xmluiSelectValues(selected) @@ -217,13 +215,18 @@ selected.append(value) self._xmluiSelectValues(selected) + class PrimitivusJidsListWidget(xmlui.ListWidget, sat_widgets.List, PrimitivusEvents): - def __init__(self, _xmlui_parent, jids, styles): - sat_widgets.List.__init__(self, options=jids+[''], # the empty field is here to add new jids if needed - option_type=lambda txt, align: sat_widgets.AdvancedEdit(edit_text=txt, align=align), - on_change=self._onChange) - self.delete=0 + sat_widgets.List.__init__( + self, + options=jids + [""], # the empty field is here to add new jids if needed + option_type=lambda txt, align: sat_widgets.AdvancedEdit( + edit_text=txt, align=align + ), + on_change=self._onChange, + ) + self.delete = 0 def _onChange(self, list_widget, jid_widget=None, text=None): if jid_widget is not None: @@ -239,13 +242,16 @@ return [jid_ for jid_ in self.getAllValues() if jid_] -class PrimitivusAdvancedListContainer(xmlui.AdvancedListContainer, sat_widgets.TableContainer, PrimitivusEvents): - - def __init__(self, _xmlui_parent, columns, selectable='no'): - options = {'ADAPT':()} - if selectable != 'no': - options['HIGHLIGHT'] = () - sat_widgets.TableContainer.__init__(self, columns=columns, options=options, row_selectable = selectable!='no') +class PrimitivusAdvancedListContainer( + xmlui.AdvancedListContainer, sat_widgets.TableContainer, PrimitivusEvents +): + def __init__(self, _xmlui_parent, columns, selectable="no"): + options = {"ADAPT": ()} + if selectable != "no": + options["HIGHLIGHT"] = () + sat_widgets.TableContainer.__init__( + self, columns=columns, options=options, row_selectable=selectable != "no" + ) def _xmluiAppend(self, widget): self.addWidget(widget) @@ -261,21 +267,20 @@ def _xmluiOnSelect(self, callback): """ Call callback with widget as only argument """ - urwid.connect_signal(self, 'click', self._event_callback, callback) + urwid.connect_signal(self, "click", self._event_callback, callback) class PrimitivusPairsContainer(xmlui.PairsContainer, sat_widgets.TableContainer): - def __init__(self, _xmlui_parent): - options = {'ADAPT':(0,), 'HIGHLIGHT':(0,)} - if self._xmlui_main.type == 'param': - options['FOCUS_ATTR'] = 'param_selected' + options = {"ADAPT": (0,), "HIGHLIGHT": (0,)} + if self._xmlui_main.type == "param": + options["FOCUS_ATTR"] = "param_selected" sat_widgets.TableContainer.__init__(self, columns=2, options=options) def _xmluiAppend(self, widget): if isinstance(widget, PrimitivusEmptyWidget): # we don't want highlight on empty widgets - widget = urwid.AttrMap(widget, 'default') + widget = urwid.AttrMap(widget, "default") self.addWidget(widget) @@ -284,7 +289,6 @@ class PrimitivusTabsContainer(xmlui.TabsContainer, sat_widgets.TabsContainer): - def __init__(self, _xmlui_parent): sat_widgets.TabsContainer.__init__(self) @@ -305,7 +309,7 @@ self._last_size = None def _xmluiAppend(self, widget): - if 'flow' not in widget.sizing(): + if "flow" not in widget.sizing(): widget = urwid.BoxAdapter(widget, self.BOX_HEIGHT) self.body.append(widget) @@ -324,7 +328,6 @@ class PrimitivusDialog(object): - def __init__(self, _xmlui_parent): self.host = _xmlui_parent.host @@ -336,11 +339,12 @@ class PrimitivusMessageDialog(PrimitivusDialog, xmlui.MessageDialog, sat_widgets.Alert): - def __init__(self, _xmlui_parent, title, message, level): PrimitivusDialog.__init__(self, _xmlui_parent) xmlui.MessageDialog.__init__(self, _xmlui_parent) - sat_widgets.Alert.__init__(self, title, message, ok_cb=lambda dummy: self._xmluiClose()) + sat_widgets.Alert.__init__( + self, title, message, ok_cb=lambda dummy: self._xmluiClose() + ) class PrimitivusNoteDialog(xmlui.NoteDialog, PrimitivusMessageDialog): @@ -348,41 +352,51 @@ pass -class PrimitivusConfirmDialog(PrimitivusDialog, xmlui.ConfirmDialog, sat_widgets.ConfirmDialog): - +class PrimitivusConfirmDialog( + PrimitivusDialog, xmlui.ConfirmDialog, sat_widgets.ConfirmDialog +): def __init__(self, _xmlui_parent, title, message, level, buttons_set): PrimitivusDialog.__init__(self, _xmlui_parent) xmlui.ConfirmDialog.__init__(self, _xmlui_parent) - sat_widgets.ConfirmDialog.__init__(self, title, message, no_cb=lambda dummy: self._xmluiCancelled(), yes_cb=lambda dummy: self._xmluiValidated()) + sat_widgets.ConfirmDialog.__init__( + self, + title, + message, + no_cb=lambda dummy: self._xmluiCancelled(), + yes_cb=lambda dummy: self._xmluiValidated(), + ) -class PrimitivusFileDialog(PrimitivusDialog, xmlui.FileDialog, files_management.FileDialog): - +class PrimitivusFileDialog( + PrimitivusDialog, xmlui.FileDialog, files_management.FileDialog +): def __init__(self, _xmlui_parent, title, message, level, filetype): # TODO: message is not managed yet PrimitivusDialog.__init__(self, _xmlui_parent) xmlui.FileDialog.__init__(self, _xmlui_parent) style = [] if filetype == C.XMLUI_DATA_FILETYPE_DIR: - style.append('dir') - files_management.FileDialog.__init__(self, - ok_cb=lambda path: self._xmluiValidated({'path': path}), + style.append("dir") + files_management.FileDialog.__init__( + self, + ok_cb=lambda path: self._xmluiValidated({"path": path}), cancel_cb=lambda dummy: self._xmluiCancelled(), message=message, title=title, - style=style) + style=style, + ) class GenericFactory(object): - def __getattr__(self, attr): if attr.startswith("create"): - cls = globals()["Primitivus" + attr[6:]] # XXX: we prefix with "Primitivus" to work around an Urwid bug, WidgetMeta in Urwid don't manage multiple inheritance with same names + cls = globals()[ + "Primitivus" + attr[6:] + ] # XXX: we prefix with "Primitivus" to work around an Urwid bug, WidgetMeta in Urwid don't manage multiple inheritance with same names return cls class WidgetFactory(GenericFactory): - def __getattr__(self, attr): if attr.startswith("create"): cls = GenericFactory.__getattr__(self, attr) @@ -393,48 +407,66 @@ class XMLUIPanel(xmlui.XMLUIPanel, PrimitivusWidget): widget_factory = WidgetFactory() - def __init__(self, host, parsed_xml, title=None, flags=None, callback=None, ignore=None, whitelist=None, profile=C.PROF_KEY_NONE): + def __init__( + self, + host, + parsed_xml, + title=None, + flags=None, + callback=None, + ignore=None, + whitelist=None, + profile=C.PROF_KEY_NONE, + ): self.widget_factory._xmlui_main = self self._dest = None - xmlui.XMLUIPanel.__init__(self, - host, - parsed_xml, - title = title, - flags = flags, - callback = callback, - ignore = ignore, - profile = profile) + xmlui.XMLUIPanel.__init__( + self, + host, + parsed_xml, + title=title, + flags=flags, + callback=callback, + ignore=ignore, + profile=profile, + ) PrimitivusWidget.__init__(self, self.main_cont, self.xmlui_title) def constructUI(self, parsed_dom): def postTreat(): assert self.main_cont.body - if self.type in ('form', 'popup'): + if self.type in ("form", "popup"): buttons = [] - if self.type == 'form': - buttons.append(urwid.Button(_('Submit'), self.onFormSubmitted)) - if not 'NO_CANCEL' in self.flags: - buttons.append(urwid.Button(_('Cancel'), self.onFormCancelled)) + if self.type == "form": + buttons.append(urwid.Button(_("Submit"), self.onFormSubmitted)) + if not "NO_CANCEL" in self.flags: + buttons.append(urwid.Button(_("Cancel"), self.onFormCancelled)) else: - buttons.append(urwid.Button(_('OK'), on_press=lambda dummy: self._xmluiClose())) + buttons.append( + urwid.Button(_("OK"), on_press=lambda dummy: self._xmluiClose()) + ) max_len = max([len(button.get_label()) for button in buttons]) - grid_wid = urwid.GridFlow(buttons, max_len + 4, 1, 0, 'center') + grid_wid = urwid.GridFlow(buttons, max_len + 4, 1, 0, "center") self.main_cont.body.append(grid_wid) - elif self.type == 'param': + elif self.type == "param": tabs_cont = self.main_cont.body[0].base_widget - assert isinstance(tabs_cont,sat_widgets.TabsContainer) + assert isinstance(tabs_cont, sat_widgets.TabsContainer) buttons = [] - buttons.append(sat_widgets.CustomButton(_('Save'),self.onSaveParams)) - buttons.append(sat_widgets.CustomButton(_('Cancel'),lambda x:self.host.removeWindow())) + buttons.append(sat_widgets.CustomButton(_("Save"), self.onSaveParams)) + buttons.append( + sat_widgets.CustomButton( + _("Cancel"), lambda x: self.host.removeWindow() + ) + ) max_len = max([button.getSize() for button in buttons]) - grid_wid = urwid.GridFlow(buttons,max_len,1,0,'center') + grid_wid = urwid.GridFlow(buttons, max_len, 1, 0, "center") tabs_cont.addFooter(grid_wid) xmlui.XMLUIPanel.constructUI(self, parsed_dom, postTreat) urwid.WidgetWrap.__init__(self, self.main_cont) - def show(self, show_type=None, valign='middle'): + def show(self, show_type=None, valign="middle"): """Show the constructed UI @param show_type: how to show the UI: - None (follow XMLUI's recommendation) @@ -445,30 +477,32 @@ """ if show_type is None: - if self.type in ('window', 'param'): - show_type = 'window' - elif self.type in ('popup', 'form'): - show_type = 'popup' + if self.type in ("window", "param"): + show_type = "window" + elif self.type in ("popup", "form"): + show_type = "popup" - if show_type not in ('popup', 'window'): - raise ValueError('Invalid show_type [%s]' % show_type) + if show_type not in ("popup", "window"): + raise ValueError("Invalid show_type [%s]" % show_type) self._dest = show_type - if show_type == 'popup': + if show_type == "popup": self.host.showPopUp(self, valign=valign) - elif show_type == 'window': + elif show_type == "window": self.host.newWidget(self) else: - assert False + assert False self.host.redraw() def _xmluiClose(self): - if self._dest == 'window': + if self._dest == "window": self.host.removeWindow() - elif self._dest == 'popup': + elif self._dest == "popup": self.host.removePopUp(self) else: - raise exceptions.InternalError("self._dest unknown, are you sure you have called XMLUI.show ?") + raise exceptions.InternalError( + "self._dest unknown, are you sure you have called XMLUI.show ?" + ) class XMLUIDialog(xmlui.XMLUIDialog):
--- a/sat_frontends/quick_frontend/constants.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/quick_frontend/constants.py Wed Jun 27 20:14:46 2018 +0200 @@ -24,11 +24,15 @@ class Const(constants.Const): - PRESENCE = OrderedDict([("", _("Online")), - ("chat", _("Free for chat")), - ("away", _("Away from keyboard")), - ("dnd", _("Do not disturb")), - ("xa", _("Extended away"))]) + PRESENCE = OrderedDict( + [ + ("", _("Online")), + ("chat", _("Free for chat")), + ("away", _("Away from keyboard")), + ("dnd", _("Do not disturb")), + ("xa", _("Extended away")), + ] + ) # from plugin_misc_text_syntaxes SYNTAX_XHTML = "XHTML" @@ -42,27 +46,32 @@ XMLUI_STATUS_CANCELLED = constants.Const.XMLUI_DATA_CANCELLED # Roster - CONTACT_GROUPS = 'groups' - CONTACT_RESOURCES = 'resources' - CONTACT_MAIN_RESOURCE = 'main_resource' - CONTACT_SPECIAL = 'special' - CONTACT_SPECIAL_GROUP = 'group' # group chat special entity - CONTACT_SELECTED = 'selected' + CONTACT_GROUPS = "groups" + CONTACT_RESOURCES = "resources" + CONTACT_MAIN_RESOURCE = "main_resource" + CONTACT_SPECIAL = "special" + CONTACT_SPECIAL_GROUP = "group" # group chat special entity + CONTACT_SELECTED = "selected" # used in handler to track where the contact is coming from - CONTACT_PROFILE = 'profile' + CONTACT_PROFILE = "profile" CONTACT_SPECIAL_ALLOWED = (CONTACT_SPECIAL_GROUP,) # allowed values for special flag # set of forbidden names for contact data - CONTACT_DATA_FORBIDDEN = {CONTACT_GROUPS, CONTACT_RESOURCES, CONTACT_MAIN_RESOURCE, - CONTACT_SELECTED, CONTACT_PROFILE} + CONTACT_DATA_FORBIDDEN = { + CONTACT_GROUPS, + CONTACT_RESOURCES, + CONTACT_MAIN_RESOURCE, + CONTACT_SELECTED, + CONTACT_PROFILE, + } # Chats CHAT_STATE_ICON = { "": u" ", - "active": u'✔', - "inactive": u'☄', - "gone": u'✈', - "composing": u'✎', - "paused": u"…" + "active": u"✔", + "inactive": u"☄", + "gone": u"✈", + "composing": u"✎", + "paused": u"…", } # Blogs @@ -72,26 +81,36 @@ # 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' + WIDGET_NEW = "NEW" + WIDGET_KEEP = "KEEP" + WIDGET_RAISE = "RAISE" + WIDGET_RECREATE = "RECREATE" # Updates (generic) - UPDATE_DELETE = 'DELETE' - UPDATE_MODIFY = 'MODIFY' - UPDATE_ADD = 'ADD' - UPDATE_SELECTION = 'SELECTION' + UPDATE_DELETE = "DELETE" + UPDATE_MODIFY = "MODIFY" + UPDATE_ADD = "ADD" + UPDATE_SELECTION = "SELECTION" # high level update (i.e. not item level but organisation of items) - UPDATE_STRUCTURE = 'STRUCTURE' + UPDATE_STRUCTURE = "STRUCTURE" - LISTENERS = {'avatar', 'nick', 'presence', 'profilePlugged', 'disconnect', 'gotMenus', - 'menu', 'notification', 'notificationsClear', 'progressFinished', - 'progressError'} + LISTENERS = { + "avatar", + "nick", + "presence", + "profilePlugged", + "disconnect", + "gotMenus", + "menu", + "notification", + "notificationsClear", + "progressFinished", + "progressError", + } # Notifications - NOTIFY_MESSAGE = 'MESSAGE' # a message has been received - NOTIFY_MENTION = 'MENTION' # user has been mentionned - NOTIFY_PROGRESS_END = 'PROGRESS_END' # a progression has finised - NOTIFY_GENERIC = 'GENERIC' # a notification which has not its own type + NOTIFY_MESSAGE = "MESSAGE" # a message has been received + NOTIFY_MENTION = "MENTION" # user has been mentionned + NOTIFY_PROGRESS_END = "PROGRESS_END" # a progression has finised + NOTIFY_GENERIC = "GENERIC" # a notification which has not its own type NOTIFY_ALL = (NOTIFY_MESSAGE, NOTIFY_MENTION, NOTIFY_PROGRESS_END, NOTIFY_GENERIC)
--- a/sat_frontends/quick_frontend/quick_app.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/quick_frontend/quick_app.py Wed Jun 27 20:14:46 2018 +0200 @@ -18,6 +18,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.core.log import getLogger + log = getLogger(__name__) from sat.core.i18n import _ @@ -39,13 +40,17 @@ try: # FIXME: to be removed when an acceptable solution is here - unicode('') # XXX: unicode doesn't exist in pyjamas -except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options + unicode("") # XXX: unicode doesn't exist in pyjamas +except ( + TypeError, + AttributeError, +): # Error raised is not the same depending on pyjsbuild options unicode = str class ProfileManager(object): """Class managing all data relative to one profile, and plugging in mechanism""" + # TODO: handle waiting XMLUI requests: getWaitingConf doesn't exist anymore # and a way to keep some XMLUI request between sessions is expected in backend host = None @@ -69,8 +74,13 @@ def plug(self): """Plug the profile to the host""" # we get the essential params - self.bridge.asyncGetParamA("JabberID", "Connection", profile_key=self.profile, - callback=self._plug_profile_jid, errback=self._getParamError) + self.bridge.asyncGetParamA( + "JabberID", + "Connection", + profile_key=self.profile, + callback=self._plug_profile_jid, + errback=self._getParamError, + ) def _plug_profile_jid(self, jid_s): self.whoami = jid.JID(jid_s) # resource might change after the connection @@ -78,24 +88,40 @@ def _autodisconnectEb(self, failure_): # XXX: we ignore error on this parameter, as Libervia can't access it - log.warning(_("Error while trying to get autodisconnect param, ignoring: {}").format(failure_)) + log.warning( + _("Error while trying to get autodisconnect param, ignoring: {}").format( + failure_ + ) + ) self._plug_profile_autodisconnect("false") def _plug_profile_isconnected(self, connected): self.connected = connected - self.bridge.asyncGetParamA("autodisconnect", "Connection", profile_key=self.profile, - callback=self._plug_profile_autodisconnect, errback=self._autodisconnectEb) + self.bridge.asyncGetParamA( + "autodisconnect", + "Connection", + profile_key=self.profile, + callback=self._plug_profile_autodisconnect, + errback=self._autodisconnectEb, + ) def _plug_profile_autodisconnect(self, autodisconnect): if C.bool(autodisconnect): self._autodisconnect = True - self.bridge.asyncGetParamA("autoconnect", "Connection", profile_key=self.profile, - callback=self._plug_profile_autoconnect, errback=self._getParamError) + self.bridge.asyncGetParamA( + "autoconnect", + "Connection", + profile_key=self.profile, + callback=self._plug_profile_autoconnect, + errback=self._getParamError, + ) def _plug_profile_autoconnect(self, value_str): autoconnect = C.bool(value_str) if autoconnect and not self.connected: - self.host.connect(self.profile, callback=lambda dummy: self._plug_profile_afterconnect()) + self.host.connect( + self.profile, callback=lambda dummy: self._plug_profile_afterconnect() + ) else: self._plug_profile_afterconnect() @@ -103,7 +129,11 @@ # Profile can be connected or not # we get cached data self.connected = True - self.host.bridge.getFeatures(profile_key=self.profile, callback=self._plug_profile_getFeaturesCb, errback=self._plug_profile_getFeaturesEb) + self.host.bridge.getFeatures( + profile_key=self.profile, + callback=self._plug_profile_getFeaturesCb, + errback=self._plug_profile_getFeaturesEb, + ) def _plug_profile_getFeaturesEb(self, failure): log.error(u"Couldn't get features: {}".format(failure)) @@ -129,42 +159,60 @@ self.host.entityDataUpdatedHandler(entity_s, key, value, self.profile) if not self.connected: - self.host.setPresenceStatus(C.PRESENCE_UNAVAILABLE, '', profile=self.profile) + self.host.setPresenceStatus(C.PRESENCE_UNAVAILABLE, "", profile=self.profile) else: contact_list.fill() self.host.setPresenceStatus(profile=self.profile) - #The waiting subscription requests - self.bridge.getWaitingSub(self.profile, callback=self._plug_profile_gotWaitingSub) + # The waiting subscription requests + self.bridge.getWaitingSub( + self.profile, callback=self._plug_profile_gotWaitingSub + ) def _plug_profile_gotWaitingSub(self, waiting_sub): for sub in waiting_sub: self.host.subscribeHandler(waiting_sub[sub], sub, self.profile) - self.bridge.mucGetRoomsJoined(self.profile, callback=self._plug_profile_gotRoomsJoined) + self.bridge.mucGetRoomsJoined( + self.profile, callback=self._plug_profile_gotRoomsJoined + ) def _plug_profile_gotRoomsJoined(self, rooms_args): - #Now we open the MUC window where we already are: + # Now we open the MUC window where we already are: for room_args in rooms_args: self.host.mucRoomJoinedHandler(*room_args, profile=self.profile) - #Presence must be requested after rooms are filled - self.host.bridge.getPresenceStatuses(self.profile, callback=self._plug_profile_gotPresences) + # Presence must be requested after rooms are filled + self.host.bridge.getPresenceStatuses( + self.profile, callback=self._plug_profile_gotPresences + ) def _plug_profile_gotPresences(self, presences): def gotEntityData(data, contact): - for key in ('avatar', 'nick'): + for key in ("avatar", "nick"): if key in data: - self.host.entityDataUpdatedHandler(contact, key, data[key], self.profile) + self.host.entityDataUpdatedHandler( + contact, key, data[key], self.profile + ) for contact in presences: for res in presences[contact]: - jabber_id = (u'%s/%s' % (jid.JID(contact).bare, res)) if res else contact + jabber_id = (u"%s/%s" % (jid.JID(contact).bare, res)) if res else contact show = presences[contact][res][0] priority = presences[contact][res][1] statuses = presences[contact][res][2] - self.host.presenceUpdateHandler(jabber_id, show, priority, statuses, self.profile) - self.host.bridge.getEntityData(contact, ['avatar', 'nick'], self.profile, callback=lambda data, contact=contact: gotEntityData(data, contact), errback=lambda failure, contact=contact: log.debug(u"No cache data for {}".format(contact))) + self.host.presenceUpdateHandler( + jabber_id, show, priority, statuses, self.profile + ) + self.host.bridge.getEntityData( + contact, + ["avatar", "nick"], + self.profile, + callback=lambda data, contact=contact: gotEntityData(data, contact), + errback=lambda failure, contact=contact: log.debug( + u"No cache data for {}".format(contact) + ), + ) # At this point, profile should be fully plugged # and we launch frontend specific method @@ -200,13 +248,15 @@ def plug(self, profile): if profile in self._profiles: - raise exceptions.ConflictError('A profile of the name [{}] is already plugged'.format(profile)) + raise exceptions.ConflictError( + "A profile of the name [{}] is already plugged".format(profile) + ) self._profiles[profile] = ProfileManager(profile) self._profiles[profile].plug() def unplug(self, profile): if profile not in self._profiles: - raise ValueError('The profile [{}] is not plugged'.format(profile)) + raise ValueError("The profile [{}] is not plugged".format(profile)) # remove the contact list and its listener host = self._profiles[profile].host @@ -220,6 +270,7 @@ class QuickApp(object): """This class contain the main methods needed for the frontend""" + MB_HANDLER = True # Set to False if the frontend doesn't manage microblog AVATARS_HANDLER = True # set to False if avatars are not used @@ -234,9 +285,11 @@ self.menus = quick_menus.QuickMenusManager(self) ProfileManager.host = self self.profiles = ProfilesManager() - self._plugs_in_progress = set() # profiles currently being plugged, used to (un)lock contact list updates - self.ready_profiles = set() # profiles which are connected and ready - self.signals_cache = {} # used to keep signal received between start of plug_profile and when the profile is actualy ready + self._plugs_in_progress = ( + set() + ) # profiles currently being plugged, used to (un)lock contact list updates + self.ready_profiles = set() # profiles which are connected and ready + self.signals_cache = {} # used to keep signal received between start of plug_profile and when the profile is actualy ready self.contact_lists = quick_contact_list.QuickContactListHandler(self) self.widgets = quick_widgets.QuickWidgetsManager(self) if check_options is not None: @@ -245,13 +298,17 @@ self.options = None # widgets - self.selected_widget = None # widget currently selected (must be filled by frontend) + self.selected_widget = ( + None + ) # widget currently selected (must be filled by frontend) # listeners - self._listeners = {} # key: listener type ("avatar", "selected", etc), value: list of callbacks + self._listeners = {} # key: listener type ("avatar", "selected", etc), value: list of callbacks # triggers - self.trigger = trigger.TriggerManager() # trigger are used to change the default behaviour + self.trigger = ( + trigger.TriggerManager() + ) # trigger are used to change the default behaviour ## bridge ## self.bridge = bridge_factory() @@ -274,8 +331,9 @@ log.error(_(u"Can't get namespaces map: {msg}").format(msg=failure_)) def onBridgeConnected(self): - self.bridge.namespacesGet(callback=self._namespacesGetCb, - errback=self._namespacesGetEb) + self.bridge.namespacesGet( + callback=self._namespacesGetCb, errback=self._namespacesGetEb + ) def _bridgeCb(self): self.registerSignal("connected") @@ -331,7 +389,9 @@ """ raise NotImplementedError - def registerSignal(self, function_name, handler=None, iface="core", with_profile=True): + def registerSignal( + self, function_name, handler=None, iface="core", with_profile=True + ): """Register a handler for a signal @param function_name (str): name of the signal to handle @@ -339,15 +399,15 @@ @param iface (str): interface of the bridge to use ('core' or 'plugin') @param with_profile (boolean): True if the signal concerns a specific profile, in that case the profile name has to be passed by the caller """ - log.debug(u"registering signal {name}".format(name = function_name)) + log.debug(u"registering signal {name}".format(name=function_name)) if handler is None: - handler = getattr(self, "{}{}".format(function_name, 'Handler')) + handler = getattr(self, "{}{}".format(function_name, "Handler")) if not with_profile: self.bridge.register_signal(function_name, handler, iface) return def signalReceived(*args, **kwargs): - profile = kwargs.get('profile') + profile = kwargs.get("profile") if profile is None: if not args: raise exceptions.ProfileNotSetError @@ -356,9 +416,12 @@ if not self.check_profile(profile): if profile in self.profiles: # profile is not ready but is in self.profiles, that's mean that it's being connecting and we need to cache the signal - self.signals_cache.setdefault(profile, []).append((function_name, handler, args, kwargs)) + self.signals_cache.setdefault(profile, []).append( + (function_name, handler, args, kwargs) + ) return # we ignore signal for profiles we don't manage handler(*args, **kwargs) + self.bridge.register_signal(function_name, signalReceived, iface) def addListener(self, type_, callback, profiles_filter=None): @@ -449,10 +512,13 @@ # profile is ready, we can call send signals that where is cache cached_signals = self.signals_cache.pop(profile, []) for function_name, handler, args, kwargs in cached_signals: - log.debug(u"Calling cached signal [%s] with args %s and kwargs %s" % (function_name, args, kwargs)) + log.debug( + u"Calling cached signal [%s] with args %s and kwargs %s" + % (function_name, args, kwargs) + ) handler(*args, **kwargs) - self.callListeners('profilePlugged', profile=profile) + self.callListeners("profilePlugged", profile=profile) if not self._plugs_in_progress: self.contact_lists.lockUpdate(False) @@ -460,24 +526,29 @@ if not callback: callback = lambda dummy: None if not errback: + def errback(failure): log.error(_(u"Can't connect profile [%s]") % failure) try: module = failure.module except AttributeError: - module = '' + module = "" try: message = failure.message except AttributeError: - message = 'error' + message = "error" try: fullname = failure.fullname except AttributeError: - fullname = 'error' - if module.startswith('twisted.words.protocols.jabber') and failure.condition == "not-authorized": + fullname = "error" + if ( + module.startswith("twisted.words.protocols.jabber") + and failure.condition == "not-authorized" + ): self.launchAction(C.CHANGE_XMPP_PASSWD_ID, {}, profile=profile) else: - self.showDialog(message, fullname, 'error') + self.showDialog(message, fullname, "error") + self.bridge.connect(profile, callback=callback, errback=errback) def plug_profiles(self, profiles): @@ -527,7 +598,7 @@ """called when the connection is closed""" log.debug(_("Disconnected")) self.contact_lists[profile].disconnect() - self.setPresenceStatus(C.PRESENCE_UNAVAILABLE, '', profile=profile) + self.setPresenceStatus(C.PRESENCE_UNAVAILABLE, "", profile=profile) def actionNewHandler(self, action_data, id_, security_limit, profile): self.actionManager(action_data, user_action=False, profile=profile) @@ -537,10 +608,23 @@ groups = list(groups) self.contact_lists[profile].setContact(entity, groups, attributes, in_roster=True) - def messageNewHandler(self, uid, timestamp, from_jid_s, to_jid_s, msg, subject, type_, extra, profile): + def messageNewHandler( + self, uid, timestamp, from_jid_s, to_jid_s, msg, subject, type_, extra, profile + ): from_jid = jid.JID(from_jid_s) to_jid = jid.JID(to_jid_s) - if not self.trigger.point("messageNewTrigger", uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile=profile): + if not self.trigger.point( + "messageNewTrigger", + uid, + timestamp, + from_jid, + to_jid, + msg, + subject, + type_, + extra, + profile=profile, + ): return from_me = from_jid.bare == self.profiles[profile].whoami.bare @@ -550,66 +634,137 @@ # we avoid resource locking, but we must keep resource for private MUC messages target = target.bare # we want to be sure to have at least one QuickChat instance - self.widgets.getOrCreateWidget(quick_chat.QuickChat, target, type_=C.CHAT_ONE2ONE, on_new_widget=None, profile=profile) + self.widgets.getOrCreateWidget( + quick_chat.QuickChat, + target, + type_=C.CHAT_ONE2ONE, + on_new_widget=None, + profile=profile, + ) - if not from_jid in contact_list and from_jid.bare != self.profiles[profile].whoami.bare: - #XXX: needed to show entities which haven't sent any + if ( + not from_jid in contact_list + and from_jid.bare != self.profiles[profile].whoami.bare + ): + # XXX: needed to show entities which haven't sent any # presence information and which are not in roster contact_list.setContact(from_jid) # we dispatch the message in the widgets - for widget in self.widgets.getWidgets(quick_chat.QuickChat, target=target, profiles=(profile,)): - widget.messageNew(uid, timestamp, from_jid, target, msg, subject, type_, extra, profile) + for widget in self.widgets.getWidgets( + quick_chat.QuickChat, target=target, profiles=(profile,) + ): + widget.messageNew( + uid, timestamp, from_jid, target, msg, subject, type_, extra, profile + ) def messageStateHandler(self, uid, status, profile): for widget in self.widgets.getWidgets(quick_chat.QuickChat, profiles=(profile,)): widget.onMessageState(uid, status, profile) - def messageSend(self, to_jid, message, subject=None, mess_type="auto", extra=None, callback=None, errback=None, profile_key=C.PROF_KEY_NONE): + def messageSend( + self, + to_jid, + message, + subject=None, + mess_type="auto", + extra=None, + callback=None, + errback=None, + profile_key=C.PROF_KEY_NONE, + ): if subject is None: subject = {} if extra is None: extra = {} if callback is None: - callback = lambda dummy=None: None # FIXME: optional argument is here because pyjamas doesn't support callback without arg with json proxy + callback = ( + lambda dummy=None: None + ) # FIXME: optional argument is here because pyjamas doesn't support callback without arg with json proxy if errback is None: - errback = lambda failure: self.showDialog(failure.fullname, failure.message, "error") + errback = lambda failure: self.showDialog( + failure.fullname, failure.message, "error" + ) - if not self.trigger.point("messageSendTrigger", to_jid, message, subject, mess_type, extra, callback, errback, profile_key=profile_key): + if not self.trigger.point( + "messageSendTrigger", + to_jid, + message, + subject, + mess_type, + extra, + callback, + errback, + profile_key=profile_key, + ): return - self.bridge.messageSend(unicode(to_jid), message, subject, mess_type, extra, profile_key, callback=callback, errback=errback) + self.bridge.messageSend( + unicode(to_jid), + message, + subject, + mess_type, + extra, + profile_key, + callback=callback, + errback=errback, + ) - def setPresenceStatus(self, show='', status=None, profile=C.PROF_KEY_NONE): + def setPresenceStatus(self, show="", status=None, profile=C.PROF_KEY_NONE): raise NotImplementedError def presenceUpdateHandler(self, entity_s, show, priority, statuses, profile): - log.debug(_(u"presence update for %(entity)s (show=%(show)s, priority=%(priority)s, statuses=%(statuses)s) [profile:%(profile)s]") - % {'entity': entity_s, C.PRESENCE_SHOW: show, C.PRESENCE_PRIORITY: priority, C.PRESENCE_STATUSES: statuses, 'profile': profile}) + log.debug( + _( + u"presence update for %(entity)s (show=%(show)s, priority=%(priority)s, statuses=%(statuses)s) [profile:%(profile)s]" + ) + % { + "entity": entity_s, + C.PRESENCE_SHOW: show, + C.PRESENCE_PRIORITY: priority, + C.PRESENCE_STATUSES: statuses, + "profile": profile, + } + ) entity = jid.JID(entity_s) if entity == self.profiles[profile].whoami: if show == C.PRESENCE_UNAVAILABLE: - self.setPresenceStatus(C.PRESENCE_UNAVAILABLE, '', profile=profile) + self.setPresenceStatus(C.PRESENCE_UNAVAILABLE, "", profile=profile) else: # FIXME: try to retrieve user language status before fallback to default status = statuses.get(C.PRESENCE_STATUSES_DEFAULT, None) self.setPresenceStatus(show, status, profile=profile) return - self.callListeners('presence', entity, show, priority, statuses, profile=profile) + self.callListeners("presence", entity, show, priority, statuses, profile=profile) def mucRoomJoinedHandler(self, room_jid_s, occupants, user_nick, subject, profile): """Called when a MUC room is joined""" - log.debug(u"Room [{room_jid}] joined by {profile}, users presents:{users}".format(room_jid=room_jid_s, profile=profile, users=occupants.keys())) + log.debug( + u"Room [{room_jid}] joined by {profile}, users presents:{users}".format( + room_jid=room_jid_s, profile=profile, users=occupants.keys() + ) + ) room_jid = jid.JID(room_jid_s) - self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, nick=user_nick, occupants=occupants, subject=subject, profile=profile) + self.widgets.getOrCreateWidget( + quick_chat.QuickChat, + room_jid, + type_=C.CHAT_GROUP, + nick=user_nick, + occupants=occupants, + subject=subject, + profile=profile, + ) self.contact_lists[profile].setSpecial(room_jid, C.CONTACT_SPECIAL_GROUP) # chat_widget.update() def mucRoomLeftHandler(self, room_jid_s, profile): """Called when a MUC room is left""" - log.debug(u"Room [%(room_jid)s] left by %(profile)s" % {'room_jid': room_jid_s, 'profile': profile}) + log.debug( + u"Room [%(room_jid)s] left by %(profile)s" + % {"room_jid": room_jid_s, "profile": profile} + ) room_jid = jid.JID(room_jid_s) chat_widget = self.widgets.getWidget(quick_chat.QuickChat, room_jid, profile) if chat_widget: @@ -619,16 +774,26 @@ def mucRoomUserChangedNickHandler(self, room_jid_s, old_nick, new_nick, profile): """Called when an user joined a MUC room""" room_jid = jid.JID(room_jid_s) - chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile) + chat_widget = self.widgets.getOrCreateWidget( + quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile + ) chat_widget.changeUserNick(old_nick, new_nick) - log.debug(u"user [%(old_nick)s] is now known as [%(new_nick)s] in room [%(room_jid)s]" % {'old_nick': old_nick, 'new_nick': new_nick, 'room_jid': room_jid}) + log.debug( + u"user [%(old_nick)s] is now known as [%(new_nick)s] in room [%(room_jid)s]" + % {"old_nick": old_nick, "new_nick": new_nick, "room_jid": room_jid} + ) def mucRoomNewSubjectHandler(self, room_jid_s, subject, profile): """Called when subject of MUC room change""" room_jid = jid.JID(room_jid_s) - chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile) + chat_widget = self.widgets.getOrCreateWidget( + quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile + ) chat_widget.setSubject(subject) - log.debug(u"new subject for room [%(room_jid)s]: %(subject)s" % {'room_jid': room_jid, "subject": subject}) + log.debug( + u"new subject for room [%(room_jid)s]: %(subject)s" + % {"room_jid": room_jid, "subject": subject} + ) def chatStateReceivedHandler(self, from_jid_s, state, profile): """Called when a new chat state (XEP-0085) is received. @@ -641,7 +806,17 @@ for widget in self.widgets.getWidgets(quick_chat.QuickChat, profiles=(profile,)): widget.onChatState(from_jid, state, profile) - def notify(self, type_, entity=None, message=None, subject=None, callback=None, cb_args=None, widget=None, profile=C.PROF_KEY_NONE): + def notify( + self, + type_, + entity=None, + message=None, + subject=None, + callback=None, + cb_args=None, + widget=None, + profile=C.PROF_KEY_NONE, + ): """Trigger an event notification @param type_(unicode): notifation kind, @@ -656,22 +831,22 @@ """ assert type_ in C.NOTIFY_ALL notif_dict = self.profiles[profile].notifications - key = '' if entity is None else entity.bare + key = "" if entity is None else entity.bare type_notifs = notif_dict.setdefault(key, {}).setdefault(type_, []) notif_data = { - 'id': self._notif_id, - 'time': time.time(), - 'entity': entity, - 'callback': callback, - 'cb_args': cb_args, - 'message': message, - 'subject': subject, - } + "id": self._notif_id, + "time": time.time(), + "entity": entity, + "callback": callback, + "cb_args": cb_args, + "message": message, + "subject": subject, + } if widget is not None: notif_data[widget] = widget type_notifs.append(notif_data) self._notifications[self._notif_id] = notif_data - self.callListeners('notification', entity, notif_data, profile=profile) + self.callListeners("notification", entity, notif_data, profile=profile) def getNotifs(self, entity=None, type_=None, exact_jid=None, profile=C.PROF_KEY_NONE): """return notifications for given entity @@ -696,7 +871,7 @@ exact_jid = False else: if entity is None: - key = '' + key = "" exact_jid = False else: key = entity.bare @@ -713,7 +888,7 @@ for notifs in type_notifs: for notif in notifs: - if exact_jid and notif['entity'] != entity: + if exact_jid and notif["entity"] != entity: continue yield notif @@ -727,7 +902,7 @@ @return (list[dict]): list of notifications """ notif_dict = self.profiles[profile].notifications - key = '' if entity is None else entity.bare + key = "" if entity is None else entity.bare try: if type_ is None: del notif_dict[key] @@ -735,7 +910,7 @@ del notif_dict[key][type_] except KeyError: return - self.callListeners('notificationsClear', entity, type_, profile=profile) + self.callListeners("notificationsClear", entity, type_, profile=profile) def psEventHandler(self, category, service_s, node, event_type, data, profile): """Called when a PubSub event is received. @@ -750,23 +925,35 @@ if category == C.PS_MICROBLOG and self.MB_HANDLER: if event_type == C.PS_PUBLISH: - if not 'content' in data: + if not "content" in data: log.warning("No content found in microblog data") return - _groups = set(data_format.dict2iter('group', data)) or None # FIXME: check if [] make sense (instead of None) + _groups = ( + set(data_format.dict2iter("group", data)) or None + ) # FIXME: check if [] make sense (instead of None) for wid in self.widgets.getWidgets(quick_blog.QuickBlog): wid.addEntryIfAccepted(service_s, node, data, _groups, profile) try: - comments_node, comments_service = data['comments_node'], data['comments_service'] + comments_node, comments_service = ( + data["comments_node"], + data["comments_service"], + ) except KeyError: pass else: - self.bridge.mbGet(comments_service, comments_node, C.NO_LIMIT, [], {"subscribe":C.BOOL_TRUE}, profile=profile) + self.bridge.mbGet( + comments_service, + comments_node, + C.NO_LIMIT, + [], + {"subscribe": C.BOOL_TRUE}, + profile=profile, + ) elif event_type == C.PS_RETRACT: for wid in self.widgets.getWidgets(quick_blog.QuickBlog): - wid.deleteEntryIfPresent(service_s, node, data['id'], profile) + wid.deleteEntryIfPresent(service_s, node, data["id"], profile) pass else: log.warning("Unmanaged PubSub event type {}".format(event_type)) @@ -776,11 +963,11 @@ def progressFinishedHandler(self, pid, metadata, profile): log.info(u"Progress {} finished".format(pid)) - self.callListeners('progressFinished', pid, metadata, profile=profile) + self.callListeners("progressFinished", pid, metadata, profile=profile) def progressErrorHandler(self, pid, err_msg, profile): log.warning(u"Progress {pid} error: {err_msg}".format(pid=pid, err_msg=err_msg)) - self.callListeners('progressError', pid, err_msg, profile=profile) + self.callListeners("progressError", pid, err_msg, profile=profile) def _subscribe_cb(self, answer, data): entity, profile = data @@ -793,23 +980,34 @@ if type == "subscribed": # this is a subscription confirmation, we just have to inform user # TODO: call self.getEntityMBlog to add the new contact blogs - self.showDialog(_(u"The contact {contact} has accepted your subscription") - .format(contact=entity.bare), _(u'Subscription confirmation')) + self.showDialog( + _(u"The contact {contact} has accepted your subscription").format( + contact=entity.bare + ), + _(u"Subscription confirmation"), + ) elif type == "unsubscribed": # this is a subscription refusal, we just have to inform user - self.showDialog(_(u"The contact {contact} has refused your subscription") - .format(contact=entity.bare), - _(u'Subscription refusal'), - 'error') + self.showDialog( + _(u"The contact {contact} has refused your subscription").format( + contact=entity.bare + ), + _(u"Subscription refusal"), + "error", + ) elif type == "subscribe": # this is a subscriptionn request, we have to ask for user confirmation # TODO: use sat.stdui.ui_contact_list to display the groups selector - self.showDialog(_(u"The contact {contact} wants to subscribe to your presence" - u".\nDo you accept ?").format(contact=entity.bare), - _('Subscription confirmation'), - 'yes/no', - answer_cb=self._subscribe_cb, - answer_data=(entity, profile)) + self.showDialog( + _( + u"The contact {contact} wants to subscribe to your presence" + u".\nDo you accept ?" + ).format(contact=entity.bare), + _("Subscription confirmation"), + "yes/no", + answer_cb=self._subscribe_cb, + answer_data=(entity, profile), + ) def showDialog(self, message, title, type="info", answer_cb=None, answer_data=None): """Show a dialog to user @@ -832,7 +1030,7 @@ def showAlert(self, message): # FIXME: doesn't seems used anymore, to remove? - pass #FIXME + pass # FIXME def dialogFailure(self, failure): log.warning(u"Failure: {}".format(failure)) @@ -849,13 +1047,16 @@ raise NotImplementedError def paramUpdateHandler(self, name, value, namespace, profile): - log.debug(_(u"param update: [%(namespace)s] %(name)s = %(value)s") % {'namespace': namespace, 'name': name, 'value': value}) + log.debug( + _(u"param update: [%(namespace)s] %(name)s = %(value)s") + % {"namespace": namespace, "name": name, "value": value} + ) if (namespace, name) == ("Connection", "JabberID"): log.debug(_(u"Changing JID to %s") % value) self.profiles[profile].whoami = jid.JID(value) - elif (namespace, name) == ('General', C.SHOW_OFFLINE_CONTACTS): + elif (namespace, name) == ("General", C.SHOW_OFFLINE_CONTACTS): self.contact_lists[profile].showOfflineContacts(C.bool(value)) - elif (namespace, name) == ('General', C.SHOW_EMPTY_GROUPS): + elif (namespace, name) == ("General", C.SHOW_EMPTY_GROUPS): self.contact_lists[profile].showEmptyGroups(C.bool(value)) def contactDeletedHandler(self, jid_s, profile): @@ -866,13 +1067,20 @@ entity = jid.JID(entity_s) if key == "nick": # this is the roster nick, not the MUC nick if entity in self.contact_lists[profile]: - self.contact_lists[profile].setCache(entity, 'nick', value) - self.callListeners('nick', entity, value, profile=profile) + self.contact_lists[profile].setCache(entity, "nick", value) + self.callListeners("nick", entity, value, profile=profile) elif key == "avatar" and self.AVATARS_HANDLER: if value and entity in self.contact_lists[profile]: self.getAvatar(entity, ignore_cache=True, profile=profile) - def actionManager(self, action_data, callback=None, ui_show_cb=None, user_action=True, profile=C.PROF_KEY_NONE): + def actionManager( + self, + action_data, + callback=None, + ui_show_cb=None, + user_action=True, + profile=C.PROF_KEY_NONE, + ): """Handle backend action @param action_data(dict): action dict as sent by launchAction or returned by an UI action @@ -882,29 +1090,40 @@ else the action come from backend direclty (i.e. actionNew) """ try: - xmlui = action_data.pop('xmlui') + xmlui = action_data.pop("xmlui") except KeyError: pass else: - ui = self.xmlui.create(self, xml_data=xmlui, flags=("FROM_BACKEND",) if not user_action else None, callback=callback, profile=profile) + ui = self.xmlui.create( + self, + xml_data=xmlui, + flags=("FROM_BACKEND",) if not user_action else None, + callback=callback, + profile=profile, + ) if ui_show_cb is None: ui.show() else: ui_show_cb(ui) try: - progress_id = action_data.pop('progress') + progress_id = action_data.pop("progress") except KeyError: pass else: self.progressIdHandler(progress_id, profile) # we ignore metadata - action_data = {k:v for k,v in action_data.iteritems() if not k.startswith("meta_")} + action_data = { + k: v for k, v in action_data.iteritems() if not k.startswith("meta_") + } if action_data: - raise exceptions.DataError(u"Not all keys in action_data are managed ({keys})".format(keys=', '.join(action_data.keys()))) - + 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: @@ -912,7 +1131,9 @@ else: callback(data=data, cb_id=callback_id, profile=profile) - def launchAction(self, callback_id, data=None, callback=None, profile=C.PROF_KEY_NONE): + def launchAction( + self, callback_id, data=None, callback=None, profile=C.PROF_KEY_NONE + ): """Launch a dynamic action @param callback_id: id of the action to launch @@ -929,9 +1150,19 @@ 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) + 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): + 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 @param menu_type(unicode): type of the menu to launch @@ -948,19 +1179,36 @@ """ 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) + 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) contact_list.setCache(entity, "avatar", path) - self.callListeners('avatar', entity, path, profile=profile) + self.callListeners("avatar", entity, path, profile=profile) def _avatarGetEb(self, failure, entity, contact_list): log.warning(u"Can't get avatar: {}".format(failure)) contact_list.setCache(entity, "avatar", self.getDefaultAvatar(entity)) - def getAvatar(self, entity, cache_only=True, hash_only=False, ignore_cache=False, profile=C.PROF_KEY_NONE): + def getAvatar( + self, + entity, + cache_only=True, + hash_only=False, + ignore_cache=False, + profile=C.PROF_KEY_NONE, + ): """return avatar path for an entity @param entity(jid.JID): entity to get avatar from @@ -982,8 +1230,11 @@ cache_only, hash_only, profile=profile, - callback=lambda path: self._avatarGetCb(path, entity, contact_list, profile), - errback=lambda failure: self._avatarGetEb(failure, entity, contact_list)) + callback=lambda path: self._avatarGetCb( + path, entity, contact_list, profile + ), + errback=lambda failure: self._avatarGetEb(failure, entity, contact_list), + ) # we set avatar to empty string to avoid requesting several time the same avatar # while we are waiting for avatarGet result contact_list.setCache(entity, "avatar", "") @@ -999,7 +1250,7 @@ def disconnect(self, profile): log.info("disconnecting") - self.callListeners('disconnect', profile=profile) + self.callListeners("disconnect", profile=profile) self.bridge.disconnect(profile) def onExit(self): @@ -1007,7 +1258,7 @@ to_unplug = [] for profile, profile_manager in self.profiles.iteritems(): if profile_manager.connected and profile_manager.autodisconnect: - #The user wants autodisconnection + # The user wants autodisconnection self.disconnect(profile) to_unplug.append(profile) for profile in to_unplug:
--- a/sat_frontends/quick_frontend/quick_blog.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/quick_frontend/quick_blog.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,6 +19,7 @@ # from sat.core.i18n import _, D_ from sat.core.log import getLogger + log = getLogger(__name__) @@ -29,8 +30,11 @@ try: # FIXME: to be removed when an acceptable solution is here - unicode('') # XXX: unicode doesn't exist in pyjamas -except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options + unicode("") # XXX: unicode doesn't exist in pyjamas +except ( + TypeError, + AttributeError, +): # Error raised is not the same depending on pyjsbuild options unicode = str ENTRY_CLS = None @@ -45,42 +49,46 @@ @param data(dict): microblog data as return by bridge methods if data values are not defined, set default values """ - self.id = data['id'] - self.title = data.get('title') + self.id = data["id"] + self.title = data.get("title") self.title_rich = None - self.title_xhtml = data.get('title_xhtml') - self.tags = list(data_format.dict2iter('tag', data)) - self.content = data.get('content') + self.title_xhtml = data.get("title_xhtml") + self.tags = list(data_format.dict2iter("tag", data)) + self.content = data.get("content") self.content_rich = None - self.content_xhtml = data.get('content_xhtml') - self.author = data['author'] + self.content_xhtml = data.get("content_xhtml") + self.author = data["author"] try: - author_jid = data['author_jid'] + author_jid = data["author_jid"] self.author_jid = jid.JID(author_jid) if author_jid else None except KeyError: self.author_jid = None try: - self.author_verified = C.bool(data['author_jid_verified']) + self.author_verified = C.bool(data["author_jid_verified"]) except KeyError: self.author_verified = False try: - self.updated = float(data['updated']) # XXX: int doesn't work here (pyjamas bug) + self.updated = float( + data["updated"] + ) # XXX: int doesn't work here (pyjamas bug) except KeyError: self.updated = None try: - self.published = float(data['published']) # XXX: int doesn't work here (pyjamas bug) + self.published = float( + data["published"] + ) # XXX: int doesn't work here (pyjamas bug) except KeyError: self.published = None - self.comments = data.get('comments') + self.comments = data.get("comments") try: - self.comments_service = jid.JID(data['comments_service']) + self.comments_service = jid.JID(data["comments_service"]) except KeyError: self.comments_service = None - self.comments_node = data.get('comments_node') + self.comments_node = data.get("comments_node") # def loadComments(self): # """Load all the comments""" @@ -140,7 +148,16 @@ for item, comments in items: self.addEntry(item, comments, service=service, node=node, with_update=False) - def addEntry(self, item=None, comments=None, service=None, node=None, with_update=True, editable=False, edit_entry=False): + def addEntry( + self, + item=None, + comments=None, + service=None, + node=None, + with_update=True, + editable=False, + edit_entry=False, + ): """Add a microblog entry @param editable (bool): True if the entry can be modified @@ -178,7 +195,9 @@ """Graphical representation of an Item This class must be overriden by frontends""" - def __init__(self, manager, item_data=None, comments_data=None, service=None, node=None): + def __init__( + self, manager, item_data=None, comments_data=None, service=None, node=None + ): """ @param blog(QuickBlog): the parent QuickBlog @param manager(EntriesManager): the parent EntriesManager @@ -194,7 +213,7 @@ self.blog.id2entries[self.item.id] = self if self.item.comments: node_tuple = (self.item.comments_service, self.item.comments_node) - self.blog.node2entries.setdefault(node_tuple,[]).append(self) + self.blog.node2entries.setdefault(node_tuple, []).append(self) def reset(self, item_data): """Reset the entry with given data @@ -205,17 +224,20 @@ """ if item_data is None: self.new = True - item_data = {'id': None, - # TODO: find a best author value - 'author': self.blog.host.whoami.node - } + item_data = { + "id": None, + # TODO: find a best author value + "author": self.blog.host.whoami.node, + } else: self.new = False self.item = Item(item_data) - self.author_jid = self.blog.host.whoami.bare if self.new else self.item.author_jid + self.author_jid = self.blog.host.whoami.bare if self.new else self.item.author_jid if self.author_jid is None and self.service and self.service.node: self.author_jid = self.service - self.mode = C.ENTRY_MODE_TEXT if self.item.content_xhtml is None else C.ENTRY_MODE_XHTML + self.mode = ( + C.ENTRY_MODE_TEXT if self.item.content_xhtml is None else C.ENTRY_MODE_XHTML + ) def refresh(self): """Refresh the display when data have been modified""" @@ -226,7 +248,7 @@ @param editable(bool): True if the entry can be edited """ - #XXX: we don't use @property as property setter doesn't play well with pyjamas + # XXX: we don't use @property as property setter doesn't play well with pyjamas raise NotImplementedError def addComments(self, comments_data): @@ -249,7 +271,7 @@ # keys to keep other than content*, title* and tag* # FIXME: see how to avoid comments node hijacking (someone could bind his post to another post's comments node) - keys_to_keep = ('id', 'comments', 'author', 'author_jid', 'published') + keys_to_keep = ("id", "comments", "author", "author_jid", "published") mb_data = {} for key in keys_to_keep: @@ -257,14 +279,14 @@ if value is not None: mb_data[key] = unicode(value) - for prefix in ('content', 'title'): - for suffix in ('', '_rich', '_xhtml'): - name = '{}{}'.format(prefix, suffix) + for prefix in ("content", "title"): + for suffix in ("", "_rich", "_xhtml"): + name = "{}{}".format(prefix, suffix) value = getattr(self.item, name) if value is not None: mb_data[name] = value - data_format.iter2dict('tag', self.item.tags, mb_data) + data_format.iter2dict("tag", self.item.tags, mb_data) if self.blog.new_message_target not in (C.PUBLIC, C.GROUP): raise NotImplementedError @@ -273,13 +295,14 @@ mb_data["allow_comments"] = C.BOOL_TRUE if self.blog.new_message_target == C.GROUP: - data_format.iter2dict('group', self.blog.targets, mb_data) + data_format.iter2dict("group", self.blog.targets, mb_data) self.blog.host.bridge.mbSend( - unicode(self.service or ''), - self.node or '', + unicode(self.service or ""), + self.node or "", mb_data, - profile=self.blog.profile) + profile=self.blog.profile, + ) def delete(self): """Remove this Entry from parent manager @@ -288,7 +311,7 @@ all children entries will be recursively removed too """ # XXX: named delete and not remove to avoid conflict with pyjamas - log.debug(u"deleting entry {}".format('EDIT ENTRY' if self.new else self.item.id)) + log.debug(u"deleting entry {}".format("EDIT ENTRY" if self.new else self.item.id)) for child in self.entries: child.delete() try: @@ -303,8 +326,7 @@ # in QuickBlog's dictionary del self.blog.id2entries[self.item.id] if self.item.comments: - comments_tuple = (self.item.comments_service, - self.item.comments_node) + comments_tuple = (self.item.comments_service, self.item.comments_node) other_entries = self.blog.node2entries[comments_tuple].remove(self) if not other_entries: del self.blog.node2entries[comments_tuple] @@ -316,12 +338,20 @@ """ # TODO: manage several comments nodes case. if self.item.comments: - self.blog.host.bridge.psNodeDelete(unicode(self.item.comments_service) or "", self.item.comments_node, profile=self.blog.profile) - self.blog.host.bridge.mbRetract(unicode(self.service or ""), self.node or "", self.item.id, profile=self.blog.profile) + self.blog.host.bridge.psNodeDelete( + unicode(self.item.comments_service) or "", + self.item.comments_node, + profile=self.blog.profile, + ) + self.blog.host.bridge.mbRetract( + unicode(self.service or ""), + self.node or "", + self.item.id, + profile=self.blog.profile, + ) class QuickBlog(EntriesManager, quick_widgets.QuickWidget): - def __init__(self, host, targets, profiles=None): """Panel used to show microblog @@ -330,12 +360,12 @@ to know where to send new messages. """ EntriesManager.__init__(self, None) - self.id2entries = {} # used to find an entry with it's item id - # must be kept up-to-date by Entry - self.node2entries = {} # same as above, values are lists in case of - # two entries link to the same comments node + self.id2entries = {} # used to find an entry with it's item id + # must be kept up-to-date by Entry + self.node2entries = {} # same as above, values are lists in case of + # two entries link to the same comments node if not targets: - targets = () # XXX: we use empty tuple instead of None to workaround a pyjamas bug + targets = () # XXX: we use empty tuple instead of None to workaround a pyjamas bug quick_widgets.QuickWidget.__init__(self, host, targets, C.PROF_KEY_NONE) self._targets_type = C.ALL else: @@ -356,11 +386,17 @@ raise ValueError("Unkown targets type") def __str__(self): - return u"Blog Widget [target: {}, profile: {}]".format(', '.join(self.targets), self.profile) + return u"Blog Widget [target: {}, profile: {}]".format( + ", ".join(self.targets), self.profile + ) def _getResultsCb(self, data, rt_session): remaining, results = data - log.debug("Got {got_len} results, {rem_len} remaining".format(got_len=len(results), rem_len=remaining)) + log.debug( + "Got {got_len} results, {rem_len} remaining".format( + got_len=len(results), rem_len=remaining + ) + ) for result in results: service, node, failure, items, metadata = result if not failure: @@ -378,22 +414,46 @@ @param rt_session(str): session id as returned by mbGetFromMany """ - self.host.bridge.mbGetFromManyWithCommentsRTResult(rt_session, profile=self.profile, - callback=lambda data:self._getResultsCb(data, rt_session), - errback=self._getResultsEb) + self.host.bridge.mbGetFromManyWithCommentsRTResult( + rt_session, + profile=self.profile, + callback=lambda data: self._getResultsCb(data, rt_session), + errback=self._getResultsEb, + ) def getAll(self): """Get all (micro)blogs from self.targets""" + def gotSession(rt_session): self._getResults(rt_session) if self._targets_type in (C.ALL, C.GROUP): targets = tuple(self.targets) if self._targets_type is C.GROUP else () - self.host.bridge.mbGetFromManyWithComments(self._targets_type, targets, 10, 10, {}, {"subscribe":C.BOOL_TRUE}, profile=self.profile, callback=gotSession) + self.host.bridge.mbGetFromManyWithComments( + self._targets_type, + targets, + 10, + 10, + {}, + {"subscribe": C.BOOL_TRUE}, + profile=self.profile, + callback=gotSession, + ) own_pep = self.host.whoami.bare - self.host.bridge.mbGetFromManyWithComments(C.JID, (unicode(own_pep),), 10, 10, {}, {}, profile=self.profile, callback=gotSession) + self.host.bridge.mbGetFromManyWithComments( + C.JID, + (unicode(own_pep),), + 10, + 10, + {}, + {}, + profile=self.profile, + callback=gotSession, + ) else: - raise NotImplementedError(u'{} target type is not managed'.format(self._targets_type)) + raise NotImplementedError( + u"{} target type is not managed".format(self._targets_type) + ) def isJidAccepted(self, jid_): """Tell if a jid is actepted and must be shown in this panel @@ -403,7 +463,7 @@ """ if self._targets_type == C.ALL: return True - assert self._targets_type is C.GROUP # we don't manage other types for now + assert self._targets_type is C.GROUP # we don't manage other types for now for group in self.targets: if self.host.contact_lists[self.profile].isEntityInGroup(jid_, group): return True @@ -422,7 +482,7 @@ @param profile: %(doc_profile)s """ try: - entry = self.id2entries[mb_data['id']] + entry = self.id2entries[mb_data["id"]] except KeyError: # The entry is new try: @@ -430,9 +490,14 @@ except: # The node is unknown, # we need to check that we can accept the entry - if (self.isJidAccepted(service) - or (groups is None and service == self.host.profiles[self.profile].whoami.bare) - or (groups and groups.intersection(self.targets))): + if ( + self.isJidAccepted(service) + or ( + groups is None + and service == self.host.profiles[self.profile].whoami.bare + ) + or (groups and groups.intersection(self.targets)) + ): self.addEntry(mb_data, service=service, node=node) else: # the entry is a comment in a known node
--- a/sat_frontends/quick_frontend/quick_chat.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/quick_frontend/quick_chat.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,6 +19,7 @@ from sat.core.i18n import _ from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from sat_frontends.quick_frontend import quick_widgets @@ -26,31 +27,48 @@ from collections import OrderedDict from sat_frontends.tools import jid import time + try: from locale import getlocale except ImportError: # FIXME: pyjamas workaround - getlocale = lambda x: (None, 'utf-8') + getlocale = lambda x: (None, "utf-8") -ROOM_USER_JOINED = 'ROOM_USER_JOINED' -ROOM_USER_LEFT = 'ROOM_USER_LEFT' +ROOM_USER_JOINED = "ROOM_USER_JOINED" +ROOM_USER_LEFT = "ROOM_USER_LEFT" ROOM_USER_MOVED = (ROOM_USER_JOINED, ROOM_USER_LEFT) # from datetime import datetime try: # FIXME: to be removed when an acceptable solution is here - unicode('') # XXX: unicode doesn't exist in pyjamas -except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options + unicode("") # XXX: unicode doesn't exist in pyjamas +except ( + TypeError, + AttributeError, +): # Error raised is not the same depending on pyjsbuild options unicode = str # FIXME: day_format need to be settable (i18n) + class Message(object): """Message metadata""" - def __init__(self, parent, uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile): + def __init__( + self, + parent, + uid, + timestamp, + from_jid, + to_jid, + msg, + subject, + type_, + extra, + profile, + ): self.parent = parent self.profile = profile self.uid = uid @@ -64,7 +82,11 @@ self.nick = self.getNick(from_jid) self._status = None # own_mess is True if message was sent by profile's jid - self.own_mess = (from_jid.resource == self.parent.nick) if self.parent.type == C.CHAT_GROUP else (from_jid.bare == self.host.profiles[profile].whoami.bare) + self.own_mess = ( + (from_jid.resource == self.parent.nick) + if self.parent.type == C.CHAT_GROUP + else (from_jid.bare == self.host.profiles[profile].whoami.bare) + ) # is user mentioned here ? if self.parent.type == C.CHAT_GROUP and not self.own_mess: for m in msg.itervalues(): @@ -80,7 +102,7 @@ @property def info_type(self): - return self.extra.get('info_type') + return self.extra.get("info_type") @property def mention(self): @@ -92,7 +114,7 @@ @property def history(self): """True if message come from history""" - return self.extra.get('history', False) + return self.extra.get("history", False) @property def main_message(self): @@ -101,8 +123,8 @@ self.selected_lang = self.parent.lang return self.message[self.parent.lang] try: - self.selected_lang = '' - return self.message[''] + self.selected_lang = "" + return self.message[""] except KeyError: try: lang, mess = self.message.iteritems().next() @@ -110,24 +132,23 @@ return mess except StopIteration: log.error(u"Can't find message for uid {}".format(self.uid)) - return '' + return "" @property def main_message_xhtml(self): """rich message""" - xhtml = {k:v for k,v in self.extra.iteritems() if 'html' in k} + xhtml = {k: v for k, v in self.extra.iteritems() if "html" in k} if xhtml: # FIXME: we only return first found value for now return next(xhtml.itervalues()) - @property def time_text(self): """Return timestamp in a nicely formatted way""" # if the message was sent before today, we print the full date timestamp = time.localtime(self.timestamp) time_format = u"%c" if timestamp < self.parent.day_change else u"%H:%M" - return time.strftime(time_format, timestamp).decode(getlocale()[1] or 'utf-8') + return time.strftime(time_format, timestamp).decode(getlocale()[1] or "utf-8") @property def avatar(self): @@ -140,15 +161,22 @@ contact_list = self.host.contact_lists[self.profile] if self.type == C.MESS_TYPE_INFO and self.info_type in ROOM_USER_MOVED: try: - return self.extra['user_nick'] + return self.extra["user_nick"] except KeyError: log.error(u"extra data is missing user nick for uid {}".format(self.uid)) return "" # FIXME: converted getSpecials to list for pyjamas - if self.parent.type == C.CHAT_GROUP or entity in list(contact_list.getSpecials(C.CONTACT_SPECIAL_GROUP)): + if self.parent.type == C.CHAT_GROUP or entity in list( + contact_list.getSpecials(C.CONTACT_SPECIAL_GROUP) + ): return entity.resource or "" if entity.bare in contact_list: - return contact_list.getCache(entity, 'nick') or contact_list.getCache(entity, 'name') or entity.node or entity + return ( + contact_list.getCache(entity, "nick") + or contact_list.getCache(entity, "name") + or entity.node + or entity + ) return entity.node or entity @property @@ -178,7 +206,7 @@ break if me: self.type = C.MESS_TYPE_INFO - self.extra['info_type'] = 'me' + self.extra["info_type"] = "me" nick = self.nick for lang, mess in self.message.iteritems(): self.message[lang] = u"* " + nick + mess[3:] @@ -190,10 +218,10 @@ def __init__(self, parent, data, profile): self.parent = parent self.profile = profile - self.nick = data['nick'] - self._entity = data.get('entity') - self.affiliation = data['affiliation'] - self.role = data['role'] + self.nick = data["nick"] + self._entity = data.get("entity") + self.affiliation = data["affiliation"] + self.role = data["role"] self.widgets = set() # widgets linked to this occupant self._state = None @@ -201,11 +229,11 @@ def data(self): """reconstruct data dict from attributes""" data = {} - data['nick'] = self.nick + data["nick"] = self.nick if self._entity is not None: - data['entity'] = self._entity - data['affiliation'] = self.affiliation - data['role'] = self.role + data["entity"] = self._entity + data["affiliation"] = self.affiliation + data["role"] = self.role return data @property @@ -239,23 +267,34 @@ class QuickChat(quick_widgets.QuickWidget): - visible_states = ['chat_state'] # FIXME: to be removed, used only in quick_games + visible_states = ["chat_state"] # FIXME: to be removed, used only in quick_games - def __init__(self, host, target, type_=C.CHAT_ONE2ONE, nick=None, occupants=None, subject=None, profiles=None): + def __init__( + self, + host, + target, + type_=C.CHAT_ONE2ONE, + nick=None, + occupants=None, + subject=None, + profiles=None, + ): """ @param type_: can be C.CHAT_ONE2ONE for single conversation or C.CHAT_GROUP for chat à la IRC """ - self.lang = '' # default language to use for messages + self.lang = "" # default language to use for messages quick_widgets.QuickWidget.__init__(self, host, target, profiles=profiles) - self._locked = True # True when we are waiting for history/search - # messageNew signals are cached when locked + self._locked = True # True when we are waiting for history/search + # messageNew signals are cached when locked self._cache = OrderedDict() assert type_ in (C.CHAT_ONE2ONE, C.CHAT_GROUP) self.current_target = target self.type = type_ if type_ == C.CHAT_GROUP: if target.resource: - raise exceptions.InternalError(u"a group chat entity can't have a resource") + raise exceptions.InternalError( + u"a group chat entity can't have a resource" + ) if nick is None: raise exceptions.InternalError(u"nick must not be None for group chat") @@ -264,14 +303,26 @@ self.setOccupants(occupants) else: if occupants is not None or nick is not None: - raise exceptions.InternalError(u"only group chat can have occupants or nick") + raise exceptions.InternalError( + u"only group chat can have occupants or nick" + ) self.messages = OrderedDict() # key: uid, value: Message instance self.games = {} # key=game name (unicode), value=instance of quick_games.RoomGame self.subject = subject lt = time.localtime() - self.day_change = (lt.tm_year, lt.tm_mon, lt.tm_mday, 0, 0, 0, lt.tm_wday, lt.tm_yday, lt.tm_isdst) # struct_time of day changing time + self.day_change = ( + lt.tm_year, + lt.tm_mon, + lt.tm_mday, + 0, + 0, + 0, + lt.tm_wday, + lt.tm_yday, + lt.tm_isdst, + ) # struct_time of day changing time if self.host.AVATARS_HANDLER: - self.host.addListener('avatar', self.onAvatar, profiles) + self.host.addListener("avatar", self.onAvatar, profiles) def postInit(self): """Method to be called by frontend after widget is initialised @@ -284,7 +335,7 @@ def onDelete(self): if self.host.AVATARS_HANDLER: - self.host.removeListener('avatar', self.onAvatar) + self.host.removeListener("avatar", self.onAvatar) @property def contact_list(self): @@ -293,7 +344,9 @@ ## Widget management ## def __str__(self): - return u"Chat Widget [target: {}, type: {}, profile: {}]".format(self.target, self.type, self.profile) + return u"Chat Widget [target: {}, type: {}, profile: {}]".format( + self.target, self.type, self.profile + ) @staticmethod def getWidgetHash(target, profiles): @@ -311,16 +364,18 @@ def addTarget(self, target): super(QuickChat, self).addTarget(target) if target.resource: - self.current_target = target # FIXME: tmp, must use resource priority throught contactList instead + self.current_target = ( + target + ) # FIXME: tmp, must use resource priority throught contactList instead def recreateArgs(self, args, kwargs): """copy important attribute for a new widget""" - kwargs['type_'] = self.type + kwargs["type_"] = self.type if self.type == C.CHAT_GROUP: - kwargs['occupants'] = {o.nick: o.data for o in self.occupants.itervalues()} - kwargs['subject'] = self.subject + kwargs["occupants"] = {o.nick: o.data for o in self.occupants.itervalues()} + kwargs["subject"] = self.subject try: - kwargs['nick'] = self.nick + kwargs["nick"] = self.nick except AttributeError: pass @@ -333,7 +388,14 @@ @param entity: full jid of the target """ - return self.host.widgets.getOrCreateWidget(QuickChat, entity, type_=C.CHAT_ONE2ONE, force_hash=self.getPrivateHash(self.profile, entity), on_new_widget=self.onPrivateCreated, profile=self.profile) # we force hash to have a new widget, not this one again + return self.host.widgets.getOrCreateWidget( + QuickChat, + entity, + type_=C.CHAT_ONE2ONE, + force_hash=self.getPrivateHash(self.profile, entity), + on_new_widget=self.onPrivateCreated, + profile=self.profile, + ) # we force hash to have a new widget, not this one again @property def target(self): @@ -347,25 +409,17 @@ """set the whole list of occupants""" assert len(self.occupants) == 0 for nick, data in occupants.iteritems(): - self.occupants[nick] = Occupant( - self, - data, - self.profile - ) + self.occupants[nick] = Occupant(self, data, self.profile) def addUser(self, occupant_data): """Add user if it is not in the group list""" - occupant = Occupant( - self, - occupant_data, - self.profile - ) + occupant = Occupant(self, occupant_data, self.profile) self.occupants[occupant.nick] = occupant return occupant def removeUser(self, occupant_data): """Remove a user from the group list""" - nick = occupant_data['nick'] + nick = occupant_data["nick"] try: occupant = self.occupants.pop(nick) except KeyError: @@ -391,14 +445,17 @@ @return (bool): True if this Chat Widget manage this couple """ if self.type == C.CHAT_GROUP: - if mess_type in (C.MESS_TYPE_GROUPCHAT, C.MESS_TYPE_INFO) and self.target == entity.bare: + if ( + mess_type in (C.MESS_TYPE_GROUPCHAT, C.MESS_TYPE_INFO) + and self.target == entity.bare + ): return True else: if mess_type != C.MESS_TYPE_GROUPCHAT and entity in self.targets: return True return False - def updateHistory(self, size=C.HISTORY_LIMIT_DEFAULT, filters=None, profile='@NONE@'): + def updateHistory(self, size=C.HISTORY_LIMIT_DEFAULT, filters=None, profile="@NONE@"): """Called when history need to be recreated Remove all message from history then call historyPrint @@ -423,7 +480,7 @@ self.messageNew(*data) del self._cache - def historyPrint(self, size=C.HISTORY_LIMIT_DEFAULT, filters=None, profile='@NONE@'): + def historyPrint(self, size=C.HISTORY_LIMIT_DEFAULT, filters=None, profile="@NONE@"): """Print the current history @param size (int): number of messages @@ -442,21 +499,23 @@ log.debug(log_msg) if self.type == C.CHAT_ONE2ONE: - special = self.host.contact_lists[self.profile].getCache(self.target, C.CONTACT_SPECIAL) + special = self.host.contact_lists[self.profile].getCache( + self.target, C.CONTACT_SPECIAL + ) if special == C.CONTACT_SPECIAL_GROUP: # we have a private conversation # so we need full jid for the history # (else we would get history from group itself) # and to filter out groupchat message target = self.target - filters['not_types'] = C.MESS_TYPE_GROUPCHAT + filters["not_types"] = C.MESS_TYPE_GROUPCHAT else: target = self.target.bare else: # groupchat target = self.target.bare # FIXME: info not handled correctly - filters['types'] = C.MESS_TYPE_GROUPCHAT + filters["types"] = C.MESS_TYPE_GROUPCHAT def _historyGetCb(history): # day_format = "%A, %d %b %Y" # to display the day change @@ -476,42 +535,80 @@ # if ((self.type == C.CHAT_GROUP and type_ != C.MESS_TYPE_GROUPCHAT) or # (self.type == C.CHAT_ONE2ONE and type_ == C.MESS_TYPE_GROUPCHAT)): # continue - extra['history'] = True - self.messages[uid] = Message(self, uid, timestamp, from_jid, to_jid, message, subject, type_, extra, profile) + extra["history"] = True + self.messages[uid] = Message( + self, + uid, + timestamp, + from_jid, + to_jid, + message, + subject, + type_, + extra, + profile, + ) self._onHistoryPrinted() def _historyGetEb(err): log.error(_(u"Can't get history: {}").format(err)) self._onHistoryPrinted() - self.host.bridge.historyGet(unicode(self.host.profiles[profile].whoami.bare), unicode(target), size, True, filters, profile, callback=_historyGetCb, errback=_historyGetEb) + self.host.bridge.historyGet( + unicode(self.host.profiles[profile].whoami.bare), + unicode(target), + size, + True, + filters, + profile, + callback=_historyGetCb, + errback=_historyGetEb, + ) - def messageNew(self, uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile): + def messageNew( + self, uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile + ): if self._locked: - self._cache[uid] = (uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile) + self._cache[uid] = ( + uid, + timestamp, + from_jid, + to_jid, + msg, + subject, + type_, + extra, + profile, + ) return if self.type == C.CHAT_GROUP: if to_jid.resource and type_ != C.MESS_TYPE_GROUPCHAT: # we have a private message, we forward it to a private conversation widget chat_widget = self.getOrCreatePrivateWidget(to_jid) - chat_widget.messageNew(uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile) + chat_widget.messageNew( + uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile + ) return if type_ == C.MESS_TYPE_INFO: try: - info_type = extra['info_type'] + info_type = extra["info_type"] except KeyError: pass else: - user_data = {k[5:]:v for k,v in extra.iteritems() if k.startswith('user_')} + user_data = { + k[5:]: v for k, v in extra.iteritems() if k.startswith("user_") + } if info_type == ROOM_USER_JOINED: self.addUser(user_data) elif info_type == ROOM_USER_LEFT: self.removeUser(user_data) - message = Message(self, uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile) + message = Message( + self, uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile + ) self.messages[uid] = message - if 'received_timestamp' in extra: + if "received_timestamp" in extra: log.warning(u"Delayed message received after history, this should not happen") self.createMessage(message) @@ -537,7 +634,9 @@ def setSubject(self, subject): """Set title for a group chat""" if self.type != C.CHAT_GROUP: - raise exceptions.InternalError("trying to set subject for a non group chat window") + raise exceptions.InternalError( + "trying to set subject for a non group chat window" + ) self.subject = subject def changeSubject(self, new_subject): @@ -579,8 +678,11 @@ try: self.occupants[nick].state = state except KeyError: - log.warning(u"{nick} not found in {room}, ignoring new chat state".format( - nick=nick, room=self.target.bare)) + log.warning( + u"{nick} not found in {room}, ignoring new chat state".format( + nick=nick, room=self.target.bare + ) + ) def onMessageState(self, uid, status, profile): try: @@ -594,7 +696,7 @@ if self.type == C.CHAT_GROUP: if entity.bare == self.target: try: - self.occupants[entity.resource].update({'avatar': filename}) + self.occupants[entity.resource].update({"avatar": filename}) except KeyError: # can happen for a message in history where the # entity is not here anymore @@ -603,14 +705,17 @@ for m in self.messages.values(): if m.nick == entity.resource: for w in m.widgets: - w.update({'avatar': filename}) + w.update({"avatar": filename}) else: - if entity.bare == self.target.bare or entity.bare == self.host.profiles[profile].whoami.bare: + if ( + entity.bare == self.target.bare + or entity.bare == self.host.profiles[profile].whoami.bare + ): log.info(u"avatar updated for {}".format(entity)) for m in self.messages.values(): if m.from_jid.bare == entity.bare: for w in m.widgets: - w.update({'avatar': filename}) + w.update({"avatar": filename}) quick_widgets.register(QuickChat)
--- a/sat_frontends/quick_frontend/quick_contact_list.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/quick_frontend/quick_contact_list.py Wed Jun 27 20:14:46 2018 +0200 @@ -22,6 +22,7 @@ from sat.core.i18n import _ from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from sat_frontends.quick_frontend.quick_widgets import QuickWidget @@ -32,9 +33,9 @@ try: # FIXME: to be removed when an acceptable solution is here - unicode('') # XXX: unicode doesn't exist in pyjamas + unicode("") # XXX: unicode doesn't exist in pyjamas except (TypeError, AttributeError): # Error raised is not the same depending on - # pyjsbuild options + # pyjsbuild options # XXX: pyjamas' max doesn't support key argument, so we implement it ourself pyjamas_max = max @@ -95,27 +96,31 @@ # (e.g. not in contact list) if they have notifications attached self.show_entities_with_notifs = True - self.host.bridge.asyncGetParamA(C.SHOW_EMPTY_GROUPS, - "General", - profile_key=profile, - callback=self._showEmptyGroups) + self.host.bridge.asyncGetParamA( + C.SHOW_EMPTY_GROUPS, + "General", + profile_key=profile, + callback=self._showEmptyGroups, + ) - self.host.bridge.asyncGetParamA(C.SHOW_OFFLINE_CONTACTS, - "General", - profile_key=profile, - callback=self._showOfflineContacts) + self.host.bridge.asyncGetParamA( + C.SHOW_OFFLINE_CONTACTS, + "General", + profile_key=profile, + callback=self._showOfflineContacts, + ) # FIXME: workaround for a pyjamas issue: calling hash on a class method always # return a different value if that method is defined directly within the # class (with the "def" keyword) self.presenceListener = self.onPresenceUpdate - self.host.addListener('presence', self.presenceListener, [self.profile]) + self.host.addListener("presence", self.presenceListener, [self.profile]) self.nickListener = self.onNickUpdate - self.host.addListener('nick', self.nickListener, [self.profile]) + self.host.addListener("nick", self.nickListener, [self.profile]) self.notifListener = self.onNotification - self.host.addListener('notification', self.notifListener, [self.profile]) + self.host.addListener("notification", self.notifListener, [self.profile]) # notifListener only update the entity, so we can re-use it - self.host.addListener('notificationsClear', self.notifListener, [self.profile]) + self.host.addListener("notificationsClear", self.notifListener, [self.profile]) def _showEmptyGroups(self, show_str): # Called only by __init__ @@ -155,8 +160,13 @@ @return (set[jid.JID]) """ - return set([entity for entity in self._roster - if self.getCache(entity, C.PRESENCE_SHOW) is not None]) + return set( + [ + entity + for entity in self._roster + if self.getCache(entity, C.PRESENCE_SHOW) is not None + ] + ) @property def roster_entities_by_group(self): @@ -165,7 +175,7 @@ This also includes the empty group (None key). @return (dict[unicode,set(jid.JID)]) """ - return {group: self._groups[group]['jids'] for group in self._groups} + return {group: self._groups[group]["jids"] for group in self._groups} @property def roster_groups_by_entities(self): @@ -175,7 +185,7 @@ """ result = {} for group, data in self._groups.iteritems(): - for entity in data['jids']: + for entity in data["jids"]: result.setdefault(entity, set()).add(group) return result @@ -195,7 +205,6 @@ """ return self._cache.iteritems() - @property def items(self): """Return item representation for all visible entities in cache @@ -203,9 +212,11 @@ entities are not sorted key: bare jid, value: data """ - return {jid_:cache for jid_, cache in self._cache.iteritems() - if self.entityVisible(jid_)} - + return { + jid_: cache + for jid_, cache in self._cache.iteritems() + if self.entityVisible(jid_) + } def getItem(self, entity): """Return item representation of requested entity @@ -275,7 +286,7 @@ # full cache is requested return cache - if name in ('status', C.PRESENCE_STATUSES, C.PRESENCE_PRIORITY, C.PRESENCE_SHOW): + if name in ("status", C.PRESENCE_STATUSES, C.PRESENCE_PRIORITY, C.PRESENCE_SHOW): # these data are related to the resource if not entity.resource: main_resource = cache[C.CONTACT_MAIN_RESOURCE] @@ -287,9 +298,9 @@ else: cache = cache[C.CONTACT_RESOURCES].setdefault(entity.resource, {}) - if name == 'status': # XXX: we get the first status for 'status' key + if name == "status": # XXX: we get the first status for 'status' key # TODO: manage main language for statuses - return cache[C.PRESENCE_STATUSES].get(C.PRESENCE_STATUSES_DEFAULT, '') + return cache[C.PRESENCE_STATUSES].get(C.PRESENCE_STATUSES_DEFAULT, "") elif entity.resource: try: @@ -332,7 +343,7 @@ @param name: name of the data (can't be "jids") @param value: value to set """ - assert name is not 'jids' + assert name is not "jids" self._groups[group][name] = value def getGroupData(self, group, name=None): @@ -388,8 +399,10 @@ for entity in self._specials: if bare and entity.resource: continue - if (special_type is not None - and self.getCache(entity, C.CONTACT_SPECIAL) != special_type): + if ( + special_type is not None + and self.getCache(entity, C.CONTACT_SPECIAL) != special_type + ): continue yield entity @@ -440,9 +453,14 @@ if in_roster: self._roster.add(entity_bare) - cache = self._cache.setdefault(entity_bare, {C.CONTACT_RESOURCES: {}, - C.CONTACT_MAIN_RESOURCE: None, - C.CONTACT_SELECTED: set()}) + cache = self._cache.setdefault( + entity_bare, + { + C.CONTACT_RESOURCES: {}, + C.CONTACT_MAIN_RESOURCE: None, + C.CONTACT_SELECTED: set(), + }, + ) # we don't want forbidden data in attributes assert not C.CONTACT_DATA_FORBIDDEN.intersection(attributes) @@ -454,13 +472,15 @@ if C.CONTACT_GROUPS in cache: # XXX: don't use set(cache[C.CONTACT_GROUPS]).difference(groups) because # it won't work in Pyjamas if None is in cache[C.CONTACT_GROUPS] - for group in [group for group in cache[C.CONTACT_GROUPS] - if group not in groups]: - self._groups[group]['jids'].remove(entity_bare) + for group in [ + group for group in cache[C.CONTACT_GROUPS] if group not in groups + ]: + self._groups[group]["jids"].remove(entity_bare) cache[C.CONTACT_GROUPS] = groups for group in groups: - self._groups.setdefault(group, {}).setdefault('jids', set()).add( - entity_bare) + self._groups.setdefault(group, {}).setdefault("jids", set()).add( + entity_bare + ) # special entities management if C.CONTACT_SPECIAL in attributes: @@ -473,8 +493,11 @@ # now the attributes we keep in cache # XXX: if entity is a full jid, we store the value for the resource only - cache_attr = (cache[C.CONTACT_RESOURCES].setdefault(entity.resource, {}) - if entity.resource else cache) + cache_attr = ( + cache[C.CONTACT_RESOURCES].setdefault(entity.resource, {}) + if entity.resource + else cache + ) for attribute, value in attributes.iteritems(): if value is None: # XXX: pyjamas hack: we need to use pop instead of del @@ -483,7 +506,9 @@ except KeyError: pass else: - if attribute == 'nick' and self.isSpecial(entity, C.CONTACT_SPECIAL_GROUP): + if attribute == "nick" and self.isSpecial( + entity, C.CONTACT_SPECIAL_GROUP + ): # we don't want to keep nick for MUC rooms # FIXME: this is here as plugin XEP-0054 can link resource's nick # with bare jid which in the case of MUC @@ -518,12 +543,15 @@ selected = self._selected else: selected = {selected.bare for selected in self._selected} - return ((show is not None and show != C.PRESENCE_UNAVAILABLE) - or self.show_disconnected - or entity in selected - or (self.show_entities_with_notifs - and next(self.host.getNotifs(entity.bare, profile=self.profile), None)) - ) + return ( + (show is not None and show != C.PRESENCE_UNAVAILABLE) + or self.show_disconnected + or entity in selected + or ( + self.show_entities_with_notifs + and next(self.host.getNotifs(entity.bare, profile=self.profile), None) + ) + ) def anyEntityVisible(self, entities, check_resources=False): """Tell if in a list of entities, at least one should be shown @@ -564,8 +592,8 @@ pass del self._cache[entity_bare] for group in groups: - self._groups[group]['jids'].remove(entity_bare) - if not self._groups[group]['jids']: + self._groups[group]["jids"].remove(entity_bare) + if not self._groups[group]["jids"]: # FIXME: we use pop because of pyjamas: # http://wiki.goffi.org/wiki/Issues_with_Pyjamas/en self._groups.pop(group) @@ -599,14 +627,20 @@ try: del cache[C.CONTACT_RESOURCES][entity.resource] except KeyError: - log.error(u"Presence unavailable received " - u"for an unknown resource [{}]".format(entity)) + log.error( + u"Presence unavailable received " + u"for an unknown resource [{}]".format(entity) + ) if not cache[C.CONTACT_RESOURCES]: cache[C.CONTACT_MAIN_RESOURCE] = None else: if not entity.resource: - log.warning(_(u"received presence from entity " - u"without resource: {}".format(entity))) + log.warning( + _( + u"received presence from entity " + u"without resource: {}".format(entity) + ) + ) resources_data = cache[C.CONTACT_RESOURCES] resource_data = resources_data.setdefault(entity.resource, {}) resource_data[C.PRESENCE_SHOW] = show @@ -616,9 +650,12 @@ if entity.bare not in self._specials: # we may have resources with no priority # (when a cached value is added for a not connected resource) - priority_resource = max(resources_data, - key=lambda res: resources_data[res].get( - C.PRESENCE_PRIORITY, -2**32)) + priority_resource = max( + resources_data, + key=lambda res: resources_data[res].get( + C.PRESENCE_PRIORITY, -2 ** 32 + ), + ) cache[C.CONTACT_MAIN_RESOURCE] = priority_resource if self.entityVisible(entity.bare): update_type = C.UPDATE_MODIFY if was_visible else C.UPDATE_ADD @@ -634,7 +671,7 @@ @param profile: %(doc_profile)s """ assert profile == self.profile - self.setCache(entity, 'nick', new_nick) + self.setCache(entity, "nick", new_nick) def onNotification(self, entity, notif, profile): """Update entity with notification @@ -723,18 +760,18 @@ class QuickContactListHandler(object): - def __init__(self, host): super(QuickContactListHandler, self).__init__() self.host = host global handler if handler is not None: - raise exceptions.InternalError(u"QuickContactListHandler must be " - u"instanciated only once") + raise exceptions.InternalError( + u"QuickContactListHandler must be " u"instanciated only once" + ) handler = self - self._clist = {} # key: profile, value: ProfileContactList + self._clist = {} # key: profile, value: ProfileContactList self._widgets = set() - self._update_locked = False # se to True to ignore updates + self._update_locked = False # se to True to ignore updates def __getitem__(self, profile): """Return ProfileContactList instance for the requested profile""" @@ -845,16 +882,16 @@ return self.items_sort(self.items) def items_sort(self, items): - """sort items + """sort items @param items(dict): items to sort (will be emptied !) @return (OrderedDict): sorted items """ - ordered_items = OrderedDict() - bare_jids = sorted(items.keys()) - for jid_ in bare_jids: - ordered_items[jid_] = items.pop(jid_) - return ordered_items + ordered_items = OrderedDict() + bare_jids = sorted(items.keys()) + for jid_ in bare_jids: + ordered_items[jid_] = items.pop(jid_) + return ordered_items def register(self, widget): """Register a QuickContactList widget @@ -940,8 +977,11 @@ remaining = to_fill.difference(filled) if remaining != to_fill: - log.debug(u"Not re-filling already filled contact list(s) for {}".format( - u', '.join(to_fill.intersection(filled)))) + log.debug( + u"Not re-filling already filled contact list(s) for {}".format( + u", ".join(to_fill.intersection(filled)) + ) + ) for profile in remaining: self._clist[profile]._fill() @@ -973,8 +1013,11 @@ if set to False, widget state can be inconsistent, be sure to know what youa re doing! """ - log.debug(u"Contact lists updates are now {}".format( - u"LOCKED" if locked else u"UNLOCKED")) + log.debug( + u"Contact lists updates are now {}".format( + u"LOCKED" if locked else u"UNLOCKED" + ) + ) self._update_locked = locked if not locked and do_update: self.update() @@ -987,10 +1030,11 @@ class QuickContactList(QuickWidget): """This class manage the visual representation of contacts""" - SINGLE=False - PROFILES_MULTIPLE=True + + SINGLE = False + PROFILES_MULTIPLE = True # Can be linked to no profile (e.g. at the early frontend start) - PROFILES_ALLOW_NONE=True + PROFILES_ALLOW_NONE = True def __init__(self, host, profiles): super(QuickContactList, self).__init__(host, None, profiles) @@ -998,10 +1042,10 @@ # options # for next values, None means use indivual value per profile # True or False mean override these values for all profiles - self.show_disconnected = None # TODO - self.show_empty_groups = None # TODO - self.show_resources = None # TODO - self.show_status = None # TODO + self.show_disconnected = None # TODO + self.show_empty_groups = None # TODO + self.show_resources = None # TODO + self.show_status = None # TODO def postInit(self): """Method to be called by frontend after widget is initialised"""
--- a/sat_frontends/quick_frontend/quick_contact_management.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/quick_frontend/quick_contact_management.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,12 +19,14 @@ from sat.core.i18n import _ from sat.core.log import getLogger + log = getLogger(__name__) -from sat_frontends.tools.jid import JID +from sat_frontends.tools.jid import JID class QuickContactManagement(object): """This helper class manage the contacts and ease the use of nicknames and shortcuts""" + ### FIXME: is SàT a better place for all this stuff ??? ### def __init__(self): @@ -40,19 +42,19 @@ def add(self, entity): """Add contact to the list, update resources""" if not self.__contactlist.has_key(entity.bare): - self.__contactlist[entity.bare] = {'resources':[]} + self.__contactlist[entity.bare] = {"resources": []} if not entity.resource: return - if entity.resource in self.__contactlist[entity.bare]['resources']: - self.__contactlist[entity.bare]['resources'].remove(entity.resource) - self.__contactlist[entity.bare]['resources'].append(entity.resource) + if entity.resource in self.__contactlist[entity.bare]["resources"]: + self.__contactlist[entity.bare]["resources"].remove(entity.resource) + self.__contactlist[entity.bare]["resources"].append(entity.resource) def getContFromGroup(self, group): """Return all contacts which are in given group""" result = [] for contact in self.__contactlist: - if self.__contactlist[contact].has_key('groups'): - if group in self.__contactlist[contact]['groups']: + if self.__contactlist[contact].has_key("groups"): + if group in self.__contactlist[contact]["groups"]: result.append(JID(contact)) return result @@ -62,13 +64,13 @@ @param name: name of the attribute @return: asked attribute""" if self.__contactlist.has_key(entity.bare): - if name == 'status': #FIXME: for the moment, we only use the first status - if self.__contactlist[entity.bare]['statuses']: - return self.__contactlist[entity.bare]['statuses'].values()[0] + if name == "status": # FIXME: for the moment, we only use the first status + if self.__contactlist[entity.bare]["statuses"]: + return self.__contactlist[entity.bare]["statuses"].values()[0] if self.__contactlist[entity.bare].has_key(name): return self.__contactlist[entity.bare][name] else: - log.debug(_('Trying to get attribute for an unknown contact')) + log.debug(_("Trying to get attribute for an unknown contact")) return None def isConnected(self, entity): @@ -79,12 +81,12 @@ """remove resource. If no more resource is online or is no resource is specified, contact is deleted""" try: if entity.resource: - self.__contactlist[entity.bare]['resources'].remove(entity.resource) - if not entity.resource or not self.__contactlist[entity.bare]['resources']: - #no more resource available: the contact seems really disconnected + self.__contactlist[entity.bare]["resources"].remove(entity.resource) + if not entity.resource or not self.__contactlist[entity.bare]["resources"]: + # no more resource available: the contact seems really disconnected del self.__contactlist[entity.bare] except KeyError: - log.error(_('INTERNAL ERROR: Key log.error')) + log.error(_("INTERNAL ERROR: Key log.error")) raise def update(self, entity, key, value): @@ -96,8 +98,7 @@ if self.__contactlist.has_key(entity.bare): self.__contactlist[entity.bare][key] = value else: - log.debug (_('Trying to update an unknown contact: %s') % entity.bare) + log.debug(_("Trying to update an unknown contact: %s") % entity.bare) def get_full(self, entity): - return entity.bare+'/'+self.__contactlist[entity.bare]['resources'][-1] - + return entity.bare + "/" + self.__contactlist[entity.bare]["resources"][-1]
--- a/sat_frontends/quick_frontend/quick_game_tarot.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/quick_frontend/quick_game_tarot.py Wed Jun 27 20:14:46 2018 +0200 @@ -18,14 +18,14 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.core.log import getLogger + log = getLogger(__name__) from sat_frontends.tools.jid import JID class QuickTarotGame(object): - def __init__(self, parent, referee, players): - self._autoplay = None #XXX: use 0 to activate fake play, None else + self._autoplay = None # XXX: use 0 to activate fake play, None else self.parent = parent self.referee = referee self.players = players @@ -42,8 +42,8 @@ idx = (idx + 1) % len(self.players) self.left_nick = unicode(self.players[idx]) self.bottom_nick = unicode(self.player_nick) - self.selected = [] #Card choosed by the player (e.g. during ecart) - self.hand_size = 13 #number of cards in a hand + self.selected = [] # Card choosed by the player (e.g. during ecart) + self.hand_size = 13 # number of cards in a hand self.hand = [] self.to_show = [] self.state = None @@ -59,25 +59,27 @@ def getPlayerLocation(self, nick): """return player location (top,bottom,left or right)""" - for location in ['top','left','bottom','right']: - if getattr(self,'%s_nick' % location) == nick: + for location in ["top", "left", "bottom", "right"]: + if getattr(self, "%s_nick" % location) == nick: return location - assert(False) + assert False def loadCards(self): """Load all the cards in memory @param dir: directory where the PNG files are""" - self.cards={} - self.deck=[] - self.cards["atout"]={} #As Tarot is a french game, it's more handy & logical to keep french names - self.cards["pique"]={} #spade - self.cards["coeur"]={} #heart - self.cards["carreau"]={} #diamond - self.cards["trefle"]={} #club + self.cards = {} + self.deck = [] + self.cards[ + "atout" + ] = {} # As Tarot is a french game, it's more handy & logical to keep french names + self.cards["pique"] = {} # spade + self.cards["coeur"] = {} # heart + self.cards["carreau"] = {} # diamond + self.cards["trefle"] = {} # club def tarotGameNewHandler(self, hand): """Start a new game, with given hand""" - assert (len(self.hand) == 0) + assert len(self.hand) == 0 for suit, value in hand: self.hand.append(self.cards[suit, value]) self.hand.sort() @@ -93,7 +95,7 @@ self.to_show = [] for suit, value in cards: self.to_show.append(self.cards[suit, value]) - if game_stage == "chien" and data['attaquant'] == self.player_nick: + if game_stage == "chien" and data["attaquant"] == self.player_nick: self.state = "wait_for_ecart" else: self.state = "chien" @@ -113,10 +115,12 @@ if self._autoplay >= len(self.hand): self._autoplay = 0 card = self.hand[self._autoplay] - self.parent.host.bridge.tarotGamePlayCards(self.player_nick, self.referee, [(card.suit, card.value)], self.parent.profile) + self.parent.host.bridge.tarotGamePlayCards( + self.player_nick, self.referee, [(card.suit, card.value)], self.parent.profile + ) del self.hand[self._autoplay] self.state = "wait" - self._autoplay+=1 + self._autoplay += 1 def tarotGameScoreHandler(self, xml_data, winners, loosers): """Called at the end of a game @@ -130,7 +134,7 @@ if self.to_show: self.to_show = [] pl_cards = [] - if self.played[player] != None: #FIXME + if self.played[player] != None: # FIXME for pl in self.played: self.played[pl] = None for suit, value in cards: @@ -148,11 +152,10 @@ elif phase == "ecart": self.state = "ecart" else: - log.error ('INTERNAL ERROR: unmanaged game phase') + log.error("INTERNAL ERROR: unmanaged game phase") for suit, value in played_cards: self.hand.append(self.cards[suit, value]) self.hand.sort() self.__fakePlay() -
--- a/sat_frontends/quick_frontend/quick_games.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/quick_frontend/quick_games.py Wed Jun 27 20:14:46 2018 +0200 @@ -18,6 +18,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.core.log import getLogger + log = getLogger(__name__) from sat.core.i18n import _ @@ -36,23 +37,27 @@ @classmethod def registerSignals(cls, host): - def make_handler(suffix, signal): def handler(*args): if suffix in ("Started", "Players"): return cls.startedHandler(host, suffix, *args) return cls.genericHandler(host, signal, *args) + return handler for suffix in cls._signal_suffixes: signal = cls._signal_prefix + suffix - host.registerSignal(signal, handler=make_handler(suffix, signal), iface="plugin") + host.registerSignal( + signal, handler=make_handler(suffix, signal), iface="plugin" + ) @classmethod def startedHandler(cls, host, suffix, *args): room_jid, args, profile = jid.JID(args[0]), args[1:-1], args[-1] referee, players, args = args[0], args[1], args[2:] - chat_widget = host.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile) + chat_widget = host.widgets.getOrCreateWidget( + quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile + ) # update symbols if cls._game_name not in chat_widget.visible_states: @@ -62,7 +67,11 @@ contact_list = host.contact_lists[profile] for occupant in chat_widget.occupants: occupant_jid = jid.newResource(room_jid, occupant) - contact_list.setCache(occupant_jid, cls._game_name, symbols[index % len(symbols)] if occupant in players else None) + contact_list.setCache( + occupant_jid, + cls._game_name, + symbols[index % len(symbols)] if occupant in players else None, + ) chat_widget.update(occupant_jid) if suffix == "Players" or chat_widget.nick not in players: @@ -71,8 +80,12 @@ return # game panel is already there real_class = host.widgets.getRealClass(cls) if real_class == cls: - host.showDialog(_(u"A {game} activity between {players} has been started, but you couldn't take part because your client doesn't support it.").format(game=cls._game_name, players=', '.join(players)), - _(u"{game} Game").format(game=cls._game_name)) + host.showDialog( + _( + u"A {game} activity between {players} has been started, but you couldn't take part because your client doesn't support it." + ).format(game=cls._game_name, players=", ".join(players)), + _(u"{game} Game").format(game=cls._game_name), + ) return panel = real_class(chat_widget, referee, players, *args) chat_widget.games[cls._game_name] = panel @@ -86,7 +99,10 @@ try: game_panel = chat_widget.games[cls._game_name] except KeyError: - log.error("TODO: better game synchronisation - received signal %s but no panel is found" % signal) + log.error( + "TODO: better game synchronisation - received signal %s but no panel is found" + % signal + ) return else: getattr(game_panel, "%sHandler" % signal)(*args) @@ -95,23 +111,43 @@ class Tarot(RoomGame): _game_name = "Tarot" _signal_prefix = "tarotGame" - _signal_suffixes = ("Started", "Players", "New", "ChooseContrat", - "ShowCards", "YourTurn", "Score", "CardsPlayed", - "InvalidCards", - ) + _signal_suffixes = ( + "Started", + "Players", + "New", + "ChooseContrat", + "ShowCards", + "YourTurn", + "Score", + "CardsPlayed", + "InvalidCards", + ) class Quiz(RoomGame): _game_name = "Quiz" _signal_prefix = "quizGame" - _signal_suffixes = ("Started", "New", "Question", "PlayerBuzzed", - "PlayerSays", "AnswerResult", "TimerExpired", - "TimerRestarted", - ) + _signal_suffixes = ( + "Started", + "New", + "Question", + "PlayerBuzzed", + "PlayerSays", + "AnswerResult", + "TimerExpired", + "TimerRestarted", + ) class Radiocol(RoomGame): _game_name = "Radiocol" _signal_prefix = "radiocol" - _signal_suffixes = ("Started", "Players", "SongRejected", "Preload", - "Play", "NoUpload", "UploadOk") + _signal_suffixes = ( + "Started", + "Players", + "SongRejected", + "Preload", + "Play", + "NoUpload", + "UploadOk", + )
--- a/sat_frontends/quick_frontend/quick_list_manager.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/quick_frontend/quick_list_manager.py Wed Jun 27 20:14:46 2018 +0200 @@ -25,10 +25,14 @@ """ @param items (list): the suggested list of non tagged items - """ + """ self.tagged = [] - self.original = items[:] if items else [] # XXX: copy the list! It will be modified - self.untagged = items[:] if items else [] # XXX: copy the list! It will be modified + self.original = ( + items[:] if items else [] + ) # XXX: copy the list! It will be modified + self.untagged = ( + items[:] if items else [] + ) # XXX: copy the list! It will be modified self.untagged.sort() @property
--- a/sat_frontends/quick_frontend/quick_menus.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/quick_frontend/quick_menus.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,12 +19,16 @@ try: # FIXME: to be removed when an acceptable solution is here - unicode('') # XXX: unicode doesn't exist in pyjamas -except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options + unicode("") # XXX: unicode doesn't exist in pyjamas +except ( + TypeError, + AttributeError, +): # Error raised is not the same depending on pyjsbuild options unicode = str from sat.core.log import getLogger from sat.core.i18n import _, languageSwitch + log = getLogger(__name__) from sat_frontends.quick_frontend.constants import Const as C from collections import OrderedDict @@ -32,8 +36,9 @@ ## items ## + class MenuBase(object): - ACTIVE=True + ACTIVE = True def __init__(self, name, extra=None): """ @@ -61,7 +66,8 @@ class MenuItem(MenuBase): """A callable item in the menu""" - CALLABLE=False + + CALLABLE = False def __init__(self, name, name_i18n, extra=None, type_=None): """ @@ -83,7 +89,7 @@ @param caller: Menu caller """ - assert self.type is not None # if data collector are used, type must be set + assert self.type is not None # if data collector are used, type must be set data_collector = QuickMenusManager.getDataCollector(self.type) if data_collector is None: @@ -101,7 +107,6 @@ data[data_key] = unicode(getattr(caller, caller_attr)) return data - def call(self, caller, profile=C.PROF_KEY_NONE): """Execute the menu item @@ -113,7 +118,8 @@ class MenuItemDistant(MenuItem): """A MenuItem with a distant callback""" - CALLABLE=True + + CALLABLE = True def __init__(self, host, type_, name, name_i18n, id_, extra=None): """ @@ -129,14 +135,15 @@ self.id = id_ def call(self, caller, profile=C.PROF_KEY_NONE): - data = self.collectData(caller) + data = self.collectData(caller) log.debug("data collected: %s" % data) self.host.launchAction(self.id, data, profile=profile) class MenuItemLocal(MenuItem): """A MenuItem with a local callback""" - CALLABLE=True + + CALLABLE = True def __init__(self, type_, name, name_i18n, callback, extra=None): """ @@ -162,12 +169,14 @@ class MenuHook(MenuItemLocal): """A MenuItem which replace an expected item from backend""" + pass class MenuPlaceHolder(MenuItem): """A non existant menu which is used to keep a position""" - ACTIVE=False + + ACTIVE = False def __init__(self, name): MenuItem.__init__(self, name, name) @@ -175,10 +184,11 @@ class MenuSeparator(MenuItem): """A separation between items/categories""" - SEP_IDX=0 + + SEP_IDX = 0 def __init__(self): - MenuSeparator.SEP_IDX +=1 + MenuSeparator.SEP_IDX += 1 name = u"___separator_{}".format(MenuSeparator.SEP_IDX) MenuItem.__init__(self, name, name) @@ -187,7 +197,6 @@ class MenuContainer(MenuBase): - def __init__(self, name, extra=None): MenuBase.__init__(self, name, extra) self._items = OrderedDict() @@ -208,7 +217,10 @@ raise KeyError(item) def getOrCreate(self, item): - log.debug(u"MenuContainer getOrCreate: item=%s name=%s\nlist=%s" % (item, item.canonical, self._items.keys())) + log.debug( + u"MenuContainer getOrCreate: item=%s name=%s\nlist=%s" + % (item, item.canonical, self._items.keys()) + ) try: return self[item] except KeyError: @@ -255,6 +267,7 @@ class MenuType(MenuContainer): """A type which can hold other menus or categories""" + pass @@ -263,7 +276,10 @@ class QuickMenusManager(object): """Manage all the menus""" - _data_collectors={C.MENU_GLOBAL: None} # No data is associated with C.MENU_GLOBAL items + + _data_collectors = { + C.MENU_GLOBAL: None + } # No data is associated with C.MENU_GLOBAL items def __init__(self, host, menus=None, language=None): """ @@ -358,15 +374,28 @@ menu_container.replace(item) elif isinstance(container_item, MenuHook): # MenuHook must not be replaced - log.debug(u"ignoring menu at path [{}] because a hook is already in place".format(path)) + log.debug( + u"ignoring menu at path [{}] because a hook is already in place".format( + path + ) + ) else: log.error(u"Conflicting menus at path [{}]".format(path)) else: log.debug(u"Adding menu [{type_}] {path}".format(type_=type_, path=path)) menu_container.append(item) - self.host.callListeners('menu', type_, path, path_i18n, item) + self.host.callListeners("menu", type_, path, path_i18n, item) - def addMenu(self, type_, path, path_i18n=None, extra=None, top_extra=None, id_=None, callback=None): + def addMenu( + self, + type_, + path, + path_i18n=None, + extra=None, + top_extra=None, + id_=None, + callback=None, + ): """Add a menu item @param type_(unicode): same as in [sat.core.sat_main.SAT.importMenu] @@ -379,11 +408,15 @@ """ if path_i18n is None: path_i18n = self._getPathI18n(path) - assert bool(id_) ^ bool(callback) # we must have id_ xor callback defined + assert bool(id_) ^ bool(callback) # we must have id_ xor callback defined if id_: - menu_item = MenuItemDistant(self.host, type_, path[-1], path_i18n[-1], id_=id_, extra=extra) + menu_item = MenuItemDistant( + self.host, type_, path[-1], path_i18n[-1], id_=id_, extra=extra + ) else: - menu_item = MenuItemLocal(type_, path[-1], path_i18n[-1], callback=callback, extra=extra) + menu_item = MenuItemLocal( + type_, path[-1], path_i18n[-1], callback=callback, extra=extra + ) self.addMenuItem(type_, path[:-1], menu_item, path_i18n[:-1], top_extra) def addMenus(self, menus, top_extra=None): @@ -401,11 +434,17 @@ # TODO: manage icons for id_, type_, path, path_i18n, extra in menus: if callable(id_): - self.addMenu(type_, path, path_i18n, callback=id_, extra=extra, top_extra=top_extra) + self.addMenu( + type_, path, path_i18n, callback=id_, extra=extra, top_extra=top_extra + ) else: - self.addMenu(type_, path, path_i18n, id_=id_, extra=extra, top_extra=top_extra) + self.addMenu( + type_, path, path_i18n, id_=id_, extra=extra, top_extra=top_extra + ) - def addMenuHook(self, type_, path, path_i18n=None, extra=None, top_extra=None, callback=None): + def addMenuHook( + self, type_, path, path_i18n=None, extra=None, top_extra=None, callback=None + ): """Helper method to add a menu hook Menu hooks are local menus which override menu given by backend @@ -418,7 +457,9 @@ """ if path_i18n is None: path_i18n = self._getPathI18n(path) - menu_item = MenuHook(type_, path[-1], path_i18n[-1], callback=callback, extra=extra) + menu_item = MenuHook( + type_, path[-1], path_i18n[-1], callback=callback, extra=extra + ) self.addMenuItem(type_, path[:-1], menu_item, path_i18n[:-1], top_extra) log.info(u"Menu hook set on {path} ({type_})".format(path=path, type_=type_)) @@ -434,7 +475,9 @@ """ if path_i18n is None: path_i18n = self._getPathI18n(path) - last_container = self._createCategories(type_, path, path_i18n, top_extra=top_extra) + last_container = self._createCategories( + type_, path, path_i18n, top_extra=top_extra + ) last_container.setExtra(extra) return last_container
--- a/sat_frontends/quick_frontend/quick_profile_manager.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/quick_frontend/quick_profile_manager.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,6 +19,7 @@ from sat.core.i18n import _ from sat.core import log as logging + log = logging.getLogger(__name__) from sat_frontends.primitivus.constants import Const as C @@ -85,10 +86,9 @@ log.warning("No profile given to autoconnect") return self._autoconnect = True - self._autoconnect_profiles=[] + self._autoconnect_profiles = [] self._do_autoconnect(profile_keys) - def _do_autoconnect(self, profile_keys): """Connect automatically given profiles @@ -98,36 +98,39 @@ def authenticate_cb(data, cb_id, profile): - if C.bool(data.pop('validated', C.BOOL_FALSE)): + if C.bool(data.pop("validated", C.BOOL_FALSE)): self._autoconnect_profiles.append(profile) if len(self._autoconnect_profiles) == len(profile_keys): # all the profiles have been validated self.host.plug_profiles(self._autoconnect_profiles) else: # a profile is not validated, we go to manual mode - self._autoconnect=False + self._autoconnect = False self.host.actionManager(data, callback=authenticate_cb, profile=profile) def getProfileNameCb(profile): if not profile: # FIXME: this method is not handling manual mode correclty anymore # must be thought to be handled asynchronously - self._autoconnect = False # manual mode + self._autoconnect = False # manual mode msg = _("Trying to plug an unknown profile key ({})".format(profile_key)) log.warning(msg) - self.host.showDialog(_("Profile plugging in error"), msg, 'error') + self.host.showDialog(_("Profile plugging in error"), msg, "error") else: - self.host.launchAction(C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=profile) + self.host.launchAction( + C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=profile + ) def getProfileNameEb(failure): log.error(u"Can't retrieve profile name: {}".format(failure)) for profile_key in profile_keys: - self.host.bridge.profileNameGet(profile_key, callback=getProfileNameCb, errback=getProfileNameEb) - + self.host.bridge.profileNameGet( + profile_key, callback=getProfileNameCb, errback=getProfileNameEb + ) def getParamError(self, dummy): - self.host.showDialog(_(u"Error"), _("Can't get profile parameter"), 'error') + self.host.showDialog(_(u"Error"), _("Can't get profile parameter"), "error") ## Helping methods ## @@ -142,7 +145,9 @@ elif reason == "CancelError": message = _("Profile creation cancelled by backend") elif reason == "ValueError": - message = _("You profile name is not valid") # TODO: print a more informative message (empty name, name starting with '@') + message = _( + "You profile name is not valid" + ) # TODO: print a more informative message (empty name, name starting with '@') else: message = _("Can't create profile ({})").format(reason) return message @@ -150,7 +155,9 @@ def _deleteProfile(self): """Delete the currently selected profile""" if self.current.profile: - self.host.bridge.asyncDeleteProfile(self.current.profile, callback=self.refillProfiles) + self.host.bridge.asyncDeleteProfile( + self.current.profile, callback=self.refillProfiles + ) self.resetFields() ## workflow methods (events occuring during the profiles selection) ## @@ -160,12 +167,20 @@ def _onConnectProfiles(self): """Connect the profiles and start the main widget""" if self._autoconnect: - self.host.showDialog(_('Internal error'), _("You can't connect manually and automatically at the same time"), 'error') + self.host.showDialog( + _("Internal error"), + _("You can't connect manually and automatically at the same time"), + "error", + ) return self.updateConnectionParams() profiles = self.getProfiles() if not profiles: - self.host.showDialog(_('No profile selected'), _('You need to create and select at least one profile before connecting'), 'error') + self.host.showDialog( + _("No profile selected"), + _("You need to create and select at least one profile before connecting"), + "error", + ) else: # All profiles in the list are already validated, so we can plug them directly self.host.plug_profiles(profiles) @@ -175,8 +190,20 @@ @param profile: %(doc_profile)s """ - self.host.bridge.asyncGetParamA("JabberID", "Connection", profile_key=profile, callback=self.setJID, errback=self.getParamError) - self.host.bridge.asyncGetParamA("Password", "Connection", profile_key=profile, callback=self.setPassword, errback=self.getParamError) + self.host.bridge.asyncGetParamA( + "JabberID", + "Connection", + profile_key=profile, + callback=self.setJID, + errback=self.getParamError, + ) + self.host.bridge.asyncGetParamA( + "Password", + "Connection", + profile_key=profile, + callback=self.setPassword, + errback=self.getParamError, + ) def updateConnectionParams(self): """Check if connection parameters have changed, and update them if so""" @@ -185,18 +212,24 @@ password = self.getPassword() if login != self.current.login and self.current.login is not None: self.current.login = login - self.host.bridge.setParam("JabberID", login, "Connection", profile_key=self.current.profile) + self.host.bridge.setParam( + "JabberID", login, "Connection", profile_key=self.current.profile + ) log.info(u"login updated for profile [{}]".format(self.current.profile)) if password != self.current.password and self.current.password is not None: self.current.password = password - self.host.bridge.setParam("Password", password, "Connection", profile_key=self.current.profile) - log.info(u"password updated for profile [{}]".format(self.current.profile)) + self.host.bridge.setParam( + "Password", password, "Connection", profile_key=self.current.profile + ) + log.info( + u"password updated for profile [{}]".format(self.current.profile) + ) ## graphic updates (should probably be overriden in frontends) ## def resetFields(self): """Set profile to None, and reset fields""" - self.current.profile=None + self.current.profile = None self.setJID("") self.setPassword("") @@ -222,7 +255,6 @@ """Update the list of profiles""" raise NotImplementedError - def getJID(self): """Get current jid
--- a/sat_frontends/quick_frontend/quick_utils.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/quick_frontend/quick_utils.py Wed Jun 27 20:14:46 2018 +0200 @@ -21,6 +21,7 @@ from os.path import exists, splitext from optparse import OptionParser + def getNewPath(path): """ Check if path exists, and find a non existant path if needed """ idx = 2 @@ -31,21 +32,23 @@ new_path = "%s_%d%s" % (root, idx, ext) if not exists(new_path): return new_path - idx+=1 + idx += 1 + def check_options(): """Check command line options""" - usage = _(""" + usage = _( + """ %prog [options] %prog --help for options list - """) - parser = OptionParser(usage=usage) # TODO: use argparse + """ + ) + parser = OptionParser(usage=usage) # TODO: use argparse parser.add_option("-p", "--profile", help=_("Select the profile to use")) (options, args) = parser.parse_args() if options.profile: - options.profile = options.profile.decode('utf-8') + options.profile = options.profile.decode("utf-8") return options -
--- a/sat_frontends/quick_frontend/quick_widgets.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/quick_frontend/quick_widgets.py Wed Jun 27 20:14:46 2018 +0200 @@ -18,19 +18,23 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from sat_frontends.quick_frontend.constants import Const as C -NEW_INSTANCE_SUFF = '_new_instance_' +NEW_INSTANCE_SUFF = "_new_instance_" classes_map = {} try: # FIXME: to be removed when an acceptable solution is here - unicode('') # XXX: unicode doesn't exist in pyjamas -except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options + unicode("") # XXX: unicode doesn't exist in pyjamas +except ( + TypeError, + AttributeError, +): # Error raised is not the same depending on pyjsbuild options unicode = str @@ -77,7 +81,9 @@ except KeyError: cls = class_ if cls is None: - raise exceptions.InternalError("There is not class registered for {}".format(class_)) + raise exceptions.InternalError( + "There is not class registered for {}".format(class_) + ) return cls def getRootHash(self, hash_): @@ -109,7 +115,10 @@ filter_hash = None for w_hash, w in widgets_map.iteritems(): if profiles is None or w.profiles.intersection(profiles): - if filter_hash is not None and self.getRootHash(w_hash) != filter_hash: + if ( + filter_hash is not None + and self.getRootHash(w_hash) != filter_hash + ): continue yield w @@ -161,39 +170,45 @@ cls = self.getRealClass(class_) ## arguments management ## - _args = [self.host, target] + list(args) or [] # FIXME: check if it's really necessary to use optional args + _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: - raise ValueError("You can't have 'profile' and 'profiles' keys at the same time") + if "profiles" in _kwargs and "profile" in _kwargs: + raise ValueError( + "You can't have 'profile' and 'profiles' keys at the same time" + ) try: - _kwargs['profiles'] = [_kwargs.pop('profile')] + _kwargs["profiles"] = [_kwargs.pop("profile")] except KeyError: - if not 'profiles' in _kwargs: - _kwargs['profiles'] = None + if not "profiles" in _kwargs: + _kwargs["profiles"] = None - #on_new_widget tell what to do for the new widget creation + # on_new_widget tell what to do for the new widget creation try: - on_new_widget = _kwargs.pop('on_new_widget') + 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 + # on_existing_widget tell what to do when the widget already exists try: - on_existing_widget = _kwargs.pop('on_existing_widget') + 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') + hash_ = _kwargs.pop("force_hash") except KeyError: - hash_ = cls.getWidgetHash(target, _kwargs['profiles']) + hash_ = cls.getWidgetHash(target, _kwargs["profiles"]) ## widget creation or retrieval ## - widgets_map = self._widgets.setdefault(cls.__name__, {}) # we sorts widgets by classes + 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 + widget = None # if the class is not SINGLE, we always create a new widget else: try: widget = widgets_map[hash_] @@ -225,16 +240,20 @@ # 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 + 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") + raise ValueError( + "force_hash option can't be used with on_existing_widget=RECREATE" + ) - new_kwargs['on_new_widget'] = on_new_widget + new_kwargs["on_new_widget"] = on_new_widget # XXX: keep up-to-date if new special kwargs are added (i.e.: delete these keys here) - new_kwargs['on_existing_widget'] = C.WIDGET_RAISE + new_kwargs["on_existing_widget"] = C.WIDGET_RAISE try: recreateArgs = widget.recreateArgs except AttributeError: @@ -243,18 +262,28 @@ recreateArgs(args, new_kwargs) hash_idx = 1 while True: - new_kwargs['force_hash'] = "{}{}{}".format(hash_, NEW_INSTANCE_SUFF, hash_idx) + new_kwargs["force_hash"] = "{}{}{}".format( + hash_, NEW_INSTANCE_SUFF, hash_idx + ) try: - widget = self.getOrCreateWidget(class_, target, *args, **new_kwargs) + 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'])) + log.debug( + u"Widget already exists, a new one has been recreated with hash {}".format( + new_kwargs["force_hash"] + ) + ) break elif callable(on_existing_widget): on_existing_widget(widget) else: - raise exceptions.InternalError("Unexpected on_existing_widget value ({})".format(on_existing_widget)) + raise exceptions.InternalError( + "Unexpected on_existing_widget value ({})".format(on_existing_widget) + ) return widget @@ -286,9 +315,10 @@ class QuickWidget(object): """generic widget base""" - SINGLE=True # if True, there can be only one widget per target(s) - PROFILES_MULTIPLE=False # If True, this widget can handle several profiles at once - PROFILES_ALLOW_NONE=False # If True, this widget can be used without profile + + SINGLE = True # if True, there can be only one widget per target(s) + PROFILES_MULTIPLE = False # If True, this widget can handle several profiles at once + PROFILES_ALLOW_NONE = False # If True, this widget can be used without profile def __init__(self, host, target, profiles=None): """ @@ -317,7 +347,11 @@ @property def profile(self): - assert len(self.profiles) == 1 and not self.PROFILES_MULTIPLE and not self.PROFILES_ALLOW_NONE + assert ( + len(self.profiles) == 1 + and not self.PROFILES_MULTIPLE + and not self.PROFILES_ALLOW_NONE + ) return list(self.profiles)[0] def addTarget(self, target): @@ -350,7 +384,7 @@ @param profiles: profile(s) associated to target, see __init__ docstring @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 + return unicode(target) # by defaut, there is one hash for one target def onDelete(self, *args, **kwargs): """Called when a widget is being deleted @@ -360,4 +394,3 @@ """ log.debug(u"widget {} deleted".format(self)) return True -
--- a/sat_frontends/tools/composition.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/tools/composition.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,71 +20,90 @@ """ # Map the messages recipient types to their properties. -RECIPIENT_TYPES = {"To": {"desc": "Direct recipients", "optional": False}, - "Cc": {"desc": "Carbon copies", "optional": True}, - "Bcc": {"desc": "Blind carbon copies", "optional": True}} +RECIPIENT_TYPES = { + "To": {"desc": "Direct recipients", "optional": False}, + "Cc": {"desc": "Carbon copies", "optional": True}, + "Bcc": {"desc": "Blind carbon copies", "optional": True}, +} # Rich text buttons icons and descriptions RICH_BUTTONS = { "bold": {"tip": "Bold", "icon": "media/icons/dokuwiki/toolbar/16/bold.png"}, "italic": {"tip": "Italic", "icon": "media/icons/dokuwiki/toolbar/16/italic.png"}, - "underline": {"tip": "Underline", "icon": "media/icons/dokuwiki/toolbar/16/underline.png"}, + "underline": { + "tip": "Underline", + "icon": "media/icons/dokuwiki/toolbar/16/underline.png", + }, "code": {"tip": "Code", "icon": "media/icons/dokuwiki/toolbar/16/mono.png"}, - "strikethrough": {"tip": "Strikethrough", "icon": "media/icons/dokuwiki/toolbar/16/strike.png"}, + "strikethrough": { + "tip": "Strikethrough", + "icon": "media/icons/dokuwiki/toolbar/16/strike.png", + }, "heading": {"tip": "Heading", "icon": "media/icons/dokuwiki/toolbar/16/hequal.png"}, - "numberedlist": {"tip": "Numbered List", "icon": "media/icons/dokuwiki/toolbar/16/ol.png"}, + "numberedlist": { + "tip": "Numbered List", + "icon": "media/icons/dokuwiki/toolbar/16/ol.png", + }, "list": {"tip": "List", "icon": "media/icons/dokuwiki/toolbar/16/ul.png"}, "link": {"tip": "Link", "icon": "media/icons/dokuwiki/toolbar/16/linkextern.png"}, - "horizontalrule": {"tip": "Horizontal rule", "icon": "media/icons/dokuwiki/toolbar/16/hr.png"}, - "image": {"tip": "Image", "icon": "media/icons/dokuwiki/toolbar/16/image.png"}, - } + "horizontalrule": { + "tip": "Horizontal rule", + "icon": "media/icons/dokuwiki/toolbar/16/hr.png", + }, + "image": {"tip": "Image", "icon": "media/icons/dokuwiki/toolbar/16/image.png"}, +} # Define here your rich text syntaxes, the key must match the ones used in button. # Tupples values must have 3 elements : prefix to the selection or cursor # position, sample text to write if the marker is not applied on a selection, # suffix to the selection or cursor position. # FIXME: must not be hard-coded like this -RICH_SYNTAXES = {"markdown": {"bold": ("**", "bold", "**"), - "italic": ("*", "italic", "*"), - "code": ("`", "code", "`"), - "heading": ("\n# ", "Heading 1", "\n## Heading 2\n"), - "link": ("[desc](", "link", ")"), - "list": ("\n* ", "item", "\n + subitem\n"), - "horizontalrule": ("\n***\n", "", ""), - "image": ("![desc](", "path", ")"), - }, - "bbcode": {"bold": ("[b]", "bold", "[/b]"), - "italic": ("[i]", "italic", "[/i]"), - "underline": ("[u]", "underline", "[/u]"), - "code": ("[code]", "code", "[/code]"), - "strikethrough": ("[s]", "strikethrough", "[/s]"), - "link": ("[url=", "link", "]desc[/url]"), - "list": ("\n[list] [*]", "item 1", " [*]item 2 [/list]\n"), - "image": ("[img alt=\"desc\]", "path", "[/img]"), - }, - "dokuwiki": {"bold": ("**", "bold", "**"), - "italic": ("//", "italic", "//"), - "underline": ("__", "underline", "__"), - "code": ("<code>", "code", "</code>"), - "strikethrough": ("<del>", "strikethrough", "</del>"), - "heading": ("\n==== ", "Heading 1", " ====\n=== Heading 2 ===\n"), - "link": ("[[", "link", "|desc]]"), - "list": ("\n * ", "item\n", "\n * subitem\n"), - "horizontalrule": ("\n----\n", "", ""), - "image": ("{{", "path", " |desc}}"), - }, - "XHTML": {"bold": ("<b>", "bold", "</b>"), - "italic": ("<i>", "italic", "</i>"), - "underline": ("<u>", "underline", "</u>"), - "code": ("<pre>", "code", "</pre>"), - "strikethrough": ("<s>", "strikethrough", "</s>"), - "heading": ("\n<h3>", "Heading 1", "</h3>\n<h4>Heading 2</h4>\n"), - "link": ("<a href=\"", "link", "\">desc</a>"), - "list": ("\n<ul><li>", "item 1", "</li><li>item 2</li></ul>\n"), - "horizontalrule": ("\n<hr/>\n", "", ""), - "image": ("<img src=\"", "path", "\" alt=\"desc\"/>"), - } - } +RICH_SYNTAXES = { + "markdown": { + "bold": ("**", "bold", "**"), + "italic": ("*", "italic", "*"), + "code": ("`", "code", "`"), + "heading": ("\n# ", "Heading 1", "\n## Heading 2\n"), + "link": ("[desc](", "link", ")"), + "list": ("\n* ", "item", "\n + subitem\n"), + "horizontalrule": ("\n***\n", "", ""), + "image": ("![desc](", "path", ")"), + }, + "bbcode": { + "bold": ("[b]", "bold", "[/b]"), + "italic": ("[i]", "italic", "[/i]"), + "underline": ("[u]", "underline", "[/u]"), + "code": ("[code]", "code", "[/code]"), + "strikethrough": ("[s]", "strikethrough", "[/s]"), + "link": ("[url=", "link", "]desc[/url]"), + "list": ("\n[list] [*]", "item 1", " [*]item 2 [/list]\n"), + "image": ('[img alt="desc\]', "path", "[/img]"), + }, + "dokuwiki": { + "bold": ("**", "bold", "**"), + "italic": ("//", "italic", "//"), + "underline": ("__", "underline", "__"), + "code": ("<code>", "code", "</code>"), + "strikethrough": ("<del>", "strikethrough", "</del>"), + "heading": ("\n==== ", "Heading 1", " ====\n=== Heading 2 ===\n"), + "link": ("[[", "link", "|desc]]"), + "list": ("\n * ", "item\n", "\n * subitem\n"), + "horizontalrule": ("\n----\n", "", ""), + "image": ("{{", "path", " |desc}}"), + }, + "XHTML": { + "bold": ("<b>", "bold", "</b>"), + "italic": ("<i>", "italic", "</i>"), + "underline": ("<u>", "underline", "</u>"), + "code": ("<pre>", "code", "</pre>"), + "strikethrough": ("<s>", "strikethrough", "</s>"), + "heading": ("\n<h3>", "Heading 1", "</h3>\n<h4>Heading 2</h4>\n"), + "link": ('<a href="', "link", '">desc</a>'), + "list": ("\n<ul><li>", "item 1", "</li><li>item 2</li></ul>\n"), + "horizontalrule": ("\n<hr/>\n", "", ""), + "image": ('<img src="', "path", '" alt="desc"/>'), + }, +} # Define here the commands that are supported by the WYSIWYG edition. # Keys must be the same than the ones used in RICH_SYNTAXES["XHTML"]. @@ -93,21 +112,21 @@ # - a tuple (cmd, prompt, arg) with cmd the name of the command, # prompt the text to display for asking a user input and arg is the # value to use directly without asking the user if prompt is empty. -COMMANDS = {"bold": "bold", - "italic": "italic", - "underline": "underline", - "code": ("formatBlock", "", "pre"), - "strikethrough": "strikeThrough", - "heading": ("heading", "Please specify the heading level (h1, h2, h3...)", ""), - "link": ("createLink", "Please specify an URL", ""), - "list": "insertUnorderedList", - "horizontalrule": "insertHorizontalRule", - "image": ("insertImage", "Please specify an image path", ""), - } +COMMANDS = { + "bold": "bold", + "italic": "italic", + "underline": "underline", + "code": ("formatBlock", "", "pre"), + "strikethrough": "strikeThrough", + "heading": ("heading", "Please specify the heading level (h1, h2, h3...)", ""), + "link": ("createLink", "Please specify an URL", ""), + "list": "insertUnorderedList", + "horizontalrule": "insertHorizontalRule", + "image": ("insertImage", "Please specify an image path", ""), +} # These values should be equal to the ones in plugin_misc_text_syntaxes # FIXME: should the plugin import them from here to avoid duplicity? Importing # the plugin's values from here is not possible because Libervia would fail. PARAM_KEY_COMPOSITION = "Composition" PARAM_NAME_SYNTAX = "Syntax" -
--- a/sat_frontends/tools/css_color.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/tools/css_color.py Wed Jun 27 20:14:46 2018 +0200 @@ -18,6 +18,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.core.log import getLogger + log = getLogger(__name__) @@ -167,7 +168,7 @@ u"wheat": u"f5deb3", u"whitesmoke": u"f5f5f5", u"yellowgreen": u"9acd32", - u"rebeccapurple": u"663399" + u"rebeccapurple": u"663399", } DEFAULT = u"000000" @@ -185,23 +186,23 @@ If value can't be parsed, a warning message is logged, and DEFAULT is returned """ raw_value = raw_value.strip().lower() - if raw_value.startswith(u'#'): + if raw_value.startswith(u"#"): # we have a hexadecimal value str_value = raw_value[1:] - if len(raw_value) in (3,4): - str_value = u''.join([2*v for v in str_value]) - elif raw_value.startswith(u'rgb'): - left_p = raw_value.find(u'(') - right_p = raw_value.find(u')') - rgb_values = [v.strip() for v in raw_value[left_p+1:right_p].split(',')] - expected_len = 4 if raw_value.startswith(u'rgba') else 3 + if len(raw_value) in (3, 4): + str_value = u"".join([2 * v for v in str_value]) + elif raw_value.startswith(u"rgb"): + left_p = raw_value.find(u"(") + right_p = raw_value.find(u")") + rgb_values = [v.strip() for v in raw_value[left_p + 1 : right_p].split(",")] + expected_len = 4 if raw_value.startswith(u"rgba") else 3 if len(rgb_values) != expected_len: log.warning(u"incorrect value: {}".format(raw_value)) str_value = DEFAULT else: int_values = [] for rgb_v in rgb_values: - p_idx = rgb_v.find(u'%') + p_idx = rgb_v.find(u"%") if p_idx == -1: # base 10 value try: @@ -222,8 +223,8 @@ except ValueError: log.warning(u"invalid percent value: {}".format(rgb_v)) int_values.append(0) - str_value = u''.join([u"{:02x}".format(v) for v in int_values]) - elif raw_value.startswith(u'hsl'): + str_value = u"".join([u"{:02x}".format(v) for v in int_values]) + elif raw_value.startswith(u"hsl"): log.warning(u"hue-saturation-lightness not handled yet") # TODO str_value = DEFAULT else: @@ -236,4 +237,9 @@ if as_string: return str_value else: - return tuple([int(str_value[i]+str_value[i+1], 16) for i in xrange(0, len(str_value), 2)]) + return tuple( + [ + int(str_value[i] + str_value[i + 1], 16) + for i in xrange(0, len(str_value), 2) + ] + )
--- a/sat_frontends/tools/games.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/tools/games.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,7 +19,13 @@ """This library help manage general games (e.g. card games) and it is shared by the frontends""" -SUITS_ORDER = ['pique', 'coeur', 'trefle', 'carreau', 'atout'] # I have switched the usual order 'trefle' and 'carreau' because card are more easy to see if suit colour change (black, red, black, red) +SUITS_ORDER = [ + "pique", + "coeur", + "trefle", + "carreau", + "atout", +] # I have switched the usual order 'trefle' and 'carreau' because card are more easy to see if suit colour change (black, red, black, red) VALUES_ORDER = [str(i) for i in xrange(1, 11)] + ["valet", "cavalier", "dame", "roi"] @@ -58,12 +64,12 @@ idx1 = SUITS_ORDER.index(self.suit) idx2 = SUITS_ORDER.index(other.suit) return idx1.__cmp__(idx2) - if self.suit == 'atout': - if self.value == other.value == 'excuse': + if self.suit == "atout": + if self.value == other.value == "excuse": return 0 - if self.value == 'excuse': + if self.value == "excuse": return -1 - if other.value == 'excuse': + if other.value == "excuse": return 1 return int(self.value).__cmp__(int(other.value)) # at this point we have the same suit which is not 'atout'
--- a/sat_frontends/tools/host_listener.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/tools/host_listener.py Wed Jun 27 20:14:46 2018 +0200 @@ -22,6 +22,7 @@ listeners = [] + def addListener(cb): """Add a listener which will be called when host is ready @@ -29,6 +30,7 @@ """ listeners.append(cb) + def callListeners(host): """Must be called by frontend when host is ready.
--- a/sat_frontends/tools/jid.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/tools/jid.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,7 +20,7 @@ # hack to use this module with pyjamas try: - unicode('') # XXX: unicode doesn't exist in pyjamas + unicode("") # XXX: unicode doesn't exist in pyjamas # normal version class BaseJID(unicode): @@ -33,19 +33,23 @@ def _parse(self): """Find node domain and resource""" - node_end = self.find('@') + node_end = self.find("@") if node_end < 0: node_end = 0 - domain_end = self.find('/') + domain_end = self.find("/") if domain_end == 0: raise ValueError("a jid can't start with '/'") if domain_end == -1: domain_end = len(self) self.node = self[:node_end] or None - self.domain = self[(node_end + 1) if node_end else 0:domain_end] - self.resource = self[domain_end + 1:] or None + self.domain = self[(node_end + 1) if node_end else 0 : domain_end] + self.resource = self[domain_end + 1 :] or None + -except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options +except ( + TypeError, + AttributeError, +): # Error raised is not the same depending on pyjsbuild options # pyjamas version class BaseJID(object): @@ -61,29 +65,33 @@ def __eq__(self, other): if not isinstance(other, JID): return False - return (self.node == other.node - and self.domain == other.domain - and self.resource == other.resource) + return ( + self.node == other.node + and self.domain == other.domain + and self.resource == other.resource + ) def __hash__(self): - return hash('JID<{}>'.format(self.__internal_str)) + return hash("JID<{}>".format(self.__internal_str)) def find(self, *args): return self.__internal_str.find(*args) def _parse(self): """Find node domain and resource""" - node_end = self.__internal_str.find('@') + node_end = self.__internal_str.find("@") if node_end < 0: node_end = 0 - domain_end = self.__internal_str.find('/') + domain_end = self.__internal_str.find("/") if domain_end == 0: raise ValueError("a jid can't start with '/'") if domain_end == -1: domain_end = len(self.__internal_str) self.node = self.__internal_str[:node_end] or None - self.domain = self.__internal_str[(node_end + 1) if node_end else 0:domain_end] - self.resource = self.__internal_str[domain_end + 1:] or None + self.domain = self.__internal_str[ + (node_end + 1) if node_end else 0 : domain_end + ] + self.resource = self.__internal_str[domain_end + 1 :] or None class JID(BaseJID): @@ -98,9 +106,9 @@ """Naive normalization before instantiating and parsing the JID""" if not jid_str: return jid_str - tokens = jid_str.split('/') + tokens = jid_str.split("/") tokens[0] = tokens[0].lower() # force node and domain to lower-case - return '/'.join(tokens) + return "/".join(tokens) @property def bare(self):
--- a/sat_frontends/tools/misc.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/tools/misc.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,7 +19,6 @@ class InputHistory(object): - def _updateInputHistory(self, text=None, step=None, callback=None, mode=""): """Update the lists of previously sent messages. Several lists can be handled as they are stored in a dictionary, the argument "mode" being @@ -92,4 +91,3 @@ def unused(self): """Return flags which has not been used yet""" return self.flags.difference(self._used_flags) -
--- a/sat_frontends/tools/strings.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/tools/strings.py Wed Jun 27 20:14:46 2018 +0200 @@ -20,7 +20,9 @@ import re # Regexp from http://daringfireball.net/2010/07/improved_regex_for_matching_urls -RE_URL = re.compile(r"""(?i)\b((?:[a-z]{3,}://|(www|ftp)\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/|mailto:|xmpp:)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?]))""") +RE_URL = re.compile( + r"""(?i)\b((?:[a-z]{3,}://|(www|ftp)\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/|mailto:|xmpp:)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?]))""" +) # TODO: merge this class with an other module or at least rename it (strings is not a good name) @@ -36,7 +38,7 @@ dict_ = {} if "/" in url: # keep the part after the last "/" - url = url[url.rindex("/") + 1:] + url = url[url.rindex("/") + 1 :] if url.startswith("?"): # remove the first "?" url = url[1:] @@ -60,8 +62,9 @@ url = match.group(0) if not re.match(r"""[a-z]{3,}://|mailto:|xmpp:""", url): url = "http://" + url - target = ' target="_blank"' if new_target else '' + target = ' target="_blank"' if new_target else "" return '<a href="%s"%s class="url">%s</a>' % (url, target, match.group(0)) + return RE_URL.sub(repl, string) @@ -74,6 +77,7 @@ def repl(match): url = match.group(1) return '<a href="%s" target="_blank">%s</a>' % (url, match.group(0)) + pattern = r"""<img[^>]* src="([^"]+)"[^>]*>""" return re.sub(pattern, repl, string) @@ -96,4 +100,3 @@ for url, new_url in subs: xhtml = xhtml.replace(url, new_url) return xhtml -
--- a/sat_frontends/tools/xmltools.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/tools/xmltools.py Wed Jun 27 20:14:46 2018 +0200 @@ -29,20 +29,19 @@ @return: plain XML """ root_elt = doc.documentElement - if root_elt.hasAttribute('style'): - styles_raw = root_elt.getAttribute('style') - styles = styles_raw.split(';') + if root_elt.hasAttribute("style"): + styles_raw = root_elt.getAttribute("style") + styles = styles_raw.split(";") new_styles = [] for style in styles: try: - key, value = style.split(':') + key, value = style.split(":") except ValueError: continue - if key.strip().lower() == 'display': - value = 'inline' - new_styles.append('%s: %s' % (key.strip(), value.strip())) - root_elt.setAttribute('style', "; ".join(new_styles)) + if key.strip().lower() == "display": + value = "inline" + new_styles.append("%s: %s" % (key.strip(), value.strip())) + root_elt.setAttribute("style", "; ".join(new_styles)) else: - root_elt.setAttribute('style', 'display: inline') + root_elt.setAttribute("style", "display: inline") return root_elt.toxml() -
--- a/sat_frontends/tools/xmlui.py Wed Jun 27 07:51:29 2018 +0200 +++ b/sat_frontends/tools/xmlui.py Wed Jun 27 20:14:46 2018 +0200 @@ -19,15 +19,17 @@ from sat.core.i18n import _ from sat.core.log import getLogger + log = getLogger(__name__) from sat_frontends.quick_frontend.constants import Const as C from sat.core import exceptions class_map = {} -CLASS_PANEL = 'panel' -CLASS_DIALOG = 'dialog' -CURRENT_LABEL = 'current_label' +CLASS_PANEL = "panel" +CLASS_DIALOG = "dialog" +CURRENT_LABEL = "current_label" + class InvalidXMLUI(Exception): pass @@ -53,31 +55,37 @@ class Widget(object): """base Widget""" + pass class EmptyWidget(Widget): """Just a placeholder widget""" + pass class TextWidget(Widget): """Non interactive text""" + pass class LabelWidget(Widget): """Non interactive text""" + pass class JidWidget(Widget): """Jabber ID""" + pass class DividerWidget(Widget): """Separator""" + pass @@ -86,6 +94,7 @@ often called Edit in toolkits """ + pass @@ -94,11 +103,13 @@ often called Edit in toolkits """ + pass class PasswordWidget(Widget): """Input widget with require a masked string""" + pass @@ -106,6 +117,7 @@ """Input widget with require a long, possibly multilines string often called TextArea in toolkits """ + pass @@ -113,26 +125,31 @@ """Input widget with require a boolean value often called CheckBox in toolkits """ + pass class IntWidget(Widget): """Input widget with require an integer""" + pass class ButtonWidget(Widget): """A clickable widget""" + pass class ListWidget(Widget): """A widget able to show/choose one or several strings in a list""" + pass class JidsListWidget(Widget): """A widget able to show/choose one or several strings in a list""" + pass @@ -152,11 +169,13 @@ class PairsContainer(Container): """Widgets are disposed in rows of two (usually label/input)""" + pass class LabelContainer(Container): """Widgets are associated with label or empty widget""" + pass @@ -165,15 +184,19 @@ Often called Notebook in toolkits """ + pass + class VerticalContainer(Container): """Widgets are disposed vertically""" + pass class AdvancedListContainer(Container): """Widgets are disposed in rows with advaned features""" + pass @@ -206,11 +229,13 @@ class MessageDialog(Dialog): """Dialog with a OK/Cancel type configuration""" + pass class NoteDialog(Dialog): """Short message which doesn't need user confirmation to disappear""" + pass @@ -226,6 +251,7 @@ class FileDialog(Dialog): """Dialog with a OK/Cancel type configuration""" + pass @@ -235,7 +261,15 @@ This class must not be instancied directly """ - def __init__(self, host, parsed_dom, title=None, flags=None, callback=None, profile=C.PROF_KEY_NONE): + def __init__( + self, + host, + parsed_dom, + title=None, + flags=None, + callback=None, + profile=C.PROF_KEY_NONE, + ): """Initialise the XMLUI instance @param host: %(doc_host)s @@ -250,7 +284,7 @@ dialog closing or new xmlui to display, or other action (you can call host.actionManager) """ self.host = host - top=parsed_dom.documentElement + top = parsed_dom.documentElement self.session_id = top.getAttribute("session_id") or None self.submit_id = top.getAttribute("submit") or None self.xmlui_title = title or top.getAttribute("title") or u"" @@ -298,13 +332,17 @@ if self.submit_id is None: raise ValueError("Can't submit is self.submit_id is not set") if "session_id" in data: - raise ValueError("session_id must no be used in data, it is automaticaly filled with self.session_id if present") + raise ValueError( + "session_id must no be used in data, it is automaticaly filled with self.session_id if present" + ) if self.session_id is not None: data["session_id"] = self.session_id self._xmluiLaunchAction(self.submit_id, data) def _xmluiLaunchAction(self, action_id, data): - self.host.launchAction(action_id, data, callback=self.callback, profile=self.profile) + self.host.launchAction( + action_id, data, callback=self.callback, profile=self.profile + ) def _xmluiClose(self): """Close the window/popup/... where the constructor XMLUI is @@ -317,7 +355,7 @@ class ValueGetter(object): """dict like object which return values of widgets""" - def __init__(self, widgets, attr='value'): + def __init__(self, widgets, attr="value"): self.attr = attr self.widgets = widgets @@ -338,18 +376,31 @@ @property widget_factory: factory to create frontend-specific widgets @property dialog_factory: factory to create frontend-specific dialogs """ + widget_factory = None - def __init__(self, host, parsed_dom, title=None, flags=None, callback=None, ignore=None, whitelist=None, profile=C.PROF_KEY_NONE): + def __init__( + self, + host, + parsed_dom, + title=None, + flags=None, + callback=None, + ignore=None, + whitelist=None, + profile=C.PROF_KEY_NONE, + ): """ @param title(unicode, None): title of the @property widgets(dict): widget name => widget map @property widget_value(ValueGetter): retrieve widget value from it's name """ - super(XMLUIPanel, self).__init__(host, parsed_dom, title=title, flags=flags, callback=callback, profile=profile) + super(XMLUIPanel, self).__init__( + host, parsed_dom, title=title, flags=flags, callback=callback, profile=profile + ) self.ctrl_list = {} # input widget, used mainly for forms - self.widgets = {} # allow to access any named widgets + self.widgets = {} # allow to access any named widgets self.widget_value = ValueGetter(self.widgets) self._main_cont = None if ignore is None: @@ -357,7 +408,9 @@ self._ignore = ignore if whitelist is not None: if ignore: - raise exceptions.InternalError('ignore and whitelist must not be used at the same time') + raise exceptions.InternalError( + "ignore and whitelist must not be used at the same time" + ) self._whitelist = whitelist else: self._whitelist = None @@ -377,7 +430,7 @@ raise ValueError(_("XMLUI can have only one main container")) self._main_cont = value - def _parseChilds(self, _xmlui_parent, current_node, wanted = ('container',), data = None): + def _parseChilds(self, _xmlui_parent, current_node, wanted=("container",), data=None): """Recursively parse childNodes of an element @param _xmlui_parent: widget container with '_xmluiAppend' method @@ -389,82 +442,99 @@ if data is None: data = {} if wanted and not node.nodeName in wanted: - raise InvalidXMLUI('Unexpected node: [%s]' % node.nodeName) + raise InvalidXMLUI("Unexpected node: [%s]" % node.nodeName) if node.nodeName == "container": - type_ = node.getAttribute('type') - if _xmlui_parent is self and type_ != 'vertical': + type_ = node.getAttribute("type") + if _xmlui_parent is self and type_ != "vertical": # main container is not a VerticalContainer and we want one, so we create one to wrap it _xmlui_parent = self.widget_factory.createVerticalContainer(self) self.main_cont = _xmlui_parent if type_ == "tabs": cont = self.widget_factory.createTabsContainer(_xmlui_parent) - self._parseChilds(_xmlui_parent, node, ('tab',), {'tabs_cont': cont}) + self._parseChilds(_xmlui_parent, node, ("tab",), {"tabs_cont": cont}) elif type_ == "vertical": cont = self.widget_factory.createVerticalContainer(_xmlui_parent) - self._parseChilds(cont, node, ('widget', 'container')) + self._parseChilds(cont, node, ("widget", "container")) elif type_ == "pairs": cont = self.widget_factory.createPairsContainer(_xmlui_parent) - self._parseChilds(cont, node, ('widget', 'container')) + self._parseChilds(cont, node, ("widget", "container")) elif type_ == "label": cont = self.widget_factory.createLabelContainer(_xmlui_parent) - self._parseChilds(cont, node, ('widget', 'container'), {CURRENT_LABEL: None}) + self._parseChilds( + cont, node, ("widget", "container"), {CURRENT_LABEL: None} + ) elif type_ == "advanced_list": try: - columns = int(node.getAttribute('columns')) + columns = int(node.getAttribute("columns")) except (TypeError, ValueError): raise exceptions.DataError("Invalid columns") - selectable = node.getAttribute('selectable') or 'no' - auto_index = node.getAttribute('auto_index') == C.BOOL_TRUE - data = {'index': 0} if auto_index else None - cont = self.widget_factory.createAdvancedListContainer(_xmlui_parent, columns, selectable) + selectable = node.getAttribute("selectable") or "no" + auto_index = node.getAttribute("auto_index") == C.BOOL_TRUE + data = {"index": 0} if auto_index else None + cont = self.widget_factory.createAdvancedListContainer( + _xmlui_parent, columns, selectable + ) callback_id = node.getAttribute("callback") or None if callback_id is not None: - if selectable == 'no': - raise ValueError("can't have selectable=='no' and callback_id at the same time") + if selectable == "no": + raise ValueError( + "can't have selectable=='no' and callback_id at the same time" + ) cont._xmlui_callback_id = callback_id cont._xmluiOnSelect(self.onAdvListSelect) - self._parseChilds(cont, node, ('row',), data) + self._parseChilds(cont, node, ("row",), data) else: log.warning(_("Unknown container [%s], using default one") % type_) cont = self.widget_factory.createVerticalContainer(_xmlui_parent) - self._parseChilds(cont, node, ('widget', 'container')) + self._parseChilds(cont, node, ("widget", "container")) try: xmluiAppend = _xmlui_parent._xmluiAppend - except (AttributeError, TypeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError + except ( + AttributeError, + TypeError, + ): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError if _xmlui_parent is self: self.main_cont = cont else: - raise Exception(_("Internal Error, container has not _xmluiAppend method")) + raise Exception( + _("Internal Error, container has not _xmluiAppend method") + ) else: xmluiAppend(cont) - elif node.nodeName == 'tab': - name = node.getAttribute('name') - label = node.getAttribute('label') - selected = C.bool(node.getAttribute('selected') or C.BOOL_FALSE) - if not name or not 'tabs_cont' in data: + elif node.nodeName == "tab": + name = node.getAttribute("name") + label = node.getAttribute("label") + selected = C.bool(node.getAttribute("selected") or C.BOOL_FALSE) + if not name or not "tabs_cont" in data: raise InvalidXMLUI - if self.type == 'param': - self._current_category = name #XXX: awful hack because params need category and we don't keep parent - tab_cont = data['tabs_cont'] + if self.type == "param": + self._current_category = ( + name + ) # XXX: awful hack because params need category and we don't keep parent + tab_cont = data["tabs_cont"] new_tab = tab_cont._xmluiAddTab(label or name, selected) - self._parseChilds(new_tab, node, ('widget', 'container')) + self._parseChilds(new_tab, node, ("widget", "container")) - elif node.nodeName == 'row': + elif node.nodeName == "row": try: - index = str(data['index']) + index = str(data["index"]) except KeyError: - index = node.getAttribute('index') or None + index = node.getAttribute("index") or None else: - data['index'] += 1 + data["index"] += 1 _xmlui_parent._xmluiAddRow(index) - self._parseChilds(_xmlui_parent, node, ('widget', 'container')) + self._parseChilds(_xmlui_parent, node, ("widget", "container")) elif node.nodeName == "widget": name = node.getAttribute("name") - if name and (name in self._ignore or self._whitelist is not None and name not in self._whitelist): + if name and ( + name in self._ignore + or self._whitelist is not None + and name not in self._whitelist + ): # current widget is ignored, but there may be already a label if CURRENT_LABEL in data: # if so, we remove it from parent @@ -475,79 +545,127 @@ if value_elt is not None: value = getText(value_elt) else: - value = node.getAttribute("value") if node.hasAttribute('value') else u'' - if type_=="empty": + value = ( + node.getAttribute("value") if node.hasAttribute("value") else u"" + ) + if type_ == "empty": ctrl = self.widget_factory.createEmptyWidget(_xmlui_parent) if CURRENT_LABEL in data: data[CURRENT_LABEL] = None - elif type_=="text": + elif type_ == "text": ctrl = self.widget_factory.createTextWidget(_xmlui_parent, value) - elif type_=="label": + elif type_ == "label": ctrl = self.widget_factory.createLabelWidget(_xmlui_parent, value) data[CURRENT_LABEL] = ctrl - elif type_=="jid": + elif type_ == "jid": ctrl = self.widget_factory.createJidWidget(_xmlui_parent, value) - elif type_=="divider": - style = node.getAttribute("style") or 'line' + elif type_ == "divider": + style = node.getAttribute("style") or "line" ctrl = self.widget_factory.createDividerWidget(_xmlui_parent, style) - elif type_=="string": - ctrl = self.widget_factory.createStringWidget(_xmlui_parent, value, self._isAttrSet("read_only", node)) - self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) - elif type_=="jid_input": - ctrl = self.widget_factory.createJidInputWidget(_xmlui_parent, value, self._isAttrSet("read_only", node)) - self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) - elif type_=="password": - ctrl = self.widget_factory.createPasswordWidget(_xmlui_parent, value, self._isAttrSet("read_only", node)) - self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) - elif type_=="textbox": - ctrl = self.widget_factory.createTextBoxWidget(_xmlui_parent, value, self._isAttrSet("read_only", node)) - self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) - elif type_=="bool": - ctrl = self.widget_factory.createBoolWidget(_xmlui_parent, value==C.BOOL_TRUE, self._isAttrSet("read_only", node)) - self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) + elif type_ == "string": + ctrl = self.widget_factory.createStringWidget( + _xmlui_parent, value, self._isAttrSet("read_only", node) + ) + self.ctrl_list[name] = {"type": type_, "control": ctrl} + elif type_ == "jid_input": + ctrl = self.widget_factory.createJidInputWidget( + _xmlui_parent, value, self._isAttrSet("read_only", node) + ) + self.ctrl_list[name] = {"type": type_, "control": ctrl} + elif type_ == "password": + ctrl = self.widget_factory.createPasswordWidget( + _xmlui_parent, value, self._isAttrSet("read_only", node) + ) + self.ctrl_list[name] = {"type": type_, "control": ctrl} + elif type_ == "textbox": + ctrl = self.widget_factory.createTextBoxWidget( + _xmlui_parent, value, self._isAttrSet("read_only", node) + ) + self.ctrl_list[name] = {"type": type_, "control": ctrl} + elif type_ == "bool": + ctrl = self.widget_factory.createBoolWidget( + _xmlui_parent, + value == C.BOOL_TRUE, + self._isAttrSet("read_only", node), + ) + self.ctrl_list[name] = {"type": type_, "control": ctrl} elif type_ == "int": - ctrl = self.widget_factory.createIntWidget(_xmlui_parent, value, self._isAttrSet("read_only", node)) - self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) + ctrl = self.widget_factory.createIntWidget( + _xmlui_parent, value, self._isAttrSet("read_only", node) + ) + self.ctrl_list[name] = {"type": type_, "control": ctrl} elif type_ == "list": - style = [] if node.getAttribute("multi") == 'yes' else ['single'] - for attr in (u'noselect', u'extensible', u'reducible', u'inline'): - if node.getAttribute(attr) == 'yes': + style = [] if node.getAttribute("multi") == "yes" else ["single"] + for attr in (u"noselect", u"extensible", u"reducible", u"inline"): + if node.getAttribute(attr) == "yes": style.append(attr) - _options = [(option.getAttribute("value"), option.getAttribute("label")) for option in node.getElementsByTagName("option")] - _selected = [option.getAttribute("value") for option in node.getElementsByTagName("option") if option.getAttribute('selected') == C.BOOL_TRUE] - ctrl = self.widget_factory.createListWidget(_xmlui_parent, _options, _selected, style) - self.ctrl_list[name] = ({'type': type_, 'control': ctrl}) + _options = [ + (option.getAttribute("value"), option.getAttribute("label")) + for option in node.getElementsByTagName("option") + ] + _selected = [ + option.getAttribute("value") + for option in node.getElementsByTagName("option") + if option.getAttribute("selected") == C.BOOL_TRUE + ] + ctrl = self.widget_factory.createListWidget( + _xmlui_parent, _options, _selected, style + ) + self.ctrl_list[name] = {"type": type_, "control": ctrl} elif type_ == "jids_list": style = [] jids = [getText(jid_) for jid_ in node.getElementsByTagName("jid")] - ctrl = self.widget_factory.createJidsListWidget(_xmlui_parent, jids, style) - self.ctrl_list[name] = ({'type': type_, 'control': ctrl}) - elif type_=="button": + ctrl = self.widget_factory.createJidsListWidget( + _xmlui_parent, jids, style + ) + self.ctrl_list[name] = {"type": type_, "control": ctrl} + elif type_ == "button": callback_id = node.getAttribute("callback") - ctrl = self.widget_factory.createButtonWidget(_xmlui_parent, value, self.onButtonPress) - ctrl._xmlui_param_id = (callback_id, [field.getAttribute('name') for field in node.getElementsByTagName("field_back")]) + ctrl = self.widget_factory.createButtonWidget( + _xmlui_parent, value, self.onButtonPress + ) + ctrl._xmlui_param_id = ( + callback_id, + [ + field.getAttribute("name") + for field in node.getElementsByTagName("field_back") + ], + ) else: - log.error(_("FIXME FIXME FIXME: widget type [%s] is not implemented") % type_) - raise NotImplementedError(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_) + log.error( + _("FIXME FIXME FIXME: widget type [%s] is not implemented") + % type_ + ) + raise NotImplementedError( + _("FIXME FIXME FIXME: type [%s] is not implemented") % type_ + ) if name: self.widgets[name] = ctrl - if self.type == 'param' and type_ not in ('text', 'button'): + if self.type == "param" and type_ not in ("text", "button"): try: ctrl._xmluiOnChange(self.onParamChange) ctrl._param_category = self._current_category - except (AttributeError, TypeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError - if not isinstance(ctrl, (EmptyWidget, TextWidget, LabelWidget, JidWidget)): + except ( + AttributeError, + TypeError, + ): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError + if not isinstance( + ctrl, (EmptyWidget, TextWidget, LabelWidget, JidWidget) + ): log.warning(_("No change listener on [%s]") % ctrl) - elif type_ != 'text': + elif type_ != "text": callback = node.getAttribute("internal_callback") or None if callback: - fields = [field.getAttribute('name') for field in node.getElementsByTagName("internal_field")] + fields = [ + field.getAttribute("name") + for field in node.getElementsByTagName("internal_field") + ] cb_data = self.getInternalCallbackData(callback, node) ctrl._xmlui_param_internal = (callback, fields, cb_data) - if type_ == 'button': + if type_ == "button": ctrl._xmluiOnClick(self.onChangeInternal) else: ctrl._xmluiOnChange(self.onChangeInternal) @@ -560,7 +678,7 @@ data.pop(CURRENT_LABEL)._xmlui_for_name = name else: - raise NotImplementedError(_('Unknown tag [%s]') % node.nodeName) + raise NotImplementedError(_("Unknown tag [%s]") % node.nodeName) def constructUI(self, parsed_dom, post_treat=None): """Actually construct the UI @@ -569,12 +687,17 @@ @param post_treat: frontend specific treatments to do once the UI is constructed @return: constructed widget """ - top=parsed_dom.documentElement + top = parsed_dom.documentElement self.type = top.getAttribute("type") - if top.nodeName != "sat_xmlui" or not self.type in ['form', 'param', 'window', 'popup']: + if top.nodeName != "sat_xmlui" or not self.type in [ + "form", + "param", + "window", + "popup", + ]: raise InvalidXMLUI - if self.type == 'param': + if self.type == "param": self.param_changed = set() self._parseChilds(self, parsed_dom.documentElement) @@ -603,11 +726,14 @@ name = self.escape(wid._xmlui_name) value = wid._xmluiGetValue() data[name] = value - except (AttributeError, TypeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError + except ( + AttributeError, + TypeError, + ): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError pass idx = ctrl._xmluiGetSelectedIndex() if idx is not None: - data['index'] = idx + data["index"] = idx callback_id = ctrl._xmlui_callback_id if callback_id is None: log.info(_("No callback_id found")) @@ -627,10 +753,10 @@ for field in fields: escaped = self.escape(field) ctrl = self.ctrl_list[field] - if isinstance(ctrl['control'], ListWidget): - data[escaped] = u'\t'.join(ctrl['control']._xmluiGetSelectedValues()) + if isinstance(ctrl["control"], ListWidget): + data[escaped] = u"\t".join(ctrl["control"]._xmluiGetSelectedValues()) else: - data[escaped] = ctrl['control']._xmluiGetValue() + data[escaped] = ctrl["control"]._xmluiGetValue() self._xmluiLaunchAction(callback_id, data) def onChangeInternal(self, ctrl): @@ -641,8 +767,10 @@ @param ctrl: widget modified """ action, fields, data = ctrl._xmlui_param_internal - if action not in ('copy', 'move', 'groups_of_contact'): - raise NotImplementedError(_("FIXME: XMLUI internal action [%s] is not implemented") % action) + if action not in ("copy", "move", "groups_of_contact"): + raise NotImplementedError( + _("FIXME: XMLUI internal action [%s] is not implemented") % action + ) def copy_move(source, target): """Depending of 'action' value, copy or move from source to target.""" @@ -651,18 +779,18 @@ values = source._xmluiGetSelectedValues() else: values = [source._xmluiGetValue()] - if action == 'move': - source._xmluiSetValue('') + if action == "move": + source._xmluiSetValue("") values = [value for value in values if value] if values: target._xmluiAddValues(values, select=True) else: if isinstance(source, ListWidget): - value = u', '.join(source._xmluiGetSelectedValues()) + value = u", ".join(source._xmluiGetSelectedValues()) else: value = source._xmluiGetValue() - if action == 'move': - source._xmluiSetValue('') + if action == "move": + source._xmluiSetValue("") target._xmluiSetValue(value) def groups_of_contact(source, target): @@ -678,13 +806,13 @@ source = None for field in fields: - widget = self.ctrl_list[field]['control'] + widget = self.ctrl_list[field]["control"] if not source: source = widget continue - if action in ('copy', 'move'): + if action in ("copy", "move"): copy_move(source, widget) - elif action == 'groups_of_contact': + elif action == "groups_of_contact": groups_of_contact(source, widget) source = None @@ -700,16 +828,18 @@ # extract any kind of data structure from the 'internal_data' element. try: # data is stored in the first 'internal_data' element of the node - data_elts = node.getElementsByTagName('internal_data')[0].childNodes + data_elts = node.getElementsByTagName("internal_data")[0].childNodes except IndexError: return None data = {} - if action == 'groups_of_contact': # return a dict(key: string, value: list[string]) + if ( + action == "groups_of_contact" + ): # return a dict(key: string, value: list[string]) for elt in data_elts: - jid_s = elt.getAttribute('name') + jid_s = elt.getAttribute("name") data[jid_s] = [] for value_elt in elt.childNodes: - data[jid_s].append(value_elt.getAttribute('name')) + data[jid_s].append(value_elt.getAttribute("name")) return data def onFormSubmitted(self, ignore=None): @@ -721,15 +851,19 @@ for ctrl_name in self.ctrl_list: escaped = self.escape(ctrl_name) ctrl = self.ctrl_list[ctrl_name] - if isinstance(ctrl['control'], ListWidget): - selected_values.append((escaped, u'\t'.join(ctrl['control']._xmluiGetSelectedValues()))) + if isinstance(ctrl["control"], ListWidget): + selected_values.append( + (escaped, u"\t".join(ctrl["control"]._xmluiGetSelectedValues())) + ) else: - selected_values.append((escaped, ctrl['control']._xmluiGetValue())) + selected_values.append((escaped, ctrl["control"]._xmluiGetValue())) if self.submit_id is not None: data = dict(selected_values) self.submit(data) else: - log.warning(_("The form data is not sent back, the type is not managed properly")) + log.warning( + _("The form data is not sent back, the type is not managed properly") + ) self._xmluiClose() def onFormCancelled(self, ignore=None): @@ -739,7 +873,9 @@ data = {C.XMLUI_DATA_CANCELLED: C.BOOL_TRUE} self.submit(data) else: - log.warning(_("The form data is not sent back, the type is not managed properly")) + log.warning( + _("The form data is not sent back, the type is not managed properly") + ) self._xmluiClose() def onSaveParams(self, ignore=None): @@ -747,10 +883,10 @@ self.type must be param """ - assert self.type == 'param' + assert self.type == "param" for ctrl in self.param_changed: if isinstance(ctrl, ListWidget): - value = u'\t'.join(ctrl._xmluiGetSelectedValues()) + value = u"\t".join(ctrl._xmluiGetSelectedValues()) else: value = ctrl._xmluiGetValue() param_name = ctrl._xmlui_name.split(C.SAT_PARAM_SEPARATOR)[1] @@ -765,38 +901,69 @@ class XMLUIDialog(XMLUIBase): dialog_factory = None - def __init__(self, host, parsed_dom, title=None, flags=None, callback=None, ignore=None, whitelist=None, profile=C.PROF_KEY_NONE): - super(XMLUIDialog, self).__init__(host, parsed_dom, title=title, flags=flags, callback=callback, profile=profile) - top=parsed_dom.documentElement - dlg_elt = self._getChildNode(top, "dialog") + def __init__( + self, + host, + parsed_dom, + title=None, + flags=None, + callback=None, + ignore=None, + whitelist=None, + profile=C.PROF_KEY_NONE, + ): + super(XMLUIDialog, self).__init__( + host, parsed_dom, title=title, flags=flags, callback=callback, profile=profile + ) + top = parsed_dom.documentElement + dlg_elt = self._getChildNode(top, "dialog") if dlg_elt is None: raise ValueError("Invalid XMLUI: no Dialog element found !") dlg_type = dlg_elt.getAttribute("type") or C.XMLUI_DIALOG_MESSAGE try: mess_elt = self._getChildNode(dlg_elt, C.XMLUI_DATA_MESS) message = getText(mess_elt) - except (TypeError, AttributeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError + except ( + TypeError, + AttributeError, + ): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError message = "" level = dlg_elt.getAttribute(C.XMLUI_DATA_LVL) or C.XMLUI_DATA_LVL_INFO if dlg_type == C.XMLUI_DIALOG_MESSAGE: - self.dlg = self.dialog_factory.createMessageDialog(self, self.xmlui_title, message, level) + self.dlg = self.dialog_factory.createMessageDialog( + self, self.xmlui_title, message, level + ) elif dlg_type == C.XMLUI_DIALOG_NOTE: - self.dlg = self.dialog_factory.createNoteDialog(self, self.xmlui_title, message, level) + self.dlg = self.dialog_factory.createNoteDialog( + self, self.xmlui_title, message, level + ) elif dlg_type == C.XMLUI_DIALOG_CONFIRM: try: buttons_elt = self._getChildNode(dlg_elt, "buttons") - buttons_set = buttons_elt.getAttribute("set") or C.XMLUI_DATA_BTNS_SET_DEFAULT - except (TypeError, AttributeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError + buttons_set = ( + buttons_elt.getAttribute("set") or C.XMLUI_DATA_BTNS_SET_DEFAULT + ) + except ( + TypeError, + AttributeError, + ): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError buttons_set = C.XMLUI_DATA_BTNS_SET_DEFAULT - self.dlg = self.dialog_factory.createConfirmDialog(self, self.xmlui_title, message, level, buttons_set) + self.dlg = self.dialog_factory.createConfirmDialog( + self, self.xmlui_title, message, level, buttons_set + ) elif dlg_type == C.XMLUI_DIALOG_FILE: try: file_elt = self._getChildNode(dlg_elt, "file") filetype = file_elt.getAttribute("type") or C.XMLUI_DATA_FILETYPE_DEFAULT - except (TypeError, AttributeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError + except ( + TypeError, + AttributeError, + ): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError filetype = C.XMLUI_DATA_FILETYPE_DEFAULT - self.dlg = self.dialog_factory.createFileDialog(self, self.xmlui_title, message, level, filetype) + self.dlg = self.dialog_factory.createFileDialog( + self, self.xmlui_title, message, level, filetype + ) else: raise ValueError("Unknown dialog type [%s]" % dlg_type) @@ -819,7 +986,18 @@ class_map[type_] = class_ -def create(host, xml_data, title=None, flags=None, dom_parse=None, dom_free=None, callback=None, ignore=None, whitelist=None, profile=C.PROF_KEY_NONE): +def create( + host, + xml_data, + title=None, + flags=None, + dom_parse=None, + dom_free=None, + callback=None, + ignore=None, + whitelist=None, + profile=C.PROF_KEY_NONE, +): """ @param dom_parse: methode equivalent to minidom.parseString (but which must manage unicode), or None to use default one @param dom_free: method used to free the parsed DOM @@ -831,13 +1009,14 @@ """ if dom_parse is None: from xml.dom import minidom - dom_parse = lambda xml_data: minidom.parseString(xml_data.encode('utf-8')) + + dom_parse = lambda xml_data: minidom.parseString(xml_data.encode("utf-8")) dom_free = lambda parsed_dom: parsed_dom.unlink() else: dom_parse = dom_parse dom_free = dom_free or (lambda parsed_dom: None) parsed_dom = dom_parse(xml_data) - top=parsed_dom.documentElement + top = parsed_dom.documentElement ui_type = top.getAttribute("type") try: if ui_type != C.XMLUI_DIALOG: @@ -845,14 +1024,19 @@ else: cls = class_map[CLASS_DIALOG] except KeyError: - raise ClassNotRegistedError(_("You must register classes with registerClass before creating a XMLUI")) + raise ClassNotRegistedError( + _("You must register classes with registerClass before creating a XMLUI") + ) - xmlui = cls(host, parsed_dom, - title = title, - flags = flags, - callback = callback, - ignore = ignore, - whitelist = whitelist, - profile = profile) + xmlui = cls( + host, + parsed_dom, + title=title, + flags=flags, + callback=callback, + ignore=ignore, + whitelist=whitelist, + profile=profile, + ) dom_free(parsed_dom) return xmlui