# HG changeset patch # User Goffi # Date 1718815497 -7200 # Node ID 0d7bb4df2343b8999dbd013fb2b982fffdb55ae0 # Parent 64a85ce8be700917720a69cfaf481c38506bad75 Reformatted code base using black. diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/bridge/bridge_constructor/base_constructor.py --- a/libervia/backend/bridge/bridge_constructor/base_constructor.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/bridge/bridge_constructor/base_constructor.py Wed Jun 19 18:44:57 2024 +0200 @@ -77,7 +77,8 @@ def get_default(self, name): """Return default values of a function in a dict @param name: Name of the function to get - @return: dict, each key is the integer param number (no key if no default value)""" + @return: dict, each key is the integer param number (no key if no default value) + """ default_dict = {} def_re = re.compile(r"param_(\d+)_default") @@ -109,7 +110,8 @@ def get_arguments_doc(self, name): """Return documentation of arguments @param name: Name of the function to get - @return: dict, each key is the integer param number (no key if no argument doc), value is a tuple (name, doc)""" + @return: dict, each key is the integer param number (no key if no argument doc), value is a tuple (name, doc) + """ doc_dict = {} option_re = re.compile(r"doc_param_(\d+)") value_re = re.compile(r"^(\w+): (.*)$", re.MULTILINE | re.DOTALL) @@ -145,8 +147,19 @@ 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": @@ -293,8 +306,9 @@ extend_method(completion, function, default, arg_doc, async_) for part, fmt in FORMATS.items(): - if (part.startswith(function["type"]) - or part.startswith(f"async_{function['type']}")): + if part.startswith(function["type"]) or part.startswith( + f"async_{function['type']}" + ): parts[part.upper()].append(fmt.format(**completion)) # at this point, signals_part, methods_part and direct_calls should be filled, @@ -350,10 +364,12 @@ 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)) diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/bridge/bridge_constructor/constructors/dbus/constructor.py --- a/libervia/backend/bridge/bridge_constructor/constructors/dbus/constructor.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/bridge/bridge_constructor/constructors/dbus/constructor.py Wed Jun 19 18:44:57 2024 +0200 @@ -27,14 +27,11 @@ CORE_FORMATS = { "methods_declarations": """\ Method('{name}', arguments='{sig_in}', returns='{sig_out}'),""", - "methods": """\ def dbus_{name}(self, {args}): {debug}return self._callback("{name}", {args_no_default})\n""", - "signals_declarations": """\ Signal('{name}', '{sig_in}'),""", - "signals": """\ def {name}(self, {args}): self._obj.emitSignal("{name}", {args})\n""", @@ -68,7 +65,8 @@ completion.update( { "debug": ( - "" if not self.args.debug + "" + if not self.args.debug else f'log.debug ("{completion["name"]}")\n{8 * " "}' ) } @@ -78,9 +76,11 @@ 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 * " "), + "debug": ( + "" + if not self.args.debug + else 'log.debug ("%s")\n%s' % (completion["name"], 8 * " ") + ), "args_result": self.get_arguments(function["sig_in"], name=arg_doc), "async_args": "callback=None, errback=None", "async_comma": ", " if function["sig_in"] else "", @@ -95,9 +95,9 @@ ) if async_: completion["blocking_call"] = "" - completion[ - "async_args_result" - ] = "timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler" + 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[ diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py --- a/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py Wed Jun 19 18:44:57 2024 +0200 @@ -31,10 +31,8 @@ # Interface prefix const_INT_PREFIX = config.config_get( - config.parse_main_conf(), - "", - "bridge_dbus_int_prefix", - "org.libervia.Libervia") + config.parse_main_conf(), "", "bridge_dbus_int_prefix", "org.libervia.Libervia" +) const_ERROR_PREFIX = const_INT_PREFIX + ".error" const_OBJ_PATH = "/org/libervia/Libervia/bridge" const_CORE_SUFFIX = ".core" @@ -88,12 +86,10 @@ core_iface = DBusInterface( const_INT_PREFIX + const_CORE_SUFFIX, -##METHODS_DECLARATIONS_PART## -##SIGNALS_DECLARATIONS_PART## + ##METHODS_DECLARATIONS_PART## + ##SIGNALS_DECLARATIONS_PART## ) - plugin_iface = DBusInterface( - const_INT_PREFIX + const_PLUGIN_SUFFIX - ) + plugin_iface = DBusInterface(const_INT_PREFIX + const_PLUGIN_SUFFIX) dbusInterfaces = [core_iface, plugin_iface] @@ -116,8 +112,10 @@ d.addErrback(GenericException.create_and_raise) return d + ##METHODS_PART## + class bridge: def __init__(self): @@ -140,7 +138,7 @@ conn.exportObject(self._obj) await conn.requestBusName(const_INT_PREFIX) -##SIGNALS_PART## + ##SIGNALS_PART## def register_method(self, name, callback): log.debug(f"registering DBus bridge method [{name}]") self._obj.register_method(name, callback) @@ -149,7 +147,7 @@ self._obj.emitSignal(name, *args) def add_method( - self, name, int_suffix, in_sign, out_sign, method, async_=False, doc={} + self, name, int_suffix, in_sign, out_sign, method, async_=False, doc={} ): """Dynamically add a method to D-Bus bridge""" # FIXME: doc parameter is kept only temporary, the time to remove it from calls @@ -157,10 +155,12 @@ self._obj.plugin_iface.addMethod( Method(name, arguments=in_sign, returns=out_sign) ) + # we have to create a method here instead of using partialmethod, because txdbus # uses __func__ which doesn't work with partialmethod def caller(self_, *args, **kwargs): return self_._callback(name, *args, **kwargs) + setattr(self._obj, f"dbus_{name}", MethodType(caller, self._obj)) self.register_method(name, method) diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py --- a/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py Wed Jun 19 18:44:57 2024 +0200 @@ -33,12 +33,10 @@ # Interface prefix const_INT_PREFIX = config.config_get( - config.parse_main_conf(), - "", - "bridge_dbus_int_prefix", - "org.libervia.Libervia") + config.parse_main_conf(), "", "bridge_dbus_int_prefix", "org.libervia.Libervia" +) const_ERROR_PREFIX = const_INT_PREFIX + ".error" -const_OBJ_PATH = '/org/libervia/Libervia/bridge' +const_OBJ_PATH = "/org/libervia/Libervia/bridge" const_CORE_SUFFIX = ".core" const_PLUGIN_SUFFIX = ".plugin" const_TIMEOUT = 120 @@ -52,7 +50,7 @@ """ full_name = dbus_e.get_dbus_name() if full_name.startswith(const_ERROR_PREFIX): - name = dbus_e.get_dbus_name()[len(const_ERROR_PREFIX) + 1:] + name = dbus_e.get_dbus_name()[len(const_ERROR_PREFIX) + 1 :] else: name = full_name # XXX: dbus_e.args doesn't contain the original DBusException args, but we @@ -62,7 +60,7 @@ try: message, condition = ast.literal_eval(message) except (SyntaxError, ValueError, TypeError): - condition = '' + condition = "" return BridgeException(name, message, condition) @@ -71,24 +69,33 @@ def bridge_connect(self, callback, errback): try: self.sessions_bus = dbus.SessionBus() - self.db_object = self.sessions_bus.get_object(const_INT_PREFIX, - const_OBJ_PATH) - self.db_core_iface = dbus.Interface(self.db_object, - dbus_interface=const_INT_PREFIX + const_CORE_SUFFIX) - self.db_plugin_iface = dbus.Interface(self.db_object, - dbus_interface=const_INT_PREFIX + const_PLUGIN_SUFFIX) + self.db_object = self.sessions_bus.get_object( + const_INT_PREFIX, const_OBJ_PATH + ) + self.db_core_iface = dbus.Interface( + self.db_object, dbus_interface=const_INT_PREFIX + const_CORE_SUFFIX + ) + self.db_plugin_iface = dbus.Interface( + self.db_object, dbus_interface=const_INT_PREFIX + const_PLUGIN_SUFFIX + ) except dbus.exceptions.DBusException as e: - if e._dbus_error_name in ('org.freedesktop.DBus.Error.ServiceUnknown', - 'org.freedesktop.DBus.Error.Spawn.ExecFailed'): + if e._dbus_error_name in ( + "org.freedesktop.DBus.Error.ServiceUnknown", + "org.freedesktop.DBus.Error.Spawn.ExecFailed", + ): errback(BridgeExceptionNoService()) - elif e._dbus_error_name == 'org.freedesktop.DBus.Error.NotSupported': - log.error(_("D-Bus is not launched, please see README to see instructions on how to launch it")) + elif e._dbus_error_name == "org.freedesktop.DBus.Error.NotSupported": + log.error( + _( + "D-Bus is not launched, please see README to see instructions on how to launch it" + ) + ) errback(BridgeInitError) else: errback(e) else: callback() - #props = self.db_core_iface.getProperties() + # props = self.db_core_iface.getProperties() def register_signal(self, functionName, handler, iface="core"): if iface == "core": @@ -96,10 +103,10 @@ elif iface == "plugin": self.db_plugin_iface.connect_to_signal(functionName, handler) else: - log.error(_('Unknown interface')) + log.error(_("Unknown interface")) def __getattribute__(self, name): - """ usual __getattribute__ if the method exists, else try to find a plugin method """ + """usual __getattribute__ if the method exists, else try to find a plugin method""" try: return object.__getattribute__(self, name) except AttributeError: @@ -114,20 +121,26 @@ args = list(args) if kwargs: - if 'callback' in kwargs: + if "callback" in kwargs: async_ = True - _callback = kwargs.pop('callback') - _errback = kwargs.pop('errback', lambda failure: log.error(str(failure))) + _callback = kwargs.pop("callback") + _errback = kwargs.pop( + "errback", lambda failure: log.error(str(failure)) + ) try: - args.append(kwargs.pop('profile')) + args.append(kwargs.pop("profile")) except KeyError: try: - args.append(kwargs.pop('profile_key')) + args.append(kwargs.pop("profile_key")) except KeyError: pass # at this point, kwargs should be empty if kwargs: - log.warning("unexpected keyword arguments, they will be ignored: {}".format(kwargs)) + log.warning( + "unexpected keyword arguments, they will be ignored: {}".format( + kwargs + ) + ) elif len(args) >= 2 and callable(args[-1]) and callable(args[-2]): async_ = True _errback = args.pop() @@ -136,9 +149,11 @@ method = getattr(self.db_plugin_iface, name) if async_: - kwargs['timeout'] = const_TIMEOUT - kwargs['reply_handler'] = _callback - kwargs['error_handler'] = lambda err: _errback(dbus_to_bridge_exception(err)) + kwargs["timeout"] = const_TIMEOUT + kwargs["reply_handler"] = _callback + kwargs["error_handler"] = lambda err: _errback( + dbus_to_bridge_exception(err) + ) try: return method(*args, **kwargs) @@ -158,17 +173,21 @@ return get_plugin_method + ##METHODS_PART## + class AIOBridge(bridge): def register_signal(self, functionName, handler, iface="core"): loop = asyncio.get_running_loop() - async_handler = lambda *args: asyncio.run_coroutine_threadsafe(handler(*args), loop) + async_handler = lambda *args: asyncio.run_coroutine_threadsafe( + handler(*args), loop + ) return super().register_signal(functionName, async_handler, iface) def __getattribute__(self, name): - """ usual __getattribute__ if the method exists, else try to find a plugin method """ + """usual __getattribute__ if the method exists, else try to find a plugin method""" try: return object.__getattribute__(self, name) except AttributeError: @@ -178,16 +197,18 @@ fut = loop.create_future() method = getattr(self.db_plugin_iface, name) reply_handler = lambda ret=None: loop.call_soon_threadsafe( - fut.set_result, ret) + fut.set_result, ret + ) error_handler = lambda err: loop.call_soon_threadsafe( - fut.set_exception, dbus_to_bridge_exception(err)) + fut.set_exception, dbus_to_bridge_exception(err) + ) try: method( *args, **kwargs, timeout=const_TIMEOUT, reply_handler=reply_handler, - error_handler=error_handler + error_handler=error_handler, ) except ValueError as e: if e.args[0].startswith("Unable to guess signature"): @@ -202,7 +223,7 @@ **kwargs, timeout=const_TIMEOUT, reply_handler=reply_handler, - error_handler=error_handler + error_handler=error_handler, ) else: @@ -216,8 +237,9 @@ fut = loop.create_future() super().bridge_connect( callback=lambda: loop.call_soon_threadsafe(fut.set_result, None), - errback=lambda e: loop.call_soon_threadsafe(fut.set_exception, e) + errback=lambda e: loop.call_soon_threadsafe(fut.set_exception, e), ) return fut + ##ASYNC_METHODS_PART## diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/bridge/bridge_constructor/constructors/embedded/constructor.py --- a/libervia/backend/bridge/bridge_constructor/constructors/embedded/constructor.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/bridge/bridge_constructor/constructors/embedded/constructor.py Wed Jun 19 18:44:57 2024 +0200 @@ -48,9 +48,11 @@ 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 * " "), + "debug": ( + "" + if not self.args.debug + else 'log.debug ("%s")\n%s' % (completion["name"], 8 * " ") + ), "args_result": self.get_arguments(function["sig_in"], name=arg_doc), "args_comma": ", " if function["sig_in"] else "", } @@ -60,9 +62,8 @@ completion["cb_or_lambda"] = ( "callback" if function["sig_out"] else "lambda __: callback()" ) - completion[ - "ret_routine" - ] = """\ + completion["ret_routine"] = ( + """\ d = self._methods_cbs["{name}"]({args_result}) if callback is not None: d.addCallback({cb_or_lambda}) @@ -72,13 +73,13 @@ d.addErrback(errback) return d """.format( - **completion + **completion + ) ) else: completion["ret_or_nothing"] = "ret" if function["sig_out"] else "" - completion[ - "ret_routine" - ] = """\ + completion["ret_routine"] = ( + """\ try: ret = self._methods_cbs["{name}"]({args_result}) except Exception as e: @@ -91,7 +92,8 @@ return ret else: callback({ret_or_nothing})""".format( - **completion + **completion + ) ) def core_completion_signal(self, completion, function, default, arg_doc, async_): diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/bridge/bridge_constructor/constructors/embedded/embedded_template.py --- a/libervia/backend/bridge/bridge_constructor/constructors/embedded/embedded_template.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/bridge/bridge_constructor/constructors/embedded/embedded_template.py Wed Jun 19 18:44:57 2024 +0200 @@ -85,7 +85,9 @@ else: cb(*args, **kwargs) - def add_method(self, name, int_suffix, in_sign, out_sign, method, async_=False, doc={}): + def add_method( + 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 log.debug("Adding method [{}] to embedded bridge".format(name)) self.register_method(name, method) diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/bridge/bridge_constructor/constructors/mediawiki/constructor.py --- a/libervia/backend/bridge/bridge_constructor/constructors/mediawiki/constructor.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/bridge/bridge_constructor/constructors/mediawiki/constructor.py Wed Jun 19 18:44:57 2024 +0200 @@ -108,21 +108,21 @@ function["sig_out"], ) completion = { - "signature": signature_signal - if function["type"] == "signal" - else signature_method, + "signature": ( + signature_signal if function["type"] == "signal" else signature_method + ), "sig_out": function["sig_out"] or "", "category": function["category"], "name": section, "doc": self.get_doc(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 "", + "deprecated": ( + deprecated_msg if "deprecated" in self.getFlags(section) else "" + ), "parameters": self._wiki_parameter(section, function["sig_in"]), - "return": self._wiki_return(section) - if function["type"] == "method" - else "", + "return": ( + self._wiki_return(section) if function["type"] == "method" else "" + ), } dest = signals_part if function["type"] == "signal" else methods_part diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/bridge/bridge_constructor/constructors/pb/constructor.py --- a/libervia/backend/bridge/bridge_constructor/constructors/pb/constructor.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/bridge/bridge_constructor/constructors/pb/constructor.py Wed Jun 19 18:44:57 2024 +0200 @@ -61,11 +61,13 @@ { "args_comma": ", " if function["sig_in"] else "", "args_no_def": self.get_arguments(function["sig_in"], name=arg_doc), - "callback": "callback" - if function["sig_out"] - else "lambda __: callback()", - "debug": "" - if not self.args.debug - else 'log.debug ("%s")\n%s' % (completion["name"], 8 * " "), + "callback": ( + "callback" if function["sig_out"] else "lambda __: callback()" + ), + "debug": ( + "" + if not self.args.debug + else 'log.debug ("%s")\n%s' % (completion["name"], 8 * " ") + ), } ) diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/bridge/bridge_constructor/constructors/pb/pb_core_template.py --- a/libervia/backend/bridge/bridge_constructor/constructors/pb/pb_core_template.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/bridge/bridge_constructor/constructors/pb/pb_core_template.py Wed Jun 19 18:44:57 2024 +0200 @@ -99,6 +99,7 @@ del self.signals_paused log.debug("bridge signals have been reactivated") + ##METHODS_PART## @@ -136,7 +137,7 @@ #  self.root.register_method(name, callback) def add_method( - self, name, int_suffix, in_sign, out_sign, method, async_=False, doc={} + 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 @@ -163,4 +164,5 @@ """ self.root._bridge_reactivate_signals() + ##SIGNALS_PART## diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/bridge/bridge_constructor/constructors/pb/pb_frontend_template.py --- a/libervia/backend/bridge/bridge_constructor/constructors/pb/pb_frontend_template.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/bridge/bridge_constructor/constructors/pb/pb_frontend_template.py Wed Jun 19 18:44:57 2024 +0200 @@ -48,9 +48,7 @@ pass else: raise exceptions.InternalError( - "{name} signal handler has been registered twice".format( - name=method_name - ) + "{name} signal handler has been registered twice".format(name=method_name) ) setattr(self, method_name, handler) @@ -70,8 +68,7 @@ """Convert Failure to BridgeException""" ori_errback( BridgeException( - name=failure_.type.decode('utf-8'), - message=str(failure_.value) + name=failure_.type.decode("utf-8"), message=str(failure_.value) ) ) @@ -167,11 +164,13 @@ ##METHODS_PART## + class AIOSignalsHandler(SignalsHandler): def register_signal(self, name, handler, iface="core"): async_handler = lambda *args, **kwargs: defer.Deferred.fromFuture( - asyncio.ensure_future(handler(*args, **kwargs))) + asyncio.ensure_future(handler(*args, **kwargs)) + ) return super().register_signal(name, async_handler, iface) @@ -183,9 +182,8 @@ def _errback(self, failure_): """Convert Failure to BridgeException""" raise BridgeException( - name=failure_.type.decode('utf-8'), - message=str(failure_.value) - ) + name=failure_.type.decode("utf-8"), message=str(failure_.value) + ) def call(self, name, *args, **kwargs): d = self.root.callRemote(name, *args, *kwargs) @@ -196,4 +194,5 @@ d = super().bridge_connect(callback=None, errback=None) return await d.asFuture(asyncio.get_event_loop()) + ##ASYNC_METHODS_PART## diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/bridge/dbus_bridge.py --- a/libervia/backend/bridge/dbus_bridge.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/bridge/dbus_bridge.py Wed Jun 19 18:44:57 2024 +0200 @@ -31,10 +31,8 @@ # Interface prefix const_INT_PREFIX = config.config_get( - config.parse_main_conf(), - "", - "bridge_dbus_int_prefix", - "org.libervia.Libervia") + config.parse_main_conf(), "", "bridge_dbus_int_prefix", "org.libervia.Libervia" +) const_ERROR_PREFIX = const_INT_PREFIX + ".error" const_OBJ_PATH = "/org/libervia/Libervia/bridge" const_CORE_SUFFIX = ".core" @@ -88,100 +86,104 @@ core_iface = DBusInterface( const_INT_PREFIX + const_CORE_SUFFIX, - Method('action_launch', arguments='sss', returns='s'), - Method('actions_get', arguments='s', returns='a(ssi)'), - Method('config_get', arguments='ss', returns='s'), - Method('connect', arguments='ssa{ss}', returns='b'), - Method('contact_add', arguments='ss', returns=''), - Method('contact_del', arguments='ss', returns=''), - Method('contact_get', arguments='ss', returns='(a{ss}as)'), - Method('contact_update', arguments='ssass', returns=''), - Method('contacts_get', arguments='s', returns='a(sa{ss}as)'), - Method('contacts_get_from_group', arguments='ss', returns='as'), - Method('devices_infos_get', arguments='ss', returns='s'), - Method('disco_find_by_features', arguments='asa(ss)bbbbbs', returns='(a{sa(sss)}a{sa(sss)}a{sa(sss)})'), - Method('disco_infos', arguments='ssbs', returns='(asa(sss)a{sa(a{ss}as)})'), - Method('disco_items', arguments='ssbs', returns='a(sss)'), - Method('disconnect', arguments='s', returns=''), - Method('encryption_namespace_get', arguments='s', returns='s'), - Method('encryption_plugins_get', arguments='', returns='s'), - Method('encryption_trust_ui_get', arguments='sss', returns='s'), - Method('entities_data_get', arguments='asass', returns='a{sa{ss}}'), - Method('entity_data_get', arguments='sass', returns='a{ss}'), - Method('features_get', arguments='s', returns='a{sa{ss}}'), - Method('history_get', arguments='ssiba{ss}s', returns='a(sdssa{ss}a{ss}ss)'), - Method('image_check', arguments='s', returns='s'), - Method('image_convert', arguments='ssss', returns='s'), - Method('image_generate_preview', arguments='ss', returns='s'), - Method('image_resize', arguments='sii', returns='s'), - Method('init_pre_script', arguments='', returns=''), - Method('is_connected', arguments='s', returns='b'), - Method('main_resource_get', arguments='ss', returns='s'), - Method('menu_help_get', arguments='ss', returns='s'), - Method('menu_launch', arguments='sasa{ss}is', returns='a{ss}'), - Method('menus_get', arguments='si', returns='a(ssasasa{ss})'), - Method('message_encryption_get', arguments='ss', returns='s'), - Method('message_encryption_start', arguments='ssbs', returns=''), - Method('message_encryption_stop', arguments='ss', returns=''), - Method('message_send', arguments='sa{ss}a{ss}sss', returns=''), - Method('namespaces_get', arguments='', returns='a{ss}'), - Method('notification_add', arguments='ssssbbsdss', returns=''), - Method('notification_delete', arguments='sbs', returns=''), - Method('notifications_expired_clean', arguments='ds', returns=''), - Method('notifications_get', arguments='ss', returns='s'), - Method('param_get_a', arguments='ssss', returns='s'), - Method('param_get_a_async', arguments='sssis', returns='s'), - Method('param_set', arguments='sssis', returns=''), - Method('param_ui_get', arguments='isss', returns='s'), - Method('params_categories_get', arguments='', returns='as'), - Method('params_register_app', arguments='sis', returns=''), - Method('params_template_load', arguments='s', returns='b'), - Method('params_template_save', arguments='s', returns='b'), - Method('params_values_from_category_get_async', arguments='sisss', returns='a{ss}'), - Method('presence_set', arguments='ssa{ss}s', returns=''), - Method('presence_statuses_get', arguments='s', returns='a{sa{s(sia{ss})}}'), - Method('private_data_delete', arguments='sss', returns=''), - Method('private_data_get', arguments='sss', returns='s'), - Method('private_data_set', arguments='ssss', returns=''), - Method('profile_create', arguments='sss', returns=''), - Method('profile_delete_async', arguments='s', returns=''), - Method('profile_is_session_started', arguments='s', returns='b'), - Method('profile_name_get', arguments='s', returns='s'), - Method('profile_set_default', arguments='s', returns=''), - Method('profile_start_session', arguments='ss', returns='b'), - Method('profiles_list_get', arguments='bb', returns='as'), - Method('progress_get', arguments='ss', returns='a{ss}'), - Method('progress_get_all', arguments='s', returns='a{sa{sa{ss}}}'), - Method('progress_get_all_metadata', arguments='s', returns='a{sa{sa{ss}}}'), - Method('ready_get', arguments='', returns=''), - Method('roster_resync', arguments='s', returns=''), - Method('session_infos_get', arguments='s', returns='a{ss}'), - Method('sub_waiting_get', arguments='s', returns='a{ss}'), - Method('subscription', arguments='sss', returns=''), - Method('version_get', arguments='', returns='s'), - Signal('_debug', 'sa{ss}s'), - Signal('action_new', 'ssis'), - Signal('connected', 'ss'), - Signal('contact_deleted', 'ss'), - Signal('contact_new', 'sa{ss}ass'), - Signal('disconnected', 's'), - Signal('entity_data_updated', 'ssss'), - Signal('message_encryption_started', 'sss'), - Signal('message_encryption_stopped', 'sa{ss}s'), - Signal('message_new', 'sdssa{ss}a{ss}sss'), - Signal('message_update', 'ssss'), - Signal('notification_deleted', 'ss'), - Signal('notification_new', 'sdssssbidss'), - Signal('param_update', 'ssss'), - Signal('presence_update', 'ssia{ss}s'), - Signal('progress_error', 'sss'), - Signal('progress_finished', 'sa{ss}s'), - Signal('progress_started', 'sa{ss}s'), - Signal('subscribe', 'sss'), + Method("action_launch", arguments="sss", returns="s"), + Method("actions_get", arguments="s", returns="a(ssi)"), + Method("config_get", arguments="ss", returns="s"), + Method("connect", arguments="ssa{ss}", returns="b"), + Method("contact_add", arguments="ss", returns=""), + Method("contact_del", arguments="ss", returns=""), + Method("contact_get", arguments="ss", returns="(a{ss}as)"), + Method("contact_update", arguments="ssass", returns=""), + Method("contacts_get", arguments="s", returns="a(sa{ss}as)"), + Method("contacts_get_from_group", arguments="ss", returns="as"), + Method("devices_infos_get", arguments="ss", returns="s"), + Method( + "disco_find_by_features", + arguments="asa(ss)bbbbbs", + returns="(a{sa(sss)}a{sa(sss)}a{sa(sss)})", + ), + Method("disco_infos", arguments="ssbs", returns="(asa(sss)a{sa(a{ss}as)})"), + Method("disco_items", arguments="ssbs", returns="a(sss)"), + Method("disconnect", arguments="s", returns=""), + Method("encryption_namespace_get", arguments="s", returns="s"), + Method("encryption_plugins_get", arguments="", returns="s"), + Method("encryption_trust_ui_get", arguments="sss", returns="s"), + Method("entities_data_get", arguments="asass", returns="a{sa{ss}}"), + Method("entity_data_get", arguments="sass", returns="a{ss}"), + Method("features_get", arguments="s", returns="a{sa{ss}}"), + Method("history_get", arguments="ssiba{ss}s", returns="a(sdssa{ss}a{ss}ss)"), + Method("image_check", arguments="s", returns="s"), + Method("image_convert", arguments="ssss", returns="s"), + Method("image_generate_preview", arguments="ss", returns="s"), + Method("image_resize", arguments="sii", returns="s"), + Method("init_pre_script", arguments="", returns=""), + Method("is_connected", arguments="s", returns="b"), + Method("main_resource_get", arguments="ss", returns="s"), + Method("menu_help_get", arguments="ss", returns="s"), + Method("menu_launch", arguments="sasa{ss}is", returns="a{ss}"), + Method("menus_get", arguments="si", returns="a(ssasasa{ss})"), + Method("message_encryption_get", arguments="ss", returns="s"), + Method("message_encryption_start", arguments="ssbs", returns=""), + Method("message_encryption_stop", arguments="ss", returns=""), + Method("message_send", arguments="sa{ss}a{ss}sss", returns=""), + Method("namespaces_get", arguments="", returns="a{ss}"), + Method("notification_add", arguments="ssssbbsdss", returns=""), + Method("notification_delete", arguments="sbs", returns=""), + Method("notifications_expired_clean", arguments="ds", returns=""), + Method("notifications_get", arguments="ss", returns="s"), + Method("param_get_a", arguments="ssss", returns="s"), + Method("param_get_a_async", arguments="sssis", returns="s"), + Method("param_set", arguments="sssis", returns=""), + Method("param_ui_get", arguments="isss", returns="s"), + Method("params_categories_get", arguments="", returns="as"), + Method("params_register_app", arguments="sis", returns=""), + Method("params_template_load", arguments="s", returns="b"), + Method("params_template_save", arguments="s", returns="b"), + Method( + "params_values_from_category_get_async", arguments="sisss", returns="a{ss}" + ), + Method("presence_set", arguments="ssa{ss}s", returns=""), + Method("presence_statuses_get", arguments="s", returns="a{sa{s(sia{ss})}}"), + Method("private_data_delete", arguments="sss", returns=""), + Method("private_data_get", arguments="sss", returns="s"), + Method("private_data_set", arguments="ssss", returns=""), + Method("profile_create", arguments="sss", returns=""), + Method("profile_delete_async", arguments="s", returns=""), + Method("profile_is_session_started", arguments="s", returns="b"), + Method("profile_name_get", arguments="s", returns="s"), + Method("profile_set_default", arguments="s", returns=""), + Method("profile_start_session", arguments="ss", returns="b"), + Method("profiles_list_get", arguments="bb", returns="as"), + Method("progress_get", arguments="ss", returns="a{ss}"), + Method("progress_get_all", arguments="s", returns="a{sa{sa{ss}}}"), + Method("progress_get_all_metadata", arguments="s", returns="a{sa{sa{ss}}}"), + Method("ready_get", arguments="", returns=""), + Method("roster_resync", arguments="s", returns=""), + Method("session_infos_get", arguments="s", returns="a{ss}"), + Method("sub_waiting_get", arguments="s", returns="a{ss}"), + Method("subscription", arguments="sss", returns=""), + Method("version_get", arguments="", returns="s"), + Signal("_debug", "sa{ss}s"), + Signal("action_new", "ssis"), + Signal("connected", "ss"), + Signal("contact_deleted", "ss"), + Signal("contact_new", "sa{ss}ass"), + Signal("disconnected", "s"), + Signal("entity_data_updated", "ssss"), + Signal("message_encryption_started", "sss"), + Signal("message_encryption_stopped", "sa{ss}s"), + Signal("message_new", "sdssa{ss}a{ss}sss"), + Signal("message_update", "ssss"), + Signal("notification_deleted", "ss"), + Signal("notification_new", "sdssssbidss"), + Signal("param_update", "ssss"), + Signal("presence_update", "ssia{ss}s"), + Signal("progress_error", "sss"), + Signal("progress_finished", "sa{ss}s"), + Signal("progress_started", "sa{ss}s"), + Signal("subscribe", "sss"), ) - plugin_iface = DBusInterface( - const_INT_PREFIX + const_PLUGIN_SUFFIX - ) + plugin_iface = DBusInterface(const_INT_PREFIX + const_PLUGIN_SUFFIX) dbusInterfaces = [core_iface, plugin_iface] @@ -213,7 +215,7 @@ def dbus_config_get(self, section, name): return self._callback("config_get", section, name) - def dbus_connect(self, profile_key="@DEFAULT@", password='', options={}): + def dbus_connect(self, profile_key="@DEFAULT@", password="", options={}): return self._callback("connect", profile_key, password, options) def dbus_contact_add(self, entity_jid, profile_key="@DEFAULT@"): @@ -237,13 +239,37 @@ def dbus_devices_infos_get(self, bare_jid, profile_key): return self._callback("devices_infos_get", bare_jid, profile_key) - def dbus_disco_find_by_features(self, namespaces, identities, bare_jid=False, service=True, roster=True, own_jid=True, local_device=False, profile_key="@DEFAULT@"): - return self._callback("disco_find_by_features", namespaces, identities, bare_jid, service, roster, own_jid, local_device, profile_key) + def dbus_disco_find_by_features( + self, + namespaces, + identities, + bare_jid=False, + service=True, + roster=True, + own_jid=True, + local_device=False, + profile_key="@DEFAULT@", + ): + return self._callback( + "disco_find_by_features", + namespaces, + identities, + bare_jid, + service, + roster, + own_jid, + local_device, + profile_key, + ) - def dbus_disco_infos(self, entity_jid, node=u'', use_cache=True, profile_key="@DEFAULT@"): + def dbus_disco_infos( + self, entity_jid, node="", use_cache=True, profile_key="@DEFAULT@" + ): return self._callback("disco_infos", entity_jid, node, use_cache, profile_key) - def dbus_disco_items(self, entity_jid, node=u'', use_cache=True, profile_key="@DEFAULT@"): + def dbus_disco_items( + self, entity_jid, node="", use_cache=True, profile_key="@DEFAULT@" + ): return self._callback("disco_items", entity_jid, node, use_cache, profile_key) def dbus_disconnect(self, profile_key="@DEFAULT@"): @@ -252,8 +278,12 @@ def dbus_encryption_namespace_get(self, arg_0): return self._callback("encryption_namespace_get", arg_0) - def dbus_encryption_plugins_get(self, ): - return self._callback("encryption_plugins_get", ) + def dbus_encryption_plugins_get( + self, + ): + return self._callback( + "encryption_plugins_get", + ) def dbus_encryption_trust_ui_get(self, to_jid, namespace, profile_key): return self._callback("encryption_trust_ui_get", to_jid, namespace, profile_key) @@ -267,8 +297,12 @@ def dbus_features_get(self, profile_key): return self._callback("features_get", profile_key) - def dbus_history_get(self, from_jid, to_jid, limit, between=True, filters='', profile="@NONE@"): - return self._callback("history_get", from_jid, to_jid, limit, between, filters, profile) + def dbus_history_get( + self, from_jid, to_jid, limit, between=True, filters="", profile="@NONE@" + ): + return self._callback( + "history_get", from_jid, to_jid, limit, between, filters, profile + ) def dbus_image_check(self, arg_0): return self._callback("image_check", arg_0) @@ -282,8 +316,12 @@ def dbus_image_resize(self, image_path, width, height): return self._callback("image_resize", image_path, width, height) - def dbus_init_pre_script(self, ): - return self._callback("init_pre_script", ) + def dbus_init_pre_script( + self, + ): + return self._callback( + "init_pre_script", + ) def dbus_is_connected(self, profile_key="@DEFAULT@"): return self._callback("is_connected", profile_key) @@ -295,7 +333,9 @@ return self._callback("menu_help_get", menu_id, language) def dbus_menu_launch(self, menu_type, path, data, security_limit, profile_key): - return self._callback("menu_launch", menu_type, path, data, security_limit, profile_key) + return self._callback( + "menu_launch", menu_type, path, data, security_limit, profile_key + ) def dbus_menus_get(self, language, security_limit): return self._callback("menus_get", language, security_limit) @@ -303,20 +343,62 @@ def dbus_message_encryption_get(self, to_jid, profile_key): return self._callback("message_encryption_get", to_jid, profile_key) - def dbus_message_encryption_start(self, to_jid, namespace='', replace=False, profile_key="@NONE@"): - return self._callback("message_encryption_start", to_jid, namespace, replace, profile_key) + def dbus_message_encryption_start( + self, to_jid, namespace="", replace=False, profile_key="@NONE@" + ): + return self._callback( + "message_encryption_start", to_jid, namespace, replace, profile_key + ) def dbus_message_encryption_stop(self, to_jid, profile_key): return self._callback("message_encryption_stop", to_jid, profile_key) - def dbus_message_send(self, to_jid, message, subject={}, mess_type="auto", extra={}, profile_key="@NONE@"): - return self._callback("message_send", to_jid, message, subject, mess_type, extra, profile_key) + def dbus_message_send( + self, + to_jid, + message, + subject={}, + mess_type="auto", + extra={}, + profile_key="@NONE@", + ): + return self._callback( + "message_send", to_jid, message, subject, mess_type, extra, profile_key + ) + + def dbus_namespaces_get( + self, + ): + return self._callback( + "namespaces_get", + ) - def dbus_namespaces_get(self, ): - return self._callback("namespaces_get", ) - - def dbus_notification_add(self, type_, body_plain, body_rich, title, is_global, requires_action, arg_6, priority, expire_at, extra): - return self._callback("notification_add", type_, body_plain, body_rich, title, is_global, requires_action, arg_6, priority, expire_at, extra) + def dbus_notification_add( + self, + type_, + body_plain, + body_rich, + title, + is_global, + requires_action, + arg_6, + priority, + expire_at, + extra, + ): + return self._callback( + "notification_add", + type_, + body_plain, + body_rich, + title, + is_global, + requires_action, + arg_6, + priority, + expire_at, + extra, + ) def dbus_notification_delete(self, id_, is_global, profile_key): return self._callback("notification_delete", id_, is_global, profile_key) @@ -327,22 +409,43 @@ def dbus_notifications_get(self, filters, profile_key): return self._callback("notifications_get", filters, profile_key) - def dbus_param_get_a(self, name, category, attribute="value", profile_key="@DEFAULT@"): + def dbus_param_get_a( + self, name, category, attribute="value", profile_key="@DEFAULT@" + ): return self._callback("param_get_a", name, category, attribute, profile_key) - def dbus_param_get_a_async(self, name, category, attribute="value", security_limit=-1, profile_key="@DEFAULT@"): - return self._callback("param_get_a_async", name, category, attribute, security_limit, profile_key) + def dbus_param_get_a_async( + self, + name, + category, + attribute="value", + security_limit=-1, + profile_key="@DEFAULT@", + ): + return self._callback( + "param_get_a_async", name, category, attribute, security_limit, profile_key + ) - def dbus_param_set(self, name, value, category, security_limit=-1, profile_key="@DEFAULT@"): - return self._callback("param_set", name, value, category, security_limit, profile_key) + def dbus_param_set( + self, name, value, category, security_limit=-1, profile_key="@DEFAULT@" + ): + return self._callback( + "param_set", name, value, category, security_limit, profile_key + ) - def dbus_param_ui_get(self, security_limit=-1, app='', extra='', profile_key="@DEFAULT@"): + def dbus_param_ui_get( + self, security_limit=-1, app="", extra="", profile_key="@DEFAULT@" + ): return self._callback("param_ui_get", security_limit, app, extra, profile_key) - def dbus_params_categories_get(self, ): - return self._callback("params_categories_get", ) + def dbus_params_categories_get( + self, + ): + return self._callback( + "params_categories_get", + ) - def dbus_params_register_app(self, xml, security_limit=-1, app=''): + def dbus_params_register_app(self, xml, security_limit=-1, app=""): return self._callback("params_register_app", xml, security_limit, app) def dbus_params_template_load(self, filename): @@ -351,10 +454,19 @@ def dbus_params_template_save(self, filename): return self._callback("params_template_save", filename) - def dbus_params_values_from_category_get_async(self, category, security_limit=-1, app="", extra="", profile_key="@DEFAULT@"): - return self._callback("params_values_from_category_get_async", category, security_limit, app, extra, profile_key) + def dbus_params_values_from_category_get_async( + self, category, security_limit=-1, app="", extra="", profile_key="@DEFAULT@" + ): + return self._callback( + "params_values_from_category_get_async", + category, + security_limit, + app, + extra, + profile_key, + ) - def dbus_presence_set(self, to_jid='', show='', statuses={}, profile_key="@DEFAULT@"): + def dbus_presence_set(self, to_jid="", show="", statuses={}, profile_key="@DEFAULT@"): return self._callback("presence_set", to_jid, show, statuses, profile_key) def dbus_presence_statuses_get(self, profile_key="@DEFAULT@"): @@ -369,7 +481,7 @@ def dbus_private_data_set(self, namespace, key, data, profile_key): return self._callback("private_data_set", namespace, key, data, profile_key) - def dbus_profile_create(self, profile, password='', component=''): + def dbus_profile_create(self, profile, password="", component=""): return self._callback("profile_create", profile, password, component) def dbus_profile_delete_async(self, profile): @@ -384,7 +496,7 @@ def dbus_profile_set_default(self, profile): return self._callback("profile_set_default", profile) - def dbus_profile_start_session(self, password='', profile_key="@DEFAULT@"): + def dbus_profile_start_session(self, password="", profile_key="@DEFAULT@"): return self._callback("profile_start_session", password, profile_key) def dbus_profiles_list_get(self, clients=True, components=False): @@ -399,8 +511,12 @@ def dbus_progress_get_all_metadata(self, profile): return self._callback("progress_get_all_metadata", profile) - def dbus_ready_get(self, ): - return self._callback("ready_get", ) + def dbus_ready_get( + self, + ): + return self._callback( + "ready_get", + ) def dbus_roster_resync(self, profile_key="@DEFAULT@"): return self._callback("roster_resync", profile_key) @@ -414,8 +530,12 @@ def dbus_subscription(self, sub_type, entity, profile_key="@DEFAULT@"): return self._callback("subscription", sub_type, entity, profile_key) - def dbus_version_get(self, ): - return self._callback("version_get", ) + def dbus_version_get( + self, + ): + return self._callback( + "version_get", + ) class bridge: @@ -462,13 +582,39 @@ self._obj.emitSignal("entity_data_updated", jid, name, value, profile) def message_encryption_started(self, to_jid, encryption_data, profile_key): - self._obj.emitSignal("message_encryption_started", to_jid, encryption_data, profile_key) + self._obj.emitSignal( + "message_encryption_started", to_jid, encryption_data, profile_key + ) def message_encryption_stopped(self, to_jid, encryption_data, profile_key): - self._obj.emitSignal("message_encryption_stopped", to_jid, encryption_data, profile_key) + self._obj.emitSignal( + "message_encryption_stopped", to_jid, encryption_data, profile_key + ) - def message_new(self, uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile): - self._obj.emitSignal("message_new", uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile) + def message_new( + self, + uid, + timestamp, + from_jid, + to_jid, + message, + subject, + mess_type, + extra, + profile, + ): + self._obj.emitSignal( + "message_new", + uid, + timestamp, + from_jid, + to_jid, + message, + subject, + mess_type, + extra, + profile, + ) def message_update(self, uid, message_type, message_data, profile): self._obj.emitSignal("message_update", uid, message_type, message_data, profile) @@ -476,14 +622,42 @@ def notification_deleted(self, id, profile): self._obj.emitSignal("notification_deleted", id, profile) - def notification_new(self, id, timestamp, type, body_plain, body_rich, title, requires_action, priority, expire_at, extra, profile): - self._obj.emitSignal("notification_new", id, timestamp, type, body_plain, body_rich, title, requires_action, priority, expire_at, extra, profile) + def notification_new( + self, + id, + timestamp, + type, + body_plain, + body_rich, + title, + requires_action, + priority, + expire_at, + extra, + profile, + ): + self._obj.emitSignal( + "notification_new", + id, + timestamp, + type, + body_plain, + body_rich, + title, + requires_action, + priority, + expire_at, + extra, + profile, + ) def param_update(self, name, value, category, profile): self._obj.emitSignal("param_update", name, value, category, profile) def presence_update(self, entity_jid, show, priority, statuses, profile): - self._obj.emitSignal("presence_update", entity_jid, show, priority, statuses, profile) + self._obj.emitSignal( + "presence_update", entity_jid, show, priority, statuses, profile + ) def progress_error(self, id, error, profile): self._obj.emitSignal("progress_error", id, error, profile) @@ -505,7 +679,7 @@ self._obj.emitSignal(name, *args) def add_method( - self, name, int_suffix, in_sign, out_sign, method, async_=False, doc={} + self, name, int_suffix, in_sign, out_sign, method, async_=False, doc={} ): """Dynamically add a method to D-Bus bridge""" # FIXME: doc parameter is kept only temporary, the time to remove it from calls @@ -513,10 +687,12 @@ self._obj.plugin_iface.addMethod( Method(name, arguments=in_sign, returns=out_sign) ) + # we have to create a method here instead of using partialmethod, because txdbus # uses __func__ which doesn't work with partialmethod def caller(self_, *args, **kwargs): return self_._callback(name, *args, **kwargs) + setattr(self._obj, f"dbus_{name}", MethodType(caller, self._obj)) self.register_method(name, method) @@ -524,4 +700,4 @@ """Dynamically add a signal to D-Bus bridge""" log.debug(f"Adding signal {name!r} to D-Bus bridge") self._obj.plugin_iface.addSignal(Signal(name, signature)) - setattr(bridge, name, partialmethod(bridge.emit_signal, name)) \ No newline at end of file + setattr(bridge, name, partialmethod(bridge.emit_signal, name)) diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/bridge/pb.py --- a/libervia/backend/bridge/pb.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/bridge/pb.py Wed Jun 19 18:44:57 2024 +0200 @@ -99,6 +99,7 @@ del self.signals_paused log.debug("bridge signals have been reactivated") + ##METHODS_PART## @@ -136,7 +137,7 @@ #  self.root.register_method(name, callback) def add_method( - self, name, int_suffix, in_sign, out_sign, method, async_=False, doc={} + 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 @@ -185,13 +186,39 @@ self.send_signal("entity_data_updated", jid, name, value, profile) def message_encryption_started(self, to_jid, encryption_data, profile_key): - self.send_signal("message_encryption_started", to_jid, encryption_data, profile_key) + self.send_signal( + "message_encryption_started", to_jid, encryption_data, profile_key + ) def message_encryption_stopped(self, to_jid, encryption_data, profile_key): - self.send_signal("message_encryption_stopped", to_jid, encryption_data, profile_key) + self.send_signal( + "message_encryption_stopped", to_jid, encryption_data, profile_key + ) - def message_new(self, uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile): - self.send_signal("message_new", uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile) + def message_new( + self, + uid, + timestamp, + from_jid, + to_jid, + message, + subject, + mess_type, + extra, + profile, + ): + self.send_signal( + "message_new", + uid, + timestamp, + from_jid, + to_jid, + message, + subject, + mess_type, + extra, + profile, + ) def message_update(self, uid, message_type, message_data, profile): self.send_signal("message_update", uid, message_type, message_data, profile) @@ -199,8 +226,34 @@ def notification_deleted(self, id, profile): self.send_signal("notification_deleted", id, profile) - def notification_new(self, id, timestamp, type, body_plain, body_rich, title, requires_action, priority, expire_at, extra, profile): - self.send_signal("notification_new", id, timestamp, type, body_plain, body_rich, title, requires_action, priority, expire_at, extra, profile) + def notification_new( + self, + id, + timestamp, + type, + body_plain, + body_rich, + title, + requires_action, + priority, + expire_at, + extra, + profile, + ): + self.send_signal( + "notification_new", + id, + timestamp, + type, + body_plain, + body_rich, + title, + requires_action, + priority, + expire_at, + extra, + profile, + ) def param_update(self, name, value, category, profile): self.send_signal("param_update", name, value, category, profile) diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/core/constants.py --- a/libervia/backend/core/constants.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/core/constants.py Wed Jun 19 18:44:57 2024 +0200 @@ -46,9 +46,7 @@ APP_NAME_ALT = "Libervia" APP_NAME_FILE = "libervia" APP_NAME_FULL = f"{APP_NAME} ({APP_COMPONENT})" - APP_VERSION = ( - backend.__version__ - ) + APP_VERSION = backend.__version__ APP_RELEASE_NAME = "La Ruche" APP_URL = "https://libervia.org" @@ -117,8 +115,8 @@ ## Roster jids selection ## PUBLIC = "PUBLIC" ALL = ( - "ALL" - ) # ALL means all known contacts, while PUBLIC means everybody, known or not + "ALL" # ALL means all known contacts, while PUBLIC means everybody, known or not + ) GROUP = "GROUP" JID = "JID" @@ -226,9 +224,7 @@ PI_IMPORT_NAME = "import_name" PI_MAIN = "main" PI_HANDLER = "handler" - PI_TYPE = ( - "type" - ) #  FIXME: should be types, and should handle single unicode type or tuple of types (e.g. "blog" and "import") + PI_TYPE = "type" #  FIXME: should be types, and should handle single unicode type or tuple of types (e.g. "blog" and "import") PI_MODES = "modes" PI_PROTOCOLS = "protocols" PI_DEPENDENCIES = "dependencies" @@ -405,14 +401,13 @@ KEY_ATTACHMENTS_PREVIEW = "preview" KEY_ATTACHMENTS_RESIZE = "resize" - ## Common extra keys/values ## KEY_ORDER_BY = "order_by" KEY_USE_CACHE = "use_cache" KEY_DECRYPT = "decrypt" - ORDER_BY_CREATION = 'creation' - ORDER_BY_MODIFICATION = 'modification' + ORDER_BY_CREATION = "creation" + ORDER_BY_MODIFICATION = "modification" # internationalisation DEFAULT_LOCALE = "en_GB" @@ -432,9 +427,7 @@ EXIT_CONFLICT = 19 # an item already exists EXIT_USER_CANCELLED = 20 # user cancelled action EXIT_INTERNAL_ERROR = 111 # unexpected error - 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_CMD_ERROR = 127 # a utility to be executed returned an error exit code EXIT_SIGNAL_INT = 128 # a command was interrupted by a signal (cf. man 1 exit) @@ -492,8 +485,9 @@ # we don't want the very verbose jnius log when we are in DEBUG level import logging - logging.getLogger('jnius').setLevel(logging.WARNING) - logging.getLogger('jnius.reflect').setLevel(logging.WARNING) + + logging.getLogger("jnius").setLevel(logging.WARNING) + logging.getLogger("jnius.reflect").setLevel(logging.WARNING) Environment = autoclass("android.os.Environment") @@ -518,9 +512,10 @@ ] else: import os + # we use parent of "sat" module dir as last config path, this is useful for # per instance configurations (e.g. a dev instance and a main instance) - root_dir = dirname(dirname(backend.__file__)) + '/' + root_dir = dirname(dirname(backend.__file__)) + "/" Const.CONFIG_PATHS = ( # /etc/_sat.conf is used for system-related settings (e.g. when media_dir # is set by the distribution and has not reason to change, or in a Docker @@ -541,10 +536,10 @@ # on recent versions of Flatpak, FLATPAK_ID is set at run time # it seems that this is not the case on older versions, # but FLATPAK_SANDBOX_DIR seems set then - if os.getenv('FLATPAK_ID') or os.getenv('FLATPAK_SANDBOX_DIR'): + if os.getenv("FLATPAK_ID") or os.getenv("FLATPAK_SANDBOX_DIR"): # for Flatpak, the conf can't be set in /etc or $HOME, so we have # to add /app - Const.CONFIG_PATHS.append('/app/') + Const.CONFIG_PATHS.append("/app/") ## Configuration ## Const.DEFAULT_CONFIG = { @@ -564,4 +559,3 @@ realpath(expanduser(path) + "sat.conf") for path in Const.CONFIG_PATHS ] - diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/core/core_types.py --- a/libervia/backend/core/core_types.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/core/core_types.py Wed Jun 19 18:44:57 2024 +0200 @@ -34,11 +34,10 @@ server_jid: t_jid.JID IQ: Callable[[Optional[str], Optional[int]], xmlstream.IQ] -EncryptionPlugin = namedtuple("EncryptionPlugin", ("instance", - "name", - "namespace", - "priority", - "directed")) + +EncryptionPlugin = namedtuple( + "EncryptionPlugin", ("instance", "name", "namespace", "priority", "directed") +) class EncryptionSession(TypedDict): @@ -47,21 +46,23 @@ # Incomplete types built through observation rather than code inspection. MessageDataExtra = TypedDict( - "MessageDataExtra", - { "encrypted": bool, "origin_id": str }, - total=False + "MessageDataExtra", {"encrypted": bool, "origin_id": str}, total=False ) -MessageData = TypedDict("MessageData", { - "from": t_jid.JID, - "to": t_jid.JID, - "uid": str, - "message": Dict[str, str], - "subject": Dict[str, str], - "type": str, - "timestamp": float, - "extra": MessageDataExtra, - "ENCRYPTION": EncryptionSession, - "xml": domish.Element -}, total=False) +MessageData = TypedDict( + "MessageData", + { + "from": t_jid.JID, + "to": t_jid.JID, + "uid": str, + "message": Dict[str, str], + "subject": Dict[str, str], + "type": str, + "timestamp": float, + "extra": MessageDataExtra, + "ENCRYPTION": EncryptionSession, + "xml": domish.Element, + }, + total=False, +) diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/core/exceptions.py --- a/libervia/backend/core/exceptions.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/core/exceptions.py Wed Jun 19 18:44:57 2024 +0200 @@ -62,6 +62,7 @@ class MissingPlugin(Exception): """A SàT plugin needed for a feature/method is missing""" + pass @@ -129,6 +130,7 @@ class EncryptionError(Exception): """Invalid encryption""" + pass @@ -143,6 +145,7 @@ class InvalidCertificate(Exception): """A TLS certificate is not valid""" + pass diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/core/i18n.py --- a/libervia/backend/core/i18n.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/core/i18n.py Wed Jun 19 18:44:57 2024 +0200 @@ -37,7 +37,6 @@ ) _translators[lang].install() - except ImportError: log.warning("gettext support disabled") diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/core/launcher.py --- a/libervia/backend/core/launcher.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/core/launcher.py Wed Jun 19 18:44:57 2024 +0200 @@ -201,11 +201,7 @@ return pid_dir / f"{self.APP_NAME_FILE}.pid" def wait_for_service( - self, - service_host: str, - service_port: int, - timeout: int, - service_name: str + self, service_host: str, service_port: int, timeout: int, service_name: str ) -> None: """Waits for a network service to become available. diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/core/log.py --- a/libervia/backend/core/log.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/core/log.py Wed Jun 19 18:44:57 2024 +0200 @@ -36,8 +36,8 @@ backend = None _loggers: Dict[str, "Logger"] = {} handlers = {} -COLOR_START = '%(color_start)s' -COLOR_END = '%(color_end)s' +COLOR_START = "%(color_start)s" +COLOR_END = "%(color_end)s" class Filtered(Exception): @@ -46,8 +46,9 @@ class Logger: """High level logging class""" - fmt = None # format option as given by user (e.g. SAT_LOG_LOGGER) - filter_name = None # filter to call + + fmt = None # format option as given by user (e.g. SAT_LOG_LOGGER) + filter_name = None # filter to call post_treat = None def __init__(self, name): @@ -72,7 +73,7 @@ message: object, level: Optional[str] = None, exc_info: _ExcInfoType = False, - **kwargs + **kwargs, ) -> None: """Actually log the message @@ -83,11 +84,7 @@ print(message) def log( - self, - level: str, - message: object, - exc_info: _ExcInfoType = False, - **kwargs + self, level: str, message: object, exc_info: _ExcInfoType = False, **kwargs ) -> None: """Print message @@ -116,14 +113,18 @@ """ if self.fmt is None and self.filter_name is None: return message - record = {'name': self._name, - 'message': message, - 'levelname': level, - } + record = { + "name": self._name, + "message": message, + "levelname": level, + } try: if not self.filter_name.dict_filter(record): raise Filtered - except (AttributeError, TypeError): # XXX: TypeError is here because of a pyjamas bug which need to be fixed (TypeError is raised instead of AttributeError) + except ( + AttributeError, + TypeError, + ): # XXX: TypeError is here because of a pyjamas bug which need to be fixed (TypeError is raised instead of AttributeError) if self.filter_name is not None: raise ValueError("Bad filter: filters must have a .filter method") try: @@ -131,9 +132,9 @@ except TypeError: return message except KeyError as e: - if e.args[0] == 'profile': + if e.args[0] == "profile": # XXX: %(profile)s use some magic with introspection, for debugging purpose only *DO NOT* use in production - record['profile'] = configure_cls[backend].get_profile() + record["profile"] = configure_cls[backend].get_profile() return self.fmt % record else: raise e @@ -167,6 +168,7 @@ """ assert name_re import re + self.name_re = re.compile(name_re) def filter(self, record): @@ -180,10 +182,12 @@ @param dict_record: dictionary with at list a key "name" with logger name @return: True if message should be logged """ + class LogRecord(object): pass + log_record = LogRecord() - log_record.name = dict_record['name'] + log_record.name = dict_record["name"] return self.filter(log_record) == 1 @@ -192,8 +196,17 @@ # True if color location is specified in fmt (with COLOR_START) _color_location = False - 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, + ): """Configure a backend @param level: one of C.LOG_LEVELS @@ -231,19 +244,21 @@ if level is not None: # we deactivate methods below level level_idx = C.LOG_LEVELS.index(level) + def dev_null(self, msg): pass + for _level in C.LOG_LEVELS[:level_idx]: setattr(Logger, _level.lower(), dev_null) def configure_format(self, fmt): if fmt is not None: - if fmt != '%(message)s': # %(message)s is the same as None + if fmt != "%(message)s": # %(message)s is the same as None Logger.fmt = fmt if COLOR_START in fmt: ConfigureBase._color_location = True - if fmt.find(COLOR_END,fmt.rfind(COLOR_START))<0: - # color_start not followed by an end, we add it + if fmt.find(COLOR_END, fmt.rfind(COLOR_START)) < 0: + # color_start not followed by an end, we add it Logger.fmt += COLOR_END def configure_output(self, output): @@ -265,12 +280,14 @@ taints = self.__class__.taints = {} for level in C.LOG_LEVELS: # we want use values and use constant value as default - taint_list = levels_taints_dict.get(level, C.LOG_OPT_TAINTS_DICT[1][level]) + taint_list = levels_taints_dict.get( + level, C.LOG_OPT_TAINTS_DICT[1][level] + ) ansi_list = [] for elt in taint_list: elt = elt.upper() try: - ansi = getattr(A, 'FG_{}'.format(elt)) + ansi = getattr(A, "FG_{}".format(elt)) except AttributeError: try: ansi = getattr(A, elt) @@ -278,13 +295,13 @@ # we use raw string if element is unknown ansi = elt ansi_list.append(ansi) - taints[level] = ''.join(ansi_list) + taints[level] = "".join(ansi_list) def post_treatment(self): pass def manage_outputs(self, outputs_raw): - """ Parse output option in a backend agnostic way, and fill handlers consequently + """Parse output option in a backend agnostic way, and fill handlers consequently @param outputs_raw: output option as enterred in environment variable or in configuration """ @@ -298,15 +315,19 @@ for output in outputs: if not output: continue - if output[-1] == ')': + if output[-1] == ")": # we have options - opt_begin = output.rfind('(') - options = output[opt_begin+1:-1] + opt_begin = output.rfind("(") + options = output[opt_begin + 1 : -1] output = output[:opt_begin] else: options = None - if output not in (C.LOG_OPT_OUTPUT_DEFAULT, C.LOG_OPT_OUTPUT_FILE, C.LOG_OPT_OUTPUT_MEMORY): + if output not in ( + C.LOG_OPT_OUTPUT_DEFAULT, + C.LOG_OPT_OUTPUT_FILE, + C.LOG_OPT_OUTPUT_MEMORY, + ): raise ValueError("Invalid output [%s]" % output) if output == C.LOG_OPT_OUTPUT_DEFAULT: @@ -314,20 +335,26 @@ handlers[output] = None elif output == C.LOG_OPT_OUTPUT_FILE: if not options: - ValueError("{handler} output need a path as option" .format(handle=output)) + ValueError( + "{handler} output need a path as option".format(handle=output) + ) handlers.setdefault(output, []).append(options) - options = None # option are parsed, we can empty them + options = None # option are parsed, we can empty them elif output == C.LOG_OPT_OUTPUT_MEMORY: # we have memory handler, option can be the len limit or None try: limit = int(options) - options = None # option are parsed, we can empty them + options = None # option are parsed, we can empty them except (TypeError, ValueError): limit = C.LOG_OPT_OUTPUT_MEMORY_LIMIT handlers[output] = limit - if options: # we should not have unparsed options - raise ValueError("options [{options}] are not supported for {handler} output".format(options=options, handler=output)) + if options: # we should not have unparsed options + raise ValueError( + "options [{options}] are not supported for {handler} output".format( + options=options, handler=output + ) + ) @staticmethod def memory_get(size=None): @@ -349,13 +376,12 @@ try: start = cls.taints[level] except KeyError: - start = '' + start = "" if cls._color_location: - return message % {'color_start': start, - 'color_end': A.RESET} + return message % {"color_start": start, "color_end": A.RESET} else: - return '%s%s%s' % (start, message, A.RESET) + return "%s%s%s" % (start, message, A.RESET) @staticmethod def get_profile(): @@ -370,9 +396,10 @@ ConfigureCustom.LOGGER_CLASS = logger_class -configure_cls = { None: ConfigureBase, - C.LOG_BACKEND_CUSTOM: ConfigureCustom - } # XXX: (key: backend, value: Configure subclass) must be filled when new backend are added +configure_cls = { + None: ConfigureBase, + C.LOG_BACKEND_CUSTOM: ConfigureCustom, +} # XXX: (key: backend, value: Configure subclass) must be filled when new backend are added def configure(backend_, **options): @@ -391,36 +418,46 @@ except KeyError: raise ValueError("unknown backend [{}]".format(backend)) if backend == C.LOG_BACKEND_CUSTOM: - logger_class = options.pop('logger_class') + logger_class = options.pop("logger_class") configure_class(logger_class, **options) else: configure_class(**options) + def memory_get(size=None): if not C.LOG_OPT_OUTPUT_MEMORY in handlers: - raise ValueError('memory output is not used') + raise ValueError("memory output is not used") return configure_cls[backend].memory_get(size) + def getLogger(name=C.LOG_BASE_LOGGER) -> Logger: try: logger_class = configure_cls[backend].LOGGER_CLASS except KeyError: - raise ValueError("This method should not be called with backend [{}]".format(backend)) + raise ValueError( + "This method should not be called with backend [{}]".format(backend) + ) return _loggers.setdefault(name, logger_class(name)) + _root_logger = getLogger() + def debug(msg, **kwargs): _root_logger.debug(msg, **kwargs) + def info(msg, **kwargs): _root_logger.info(msg, **kwargs) + def warning(msg, **kwargs): _root_logger.warning(msg, **kwargs) + def error(msg, **kwargs): _root_logger.error(msg, **kwargs) + def critical(msg, **kwargs): _root_logger.critical(msg, **kwargs) diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/core/log_config.py --- a/libervia/backend/core/log_config.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/core/log_config.py Wed Jun 19 18:44:57 2024 +0200 @@ -31,6 +31,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) from twisted.logger import Logger + self.twisted_log = Logger() def out(self, message, level=None, **kwargs): @@ -59,7 +60,7 @@ except AttributeError: isatty = False # FIXME: isatty should be tested on each handler, not globaly - if (force_colors or isatty): + if force_colors or isatty: # we need colors log.Logger.post_treat = lambda logger, level, message: self.ansi_colors( level, message @@ -109,6 +110,7 @@ def pre_treatment(self): from twisted import logger + global logger self.level_map = { C.LOG_LVL_DEBUG: logger.LogLevel.debug, @@ -125,6 +127,7 @@ def configure_output(self, output): import sys from twisted.python import logfile + self.log_publisher = logger.LogPublisher() if output is None: @@ -137,13 +140,15 @@ "You must pass options as backend_data with Twisted backend" ) options = self.backend_data - log_file = logfile.LogFile.fromFullPath(options['logfile']) + log_file = logfile.LogFile.fromFullPath(options["logfile"]) self.log_publisher.addObserver( - logger.FileLogObserver(log_file, self.text_formatter)) + logger.FileLogObserver(log_file, self.text_formatter) + ) # we also want output to stdout if we are in debug or nodaemon mode if options.get("nodaemon", False) or options.get("debug", False): self.log_publisher.addObserver( - logger.FileLogObserver(sys.stdout, self.text_formatter)) + logger.FileLogObserver(sys.stdout, self.text_formatter) + ) if C.LOG_OPT_OUTPUT_FILE in log.handlers: @@ -152,7 +157,8 @@ sys.stdout if path == "-" else logfile.LogFile.fromFullPath(path) ) self.log_publisher.addObserver( - logger.FileLogObserver(log_file, self.text_formatter)) + logger.FileLogObserver(log_file, self.text_formatter) + ) if C.LOG_OPT_OUTPUT_MEMORY in log.handlers: raise NotImplementedError( @@ -172,21 +178,21 @@ """Install twistedObserver which manage non SàT logs""" # from twisted import logger import sys + filtering_obs = logger.FilteringLogObserver( observer=self.log_publisher, predicates=[ logger.LogLevelFilterPredicate(self.level), - ] + ], ) logger.globalLogBeginner.beginLoggingTo([filtering_obs]) def text_formatter(self, event): - if event.get('sat_logged', False): - timestamp = ''.join([logger.formatTime(event.get("log_time", None)), " "]) + if event.get("sat_logged", False): + timestamp = "".join([logger.formatTime(event.get("log_time", None)), " "]) return f"{timestamp}{event.get('log_format', '')}\n" else: - eventText = logger.eventAsText( - event, includeSystem=True) + eventText = logger.eventAsText(event, includeSystem=True) if not eventText: return None return eventText.replace("\n", "\n\t") + "\n" @@ -241,6 +247,7 @@ class SatFormatter(logging.Formatter): """Formatter which manage SàT specificities""" + _format = fmt _with_profile = "%(profile)s" in fmt @@ -310,11 +317,9 @@ 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._add_handler(root_logger, hdlr, can_colors=False) elif handler == C.LOG_OPT_OUTPUT_FILE: import os.path diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/core/main.py --- a/libervia/backend/core/main.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/core/main.py Wed Jun 19 18:44:57 2024 +0200 @@ -29,6 +29,7 @@ from libervia import backend from libervia.backend.core.i18n import _, D_, language_switch from libervia.backend.core import patches + patches.apply() from twisted.application import service from twisted.internet import defer @@ -57,6 +58,7 @@ log = getLogger(__name__) + class LiberviaBackend(service.Service): def _init(self): @@ -67,7 +69,7 @@ # dynamic menus. key: callback_id, value: menu data (dictionnary) self._menus = {} self._menus_paths = {} # path to id. key: (menu_type, lower case tuple of path), - # value: menu id + # value: menu id # like initialised, but launched before init script is done, mainly useful for CLI # frontend, so it can be used in init script, while other frontends are waiting. @@ -75,7 +77,7 @@ self.initialised = defer.Deferred() self.profiles = {} self.plugins = {} - # map for short name to whole namespace, + # map for short name to whole namespace, # extended by plugins with register_namespace self.ns_map = { "x-data": xmpp.NS_X_DATA, @@ -85,13 +87,10 @@ self.memory = memory.Memory(self) # trigger are used to change Libervia behaviour - self.trigger = ( - trigger.TriggerManager() - ) + self.trigger = trigger.TriggerManager() - bridge_name = ( - os.getenv("LIBERVIA_BRIDGE_NAME") - or self.memory.config_get("", "bridge", "dbus") + bridge_name = os.getenv("LIBERVIA_BRIDGE_NAME") or self.memory.config_get( + "", "bridge", "dbus" ) bridge_module = dynamic_import.bridge(bridge_name) @@ -120,7 +119,6 @@ else: self.local_shared_path = None - defer.ensureDeferred(self._post_init()) @property @@ -174,40 +172,58 @@ self.bridge.register_method("entity_data_get", self.memory._get_entity_data) self.bridge.register_method("entities_data_get", self.memory._get_entities_data) self.bridge.register_method("profile_create", self.memory.create_profile) - self.bridge.register_method("profile_delete_async", self.memory.profile_delete_async) + self.bridge.register_method( + "profile_delete_async", self.memory.profile_delete_async + ) self.bridge.register_method("profile_start_session", self.memory.start_session) self.bridge.register_method( "profile_is_session_started", self.memory._is_session_started ) - self.bridge.register_method("profile_set_default", self.memory.profile_set_default) + self.bridge.register_method( + "profile_set_default", self.memory.profile_set_default + ) self.bridge.register_method("connect", self._connect) self.bridge.register_method("disconnect", self.disconnect) self.bridge.register_method("contact_get", self._contact_get) self.bridge.register_method("contacts_get", self.contacts_get) - self.bridge.register_method("contacts_get_from_group", self.contacts_get_from_group) + self.bridge.register_method( + "contacts_get_from_group", self.contacts_get_from_group + ) self.bridge.register_method("main_resource_get", self.memory._get_main_resource) self.bridge.register_method( "presence_statuses_get", self.memory._get_presence_statuses ) self.bridge.register_method("sub_waiting_get", self.memory.sub_waiting_get) self.bridge.register_method("message_send", self._message_send) - self.bridge.register_method("message_encryption_start", - self._message_encryption_start) - self.bridge.register_method("message_encryption_stop", - self._message_encryption_stop) - self.bridge.register_method("message_encryption_get", - self._message_encryption_get) - self.bridge.register_method("encryption_namespace_get", - self._encryption_namespace_get) - self.bridge.register_method("encryption_plugins_get", self._encryption_plugins_get) - self.bridge.register_method("encryption_trust_ui_get", self._encryption_trust_ui_get) + self.bridge.register_method( + "message_encryption_start", self._message_encryption_start + ) + self.bridge.register_method( + "message_encryption_stop", self._message_encryption_stop + ) + self.bridge.register_method( + "message_encryption_get", self._message_encryption_get + ) + self.bridge.register_method( + "encryption_namespace_get", self._encryption_namespace_get + ) + self.bridge.register_method( + "encryption_plugins_get", self._encryption_plugins_get + ) + self.bridge.register_method( + "encryption_trust_ui_get", self._encryption_trust_ui_get + ) self.bridge.register_method("config_get", self._get_config) self.bridge.register_method("param_set", self.param_set) self.bridge.register_method("param_get_a", self.memory.get_string_param_a) self.bridge.register_method("private_data_get", self.memory._private_data_get) self.bridge.register_method("private_data_set", self.memory._private_data_set) - self.bridge.register_method("private_data_delete", self.memory._private_data_delete) - self.bridge.register_method("param_get_a_async", self.memory.async_get_string_param_a) + self.bridge.register_method( + "private_data_delete", self.memory._private_data_delete + ) + self.bridge.register_method( + "param_get_a_async", self.memory.async_get_string_param_a + ) self.bridge.register_method( "params_values_from_category_get_async", self.memory._get_params_values_from_category, @@ -216,7 +232,9 @@ self.bridge.register_method( "params_categories_get", self.memory.params_categories_get ) - self.bridge.register_method("params_register_app", self.memory.params_register_app) + self.bridge.register_method( + "params_register_app", self.memory.params_register_app + ) self.bridge.register_method("history_get", self.memory._history_get) self.bridge.register_method("presence_set", self._set_presence) self.bridge.register_method("subscription", self.subscription) @@ -242,13 +260,18 @@ self.bridge.register_method("namespaces_get", self.get_namespaces) self.bridge.register_method("image_check", self._image_check) self.bridge.register_method("image_resize", self._image_resize) - self.bridge.register_method("image_generate_preview", self._image_generate_preview) + self.bridge.register_method( + "image_generate_preview", self._image_generate_preview + ) self.bridge.register_method("image_convert", self._image_convert) self.bridge.register_method("notification_add", self.memory._add_notification) self.bridge.register_method("notifications_get", self.memory._get_notifications) - self.bridge.register_method("notification_delete", self.memory._delete_notification) - self.bridge.register_method("notifications_expired_clean", self.memory._notifications_expired_clean) - + self.bridge.register_method( + "notification_delete", self.memory._delete_notification + ) + self.bridge.register_method( + "notifications_expired_clean", self.memory._notifications_expired_clean + ) await self.memory.initialise() self.common_cache = cache.Cache(self, None) @@ -277,10 +300,7 @@ else: log.info(f"Running init script {init_script!r}.") try: - await async_process.run( - str(init_script), - verbose=True - ) + await async_process.run(str(init_script), verbose=True) except RuntimeError as e: log.error(f"Init script failed: {e}") self.stopService() @@ -292,15 +312,18 @@ # profile autoconnection must be done after self.initialised is called because # start_session waits for it. autoconnect_dict = await self.memory.storage.get_ind_param_values( - category='Connection', name='autoconnect_backend', + category="Connection", + name="autoconnect_backend", ) profiles_autoconnect = [p for p, v in autoconnect_dict.items() if C.bool(v)] if not self.trigger.point("profilesAutoconnect", profiles_autoconnect): return if profiles_autoconnect: - log.info(D_( - "Following profiles will be connected automatically: {profiles}" - ).format(profiles= ', '.join(profiles_autoconnect))) + log.info( + D_( + "Following profiles will be connected automatically: {profiles}" + ).format(profiles=", ".join(profiles_autoconnect)) + ) connect_d_list = [] for profile in profiles_autoconnect: connect_d_list.append(defer.ensureDeferred(self.connect(profile))) @@ -312,8 +335,8 @@ profile = profiles_autoconnect[0] log.warning( _("Can't autoconnect profile {profile}: {reason}").format( - profile = profile, - reason = result) + profile=profile, reason=result + ) ) def _add_base_menus(self): @@ -342,7 +365,8 @@ init_path = plug_path / f"__init__.{C.PLUGIN_EXT}" if not init_path.exists(): log.warning( - f"{plug_path} doesn't appear to be a package, can't load it") + f"{plug_path} doesn't appear to be a package, can't load it" + ) continue plug_name = plug_path.name elif plug_path.is_file(): @@ -350,12 +374,12 @@ continue plug_name = plug_path.stem else: - log.warning( - f"{plug_path} is not a file or a dir, ignoring it") + log.warning(f"{plug_path} is not a file or a dir, ignoring it") continue if not plug_name.isidentifier(): log.warning( - f"{plug_name!r} is not a valid name for a plugin, ignoring it") + f"{plug_name!r} is not a valid name for a plugin, ignoring it" + ) continue plugin_path = f"libervia.backend.plugins.{plug_name}" try: @@ -364,9 +388,7 @@ self._unimport_plugin(plugin_path) log.warning( "Can't import plugin [{path}] because of an unavailale third party " - "module:\n{msg}".format( - path=plugin_path, msg=e - ) + "module:\n{msg}".format(path=plugin_path, msg=e) ) continue except exceptions.CancelError as e: @@ -450,9 +472,7 @@ else: if not import_name in plugins_to_import: if optional: - log.warning( - _("Recommended plugin not found: {}").format(import_name) - ) + log.warning(_("Recommended plugin not found: {}").format(import_name)) return msg = "Dependency not found: {}".format(import_name) log.error(msg) @@ -521,7 +541,8 @@ return defer.ensureDeferred(self.connect(profile, password, options)) async def connect( - self, profile, password="", options=None, max_retries=C.XMPP_MAX_RETRIES): + 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 @@ -761,16 +782,14 @@ try: return self.ns_map[short_name] except KeyError: - raise exceptions.NotFound("namespace {short_name} is not registered" - .format(short_name=short_name)) + raise exceptions.NotFound( + "namespace {short_name} is not registered".format(short_name=short_name) + ) def get_session_infos(self, profile_key): """compile interesting data on current profile session""" client = self.get_client(profile_key) - data = { - "jid": client.jid.full(), - "started": str(int(client.started)) - } + data = {"jid": client.jid.full(), "started": str(int(client.started))} return defer.succeed(data) def _get_devices_infos(self, bare_jid, profile_key): @@ -808,11 +827,11 @@ "resource": resource, } try: - presence = cache_data['presence'] + presence = cache_data["presence"] except KeyError: pass else: - res_data['presence'] = { + res_data["presence"] = { "show": presence.show, "priority": presence.priority, "statuses": presence.statuses, @@ -821,12 +840,14 @@ disco = await self.get_disco_infos(client, res_jid) for (category, type_), name in disco.identities.items(): - identities = res_data.setdefault('identities', []) - identities.append({ - "name": name, - "category": category, - "type": type_, - }) + identities = res_data.setdefault("identities", []) + identities.append( + { + "name": name, + "category": category, + "type": type_, + } + ) ret_data.append(res_data) @@ -857,7 +878,7 @@ """ report = image.check(self, path, max_size=(300, 300)) - if not report['too_large']: + if not report["too_large"]: # in the unlikely case that image is already smaller than a preview preview_path = path else: @@ -867,17 +888,14 @@ filename = f"{uid}{path.suffix.lower()}" metadata = client.cache.get_metadata(uid=uid) if metadata is not None: - preview_path = metadata['path'] + preview_path = metadata["path"] else: with client.cache.cache_data( - source='HOST_PREVIEW', - uid=uid, - filename=filename) as cache_f: + source="HOST_PREVIEW", uid=uid, filename=filename + ) as cache_f: preview_path = await image.resize( - path, - new_size=report['recommended_size'], - dest=cache_f + path, new_size=report["recommended_size"], dest=cache_f ) return preview_path @@ -922,23 +940,19 @@ metadata = cache.get_metadata(uid=uid) if metadata is not None: # there is already a conversion for this image in cache - return metadata['path'] + return metadata["path"] else: with cache.cache_data( - source='HOST_IMAGE_CONVERT', - uid=uid, - filename=filename) as cache_f: + source="HOST_IMAGE_CONVERT", uid=uid, filename=filename + ) as cache_f: converted_path = await image.convert( - source, - dest=cache_f, - extra=extra + source, dest=cache_f, extra=extra ) return converted_path else: return await image.convert(source, dest, extra) - # local dirs def get_local_path( @@ -998,18 +1012,19 @@ def register_encryption_plugin(self, *args, **kwargs): return encryption.EncryptionHandler.register_plugin(*args, **kwargs) - def _message_encryption_start(self, to_jid_s, namespace, replace=False, - profile_key=C.PROF_KEY_NONE): + def _message_encryption_start( + self, to_jid_s, namespace, replace=False, profile_key=C.PROF_KEY_NONE + ): client = self.get_client(profile_key) to_jid = jid.JID(to_jid_s) return defer.ensureDeferred( - client.encryption.start(to_jid, namespace or None, replace)) + client.encryption.start(to_jid, namespace or None, replace) + ) def _message_encryption_stop(self, to_jid_s, profile_key=C.PROF_KEY_NONE): client = self.get_client(profile_key) to_jid = jid.JID(to_jid_s) - return defer.ensureDeferred( - client.encryption.stop(to_jid)) + return defer.ensureDeferred(client.encryption.stop(to_jid)) def _message_encryption_get(self, to_jid_s, profile_key=C.PROF_KEY_NONE): client = self.get_client(profile_key) @@ -1024,42 +1039,48 @@ plugins = encryption.EncryptionHandler.getPlugins() ret = [] for p in plugins: - ret.append({ - "name": p.name, - "namespace": p.namespace, - "priority": p.priority, - "directed": p.directed, - }) + ret.append( + { + "name": p.name, + "namespace": p.namespace, + "priority": p.priority, + "directed": p.directed, + } + ) return data_format.serialise(ret) def _encryption_trust_ui_get(self, to_jid_s, namespace, profile_key): client = self.get_client(profile_key) to_jid = jid.JID(to_jid_s) d = defer.ensureDeferred( - client.encryption.get_trust_ui(to_jid, namespace=namespace or None)) + client.encryption.get_trust_ui(to_jid, namespace=namespace or None) + ) d.addCallback(lambda xmlui: xmlui.toXml()) return d ## XMPP methods ## def _message_send( - self, to_jid_s, message, subject=None, mess_type="auto", extra_s="", - profile_key=C.PROF_KEY_NONE): + self, + to_jid_s, + message, + subject=None, + mess_type="auto", + extra_s="", + profile_key=C.PROF_KEY_NONE, + ): client = self.get_client(profile_key) to_jid = jid.JID(to_jid_s) return client.sendMessage( - to_jid, - message, - subject, - mess_type, - data_format.deserialise(extra_s) + to_jid, message, subject, mess_type, data_format.deserialise(extra_s) ) def _set_presence(self, to="", show="", statuses=None, profile_key=C.PROF_KEY_NONE): return self.presence_set(jid.JID(to) if to else None, show, statuses, profile_key) - def presence_set(self, to_jid=None, show="", statuses=None, - profile_key=C.PROF_KEY_NONE): + def presence_set( + self, to_jid=None, show="", statuses=None, profile_key=C.PROF_KEY_NONE + ): """Send our presence information""" if statuses is None: statuses = {} @@ -1116,7 +1137,7 @@ def contact_update(self, client, to_jid, name, groups): """update a contact in roster list""" roster_item = RosterItem(to_jid) - roster_item.name = name or u'' + roster_item.name = name or "" roster_item.groups = set(groups) if not self.trigger.point("roster_update", client, roster_item): return @@ -1167,28 +1188,46 @@ def find_features_set(self, *args, **kwargs): return self.memory.disco.find_features_set(*args, **kwargs) - def _find_by_features(self, namespaces, identities, bare_jids, service, roster, own_jid, - local_device, profile_key): + def _find_by_features( + self, + namespaces, + identities, + bare_jids, + service, + roster, + own_jid, + local_device, + profile_key, + ): client = self.get_client(profile_key) identities = [tuple(i) for i in identities] if identities else None - return defer.ensureDeferred(self.find_by_features( - client, namespaces, identities, bare_jids, service, roster, own_jid, - local_device)) + return defer.ensureDeferred( + self.find_by_features( + client, + namespaces, + identities, + bare_jids, + service, + roster, + own_jid, + local_device, + ) + ) async def find_by_features( self, client: SatXMPPEntity, namespaces: List[str], - identities: Optional[List[Tuple[str, str]]]=None, - bare_jids: bool=False, - service: bool=True, - roster: bool=True, - own_jid: bool=True, - local_device: bool=False + identities: Optional[List[Tuple[str, str]]] = None, + bare_jids: bool = False, + service: bool = True, + roster: bool = True, + own_jid: bool = True, + local_device: bool = False, ) -> Tuple[ Dict[jid.JID, Tuple[str, str, str]], Dict[jid.JID, Tuple[str, str, str]], - Dict[jid.JID, Tuple[str, str, str]] + Dict[jid.JID, Tuple[str, str, str]], ]: """Retrieve all services or contacts managing a set a features @@ -1224,19 +1263,25 @@ if service: services_jids = await self.find_features_set(client, namespaces) services_jids = list(services_jids) # we need a list to map results below - services_infos = await defer.DeferredList( - [self.get_disco_infos(client, service_jid) for service_jid in services_jids] + services_infos = await defer.DeferredList( + [ + self.get_disco_infos(client, service_jid) + for service_jid in services_jids + ] ) for idx, (success, infos) in enumerate(services_infos): service_jid = services_jids[idx] if not success: log.warning( - _("Can't find features for service {service_jid}, ignoring") - .format(service_jid=service_jid.full())) + _( + "Can't find features for service {service_jid}, ignoring" + ).format(service_jid=service_jid.full()) + ) continue - 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 "") @@ -1291,8 +1336,10 @@ full_jid = full_jids[idx] if not success: log.warning( - _("Can't retrieve {full_jid} infos, ignoring") - .format(full_jid=full_jid.full())) + _("Can't retrieve {full_jid} infos, ignoring").format( + full_jid=full_jid.full() + ) + ) continue if infos.features.issuperset(namespaces): if identities is not None and not set( @@ -1476,21 +1523,16 @@ return callback_id def remove_callback(self, callback_id): - """ Remove a previously registered callback - @param callback_id: id returned by [register_callback] """ + """Remove a previously registered callback + @param callback_id: id returned by [register_callback]""" log.debug("Removing callback [%s]" % callback_id) del self._cb_map[callback_id] def _action_launch( - self, - callback_id: str, - data_s: str, - profile_key: str + self, callback_id: str, data_s: str, profile_key: str ) -> defer.Deferred: d = self.launch_callback( - callback_id, - data_format.deserialise(data_s), - profile_key + callback_id, data_format.deserialise(data_s), profile_key ) d.addCallback(data_format.serialise) return d @@ -1499,7 +1541,7 @@ self, callback_id: str, data: Optional[dict] = None, - profile_key: str = C.PROF_KEY_NONE + profile_key: str = C.PROF_KEY_NONE, ) -> defer.Deferred: """Launch a specific callback @@ -1568,8 +1610,14 @@ """ return tuple((p.lower().strip() for p in path)) - def import_menu(self, path, callback, security_limit=C.NO_SECURITY_LIMIT, - help_string="", type_=C.MENU_GLOBAL): + def import_menu( + self, + path, + callback, + security_limit=C.NO_SECURITY_LIMIT, + help_string="", + type_=C.MENU_GLOBAL, + ): r"""register a new menu for frontends @param path(iterable[unicode]): path to go to the menu @@ -1677,13 +1725,20 @@ return ret - def _launch_menu(self, menu_type, path, data=None, security_limit=C.NO_SECURITY_LIMIT, - profile_key=C.PROF_KEY_NONE): + def _launch_menu( + self, + menu_type, + path, + data=None, + security_limit=C.NO_SECURITY_LIMIT, + profile_key=C.PROF_KEY_NONE, + ): client = self.get_client(profile_key) return self.launch_menu(client, menu_type, path, data, security_limit) - def launch_menu(self, client, menu_type, path, data=None, - security_limit=C.NO_SECURITY_LIMIT): + def launch_menu( + 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 diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/core/xmpp.py --- a/libervia/backend/core/xmpp.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/core/xmpp.py Wed Jun 19 18:44:57 2024 +0200 @@ -91,6 +91,7 @@ class SatXMPPEntity(core_types.SatXMPPEntity): """Common code for Client and Component""" + # profile is added there when start_connection begins and removed when it is finished profiles_connecting = set() @@ -102,9 +103,11 @@ clientConnectionFailed_ori = factory.clientConnectionFailed clientConnectionLost_ori = factory.clientConnectionLost factory.clientConnectionFailed = partial( - self.connection_terminated, term_type="failed", cb=clientConnectionFailed_ori) + self.connection_terminated, term_type="failed", cb=clientConnectionFailed_ori + ) factory.clientConnectionLost = partial( - self.connection_terminated, term_type="lost", cb=clientConnectionLost_ori) + self.connection_terminated, term_type="lost", cb=clientConnectionLost_ori + ) factory.maxRetries = max_retries factory.maxDelay = 30 @@ -115,11 +118,11 @@ self.host_app = host_app self.cache = cache.Cache(host_app, profile) self.mess_id2uid = {} # map from message id to uid used in history. - # Key: (full_jid, message_id) Value: uid + # Key: (full_jid, message_id) Value: uid # this Deferred fire when entity is connected self.conn_deferred = defer.Deferred() self._progress_cb = {} # callback called when a progress is requested - # (key = progress id) + # (key = progress id) self.actions = {} # used to keep track of actions for retrieval (key = action_id) self.encryption = encryption.EncryptionHandler(self) @@ -148,9 +151,7 @@ # profile_connecting/profile_connected methods handling - timer = connection_timer[plugin] = { - "total": 0 - } + timer = connection_timer[plugin] = {"total": 0} # profile connecting is called right now (before actually starting client) connecting_cb = getattr(plugin, "profile_connecting", None) if connecting_cb is not None: @@ -187,9 +188,7 @@ @staticmethod async def _run_profile_connected( - callback: Callable, - entity: "SatXMPPEntity", - timer: Dict[str, float] + callback: Callable, entity: "SatXMPPEntity", timer: Dict[str, float] ) -> None: connected_start = time.time() await utils.as_deferred(callback, entity) @@ -217,16 +216,15 @@ ) 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 = await host.memory.param_get_a_async( "Password", "Connection", profile_key=profile ) entity_jid_s = await host.memory.param_get_a_async( - "JabberID", "Connection", profile_key=profile) + "JabberID", "Connection", profile_key=profile + ) entity_jid = jid.JID(entity_jid_s) if not entity_jid.resource and not cls.is_component and entity_jid.user: @@ -235,32 +233,43 @@ # reconnection. we only do that for client and if there is a user part, to # let server decide for anonymous login resource_dict = await host.memory.storage.get_privates( - "core:xmpp", ["resource"] , profile=profile) + "core:xmpp", ["resource"], profile=profile + ) try: resource = resource_dict["resource"] except KeyError: resource = f"{C.APP_NAME_FILE}.{shortuuid.uuid()}" await host.memory.storage.set_private_value( - "core:xmpp", "resource", resource, profile=profile) + "core:xmpp", "resource", resource, profile=profile + ) - log.info(_("We'll use the stable resource {resource}").format( - resource=resource)) + log.info( + _("We'll use the stable resource {resource}").format( + resource=resource + ) + ) entity_jid.resource = resource if profile in host.profiles: if host.profiles[profile].is_connected(): raise exceptions.InternalError( f"There is already a connected profile of name {profile!r} in " - f"host") - log.debug( - "removing unconnected profile {profile!r}") + f"host" + ) + log.debug("removing unconnected profile {profile!r}") del host.profiles[profile] entity = host.profiles[profile] = cls( - host, profile, entity_jid, password, - host.memory.param_get_a(C.FORCE_SERVER_PARAM, "Connection", - profile_key=profile) or None, - port, max_retries, + host, + profile, + entity_jid, + password, + host.memory.param_get_a( + C.FORCE_SERVER_PARAM, "Connection", profile_key=profile ) + or None, + port, + max_retries, + ) await entity.encryption.load_sessions() @@ -315,7 +324,7 @@ plugins_by_timer = sorted( connection_timer, key=lambda p: connection_timer[p]["total"], - reverse=True + reverse=True, ) # total is the addition of all connecting and connected, doesn't really # reflect the real loading time as connected are launched in a @@ -441,8 +450,9 @@ # we save connector because it may be deleted when connection will be dropped # if reconnection is disabled self._saved_connector = connector - if reason is not None and not isinstance(reason.value, - internet_error.ConnectionDone): + if reason is not None and not isinstance( + reason.value, internet_error.ConnectionDone + ): try: reason_str = str(reason.value) except Exception: @@ -496,8 +506,7 @@ def _connected(self, xs): send_hooks = [] receive_hooks = [] - self.host_app.trigger.point( - "stream_hooks", self, receive_hooks, send_hooks) + self.host_app.trigger.point("stream_hooks", self, receive_hooks, send_hooks) for hook in receive_hooks: xs.add_hook(C.STREAM_HOOK_RECEIVE, hook) for hook in send_hooks: @@ -529,11 +538,14 @@ try: if err.value.args[0][0][2] == "certificate verify failed": err = exceptions.InvalidCertificate( - _("Your server certificate is not valid " - "(its identity can't be checked).\n\n" - "This should never happen and may indicate that " - "somebody is trying to spy on you.\n" - "Please contact your server administrator.")) + _( + "Your server certificate is not valid " + "(its identity can't be checked).\n\n" + "This should never happen and may indicate that " + "somebody is trying to spy on you.\n" + "Please contact your server administrator." + ) + ) self.factory.stopTrying() try: # with invalid certificate, we should not retry to connect @@ -615,7 +627,7 @@ def generate_message_xml( self, data: core_types.MessageData, - post_xml_treatments: Optional[defer.Deferred] = None + post_xml_treatments: Optional[defer.Deferred] = None, ) -> core_types.MessageData: """Generate stanza from message data @@ -710,14 +722,22 @@ # This is intented for e2e encryption which doesn't do full stanza # encryption (e.g. OTR) # This trigger point can't cancel the method - await self.host_app.trigger.async_point("send_message_data", self, mess_data, - triggers_no_cancel=True) + await self.host_app.trigger.async_point( + "send_message_data", self, mess_data, triggers_no_cancel=True + ) await self.a_send(mess_data["xml"]) return mess_data def sendMessage( - self, to_jid, message, subject=None, mess_type="auto", extra=None, uid=None, - no_trigger=False): + self, + to_jid, + message, + subject=None, + mess_type="auto", + extra=None, + uid=None, + no_trigger=False, + ): r"""Send a message to an entity @param to_jid(jid.JID): destinee of the message @@ -797,18 +817,27 @@ ): return defer.succeed(None) - log.debug(_("Sending message (type {type}, to {to})") - .format(type=data["type"], to=to_jid.full())) + log.debug( + _("Sending message (type {type}, to {to})").format( + type=data["type"], to=to_jid.full() + ) + ) - pre_xml_treatments.addCallback(lambda __: self.generate_message_xml(data, post_xml_treatments)) + pre_xml_treatments.addCallback( + lambda __: self.generate_message_xml(data, post_xml_treatments) + ) pre_xml_treatments.addCallback(lambda __: post_xml_treatments) pre_xml_treatments.addErrback(self._cancel_error_trap) post_xml_treatments.addCallback( lambda __: defer.ensureDeferred(self.send_message_data(data)) ) 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.add_post_xml_callbacks(post_xml_treatments) post_xml_treatments.addErrback(self._cancel_error_trap) @@ -823,7 +852,8 @@ def is_message_printable(self, mess_data): """Return True if a message contain payload to show in frontends""" return ( - mess_data["message"] or mess_data["subject"] + mess_data["message"] + or mess_data["subject"] or mess_data["extra"].get(C.KEY_ATTACHMENTS) or mess_data["type"] == C.MESS_TYPE_INFO ) @@ -849,10 +879,16 @@ def message_get_bridge_args(self, data): """Generate args to use with bridge from data dict""" - return (data["uid"], data["timestamp"], data["from"].full(), - data["to"].full(), data["message"], data["subject"], - data["type"], data_format.serialise(data["extra"])) - + return ( + data["uid"], + data["timestamp"], + data["from"].full(), + data["to"].full(), + data["message"], + data["subject"], + data["type"], + data_format.serialise(data["extra"]), + ) def message_send_to_bridge(self, data): """Send message to bridge, so frontends can display it @@ -869,8 +905,7 @@ # We send back the message, so all frontends are aware of it self.host_app.bridge.message_new( - *self.message_get_bridge_args(data), - profile=self.profile + *self.message_get_bridge_args(data), profile=self.profile ) else: log.warning(_("No message found")) @@ -900,8 +935,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() @@ -933,12 +976,14 @@ host_data = None if host_data is not None: log.info( - "using {host}:{port} for host {host_ori} as requested in config" - .format(host_ori=user_jid.host, host=host, port=port) + "using {host}:{port} for host {host_ori} as requested in config".format( + host_ori=user_jid.host, host=host, port=port + ) ) self.check_certificate = host_app.memory.param_get_a( - "check_certificate", "Connection", profile_key=profile) + "check_certificate", "Connection", profile_key=profile + ) if self.check_certificate: tls_required, configurationForTLS = True, None @@ -947,18 +992,26 @@ configurationForTLS = ssl.CertificateOptions(trustRoot=None) wokkel_client.XMPPClient.__init__( - self, user_jid, password, host or None, port or C.XMPP_C2S_PORT, - tls_required=tls_required, configurationForTLS=configurationForTLS + self, + user_jid, + password, + host or None, + port or C.XMPP_C2S_PORT, + tls_required=tls_required, + configurationForTLS=configurationForTLS, ) SatXMPPEntity.__init__(self, host_app, profile, max_retries) if not self.check_certificate: - msg = (_("Certificate validation is deactivated, this is unsecure and " + msg = _( + "Certificate validation is deactivated, this is unsecure and " "somebody may be spying on you. If you have no good reason to disable " - "certificate validation, please activate \"Check certificate\" in your " - "settings in \"Connection\" tab.")) - xml_tools.quick_note(host_app, self, msg, _("Security notice"), - level = C.XMLUI_DATA_LVL_WARNING) + 'certificate validation, please activate "Check certificate" in your ' + 'settings in "Connection" tab.' + ) + xml_tools.quick_note( + host_app, self, msg, _("Security notice"), level=C.XMLUI_DATA_LVL_WARNING + ) @property def server_jid(self): @@ -1002,10 +1055,7 @@ post_xml_treatments.addCallback(self.message_send_to_bridge) def feedback( - self, - to_jid: jid.JID, - message: str, - extra: Optional[ExtraDict] = None + self, to_jid: jid.JID, message: str, extra: Optional[ExtraDict] = None ) -> None: """Send message to frontends @@ -1045,16 +1095,24 @@ """ trigger_suffix = ( - "Component" - ) # used for to distinguish some trigger points set in SatXMPPEntity + "Component" # used for to distinguish some trigger points set in SatXMPPEntity + ) is_component = True # XXX: set to True from entry plugin to keep messages in history for sent messages sendHistory = False # XXX: same as sendHistory but for received messaged receiveHistory = False - 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 @@ -1178,12 +1236,12 @@ @param to_jid: destination JID of the request """ try: - unescape = self.host_app.plugins['XEP-0106'].unescape + unescape = self.host_app.plugins["XEP-0106"].unescape except KeyError: raise exceptions.MissingPlugin("Plugin XEP-0106 is needed to retrieve owner") else: user = unescape(to_jid.user) - if '@' in user: + if "@" in user: # a full jid is specified return jid.JID(user) else: @@ -1199,7 +1257,7 @@ @param iq_elt: IQ stanza sent from the requested @return: owner and peer JIDs """ - to_jid = jid.JID(iq_elt['to']) + to_jid = jid.JID(iq_elt["to"]) if to_jid.user: owner = self.get_owner_from_jid(to_jid) else: @@ -1227,7 +1285,7 @@ def __init__(self, host): xmppim.MessageProtocol.__init__(self) self.host = host - self.messages_queue = defer.DeferredQueue() + self.messages_queue = defer.DeferredQueue() def setHandlerParent(self, parent): super().setHandlerParent(parent) @@ -1252,23 +1310,31 @@ @return(dict): message data """ if message_elt.name != "message": - log.warning(_( - "parse_message used with a non stanza, ignoring: {xml}" - .format(xml=message_elt.toXml()))) + log.warning( + _( + "parse_message used with a non stanza, ignoring: {xml}".format( + xml=message_elt.toXml() + ) + ) + ) return {} if message_elt.uri == None: # xmlns may be None when wokkel element parsing strip out root namespace self.normalize_ns(message_elt, None) elif message_elt.uri != C.NS_CLIENT: - log.warning(_( - "received with a wrong namespace: {xml}" - .format(xml=message_elt.toXml()))) + log.warning( + _( + "received with a wrong namespace: {xml}".format( + xml=message_elt.toXml() + ) + ) + ) client = self.parent - if not message_elt.hasAttribute('to'): - message_elt['to'] = client.jid.full() + if not message_elt.hasAttribute("to"): + message_elt["to"] = client.jid.full() message = {} subject = {} @@ -1306,8 +1372,11 @@ except AttributeError: # message_elt._received_timestamp should have been set in onMessage # but if parse_message is called directly, it can be missing - log.debug("missing received timestamp for {message_elt}".format( - message_elt=message_elt)) + log.debug( + "missing received timestamp for {message_elt}".format( + message_elt=message_elt + ) + ) received_timestamp = time.time() try: @@ -1321,10 +1390,9 @@ if parsed_delay.sender: data["delay_sender"] = parsed_delay.sender.full() - self.host.trigger.point("message_parse", client, message_elt, data) + self.host.trigger.point("message_parse", client, message_elt, data) return data - def onMessage(self, message_elt: domish.Element) -> None: message_elt._received_timestamp = time.time() self.messages_queue.put(message_elt) @@ -1347,9 +1415,7 @@ log.exception(f"Can't process message {message_elt.toXml()}") def _on_processing_timeout( - self, - message_elt: domish.Element, - async_point_d: defer.Deferred + self, message_elt: domish.Element, async_point_d: defer.Deferred ) -> None: log.error( "Processing of following message took too long, cancelling:" @@ -1358,9 +1424,7 @@ async_point_d.cancel() async def process_message( - self, - client: SatXMPPEntity, - message_elt: domish.Element + self, client: SatXMPPEntity, message_elt: domish.Element ) -> None: # TODO: handle threads if not "from" in message_elt.attributes: @@ -1372,16 +1436,15 @@ # plugin can add their treatments to this deferred post_treat = defer.Deferred() - async_point_d = defer.ensureDeferred(self.host.trigger.async_point( - "message_received", client, message_elt, post_treat - )) + async_point_d = defer.ensureDeferred( + self.host.trigger.async_point( + "message_received", client, message_elt, post_treat + ) + ) # message_received triggers block the messages queue, so they must not take too # long to proceed. delayed_call = reactor.callLater( - 10, - self._on_processing_timeout, - message_elt, - async_point_d + 10, self._on_processing_timeout, message_elt, async_point_d ) trigger_ret_continue = await async_point_d @@ -1411,14 +1474,15 @@ def complete_attachments(self, data: MessageData) -> MessageData: """Complete missing metadata of attachments""" - for attachment in data['extra'].get(C.KEY_ATTACHMENTS, []): + for attachment in data["extra"].get(C.KEY_ATTACHMENTS, []): if "name" not in attachment and "url" in attachment: - name = (Path(unquote(urlparse(attachment['url']).path)).name - or C.FILE_DEFAULT_NAME) + name = ( + Path(unquote(urlparse(attachment["url"]).path)).name + or C.FILE_DEFAULT_NAME + ) attachment["name"] = name - if ((C.KEY_ATTACHMENTS_MEDIA_TYPE not in attachment - and "name" in attachment)): - media_type = mimetypes.guess_type(attachment['name'], strict=False)[0] + if C.KEY_ATTACHMENTS_MEDIA_TYPE not in attachment and "name" in attachment: + media_type = mimetypes.guess_type(attachment["name"], strict=False)[0] if media_type: attachment[C.KEY_ATTACHMENTS_MEDIA_TYPE] = media_type return data @@ -1432,8 +1496,9 @@ if self.parent.is_message_printable(data): return await self.host.memory.add_to_history(self.parent, data) else: - log.debug("not storing empty message to history: {data}" - .format(data=data)) + log.debug( + "not storing empty message to history: {data}".format(data=data) + ) return data def bridge_signal(self, data: MessageData) -> MessageData: @@ -1459,8 +1524,9 @@ profile=self.parent.profile, ) else: - log.debug("Discarding bridge signal for empty message: {data}".format( - data=data)) + log.debug( + "Discarding bridge signal for empty message: {data}".format(data=data) + ) return data @@ -1480,7 +1546,7 @@ @property def versioning(self): """True if server support roster versioning""" - return (NS_ROSTER_VER, 'ver') in self.parent.xmlstream.features + return (NS_ROSTER_VER, "ver") in self.parent.xmlstream.features @property def roster_cache(self): @@ -1547,7 +1613,7 @@ @defer.inlineCallbacks def request_roster(self): - """Ask the server for Roster list """ + """Ask the server for Roster list""" if self.versioning: log.info(_("our server support roster versioning, we use it")) roster_cache = self.roster_cache @@ -1565,7 +1631,7 @@ if roster_jid_s == ROSTER_VER_KEY: continue roster_jid = jid.JID(roster_jid_s) - roster_item_elt = generic.parseXml(roster_item_elt_s.encode('utf-8')) + roster_item_elt = generic.parseXml(roster_item_elt_s.encode("utf-8")) roster_item = xmppim.RosterItem.fromElement(roster_item_elt) self._jids[roster_jid] = roster_item self._register_item(roster_item) @@ -1576,8 +1642,10 @@ log.debug("requesting roster") roster = yield self.getRoster(version=version) if roster is None: - log.debug("empty roster result received, we'll get roster item with roster " - "pushes") + log.debug( + "empty roster result received, we'll get roster item with roster " + "pushes" + ) else: # a full roster is received self._groups.clear() @@ -1589,9 +1657,7 @@ # may change in the future log.info( "Removing contact {} from roster because there is no presence " - "subscription".format( - item.jid - ) + "subscription".format(item.jid) ) self.removeItem(item.entity) # FIXME: to be checked else: @@ -1646,8 +1712,10 @@ self._jids[entity] = item self._register_item(item) self.host.bridge.contact_new( - entity.full(), self.get_attributes(item), list(item.groups), - self.parent.profile + entity.full(), + self.get_attributes(item), + list(item.groups), + self.parent.profile, ) def removeReceived(self, request): @@ -1710,7 +1778,8 @@ """Return True if jid is in roster""" if not isinstance(entity_jid, jid.JID): raise exceptions.InternalError( - f"a JID is expected, not {type(entity_jid)}: {entity_jid!r}") + f"a JID is expected, not {type(entity_jid)}: {entity_jid!r}" + ) return entity_jid in self._jids def is_subscribed_from(self, entity_jid: jid.JID) -> bool: @@ -1825,7 +1894,12 @@ statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop(None) if not self.host.trigger.point( - "presence_received", self.parent, entity, C.PRESENCE_UNAVAILABLE, 0, statuses, + "presence_received", + self.parent, + entity, + C.PRESENCE_UNAVAILABLE, + 0, + statuses, ): return @@ -1833,9 +1907,7 @@ # 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.get_entity_datum( - self.client, entity, "presence" - ) + presence = self.host.memory.get_entity_datum(self.client, entity, "presence") except (KeyError, exceptions.UnknownEntityError): # the entity has not been seen yet in this session pass @@ -1951,9 +2023,11 @@ def getDiscoInfo(self, requestor, target, nodeIdentifier=""): # those features are implemented in Wokkel (or sat_tmp.wokkel) # and thus are always available - return [disco.DiscoFeature(NS_X_DATA), - disco.DiscoFeature(NS_XML_ELEMENT), - disco.DiscoFeature(NS_DISCO_INFO)] + return [ + disco.DiscoFeature(NS_X_DATA), + disco.DiscoFeature(NS_XML_ELEMENT), + disco.DiscoFeature(NS_DISCO_INFO), + ] def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] @@ -1985,6 +2059,7 @@ @implementer(iwokkel.IDisco) class SatIdentityHandler(XMPPHandler): """Manage disco Identity of SàT.""" + # TODO: dynamic identity update (see docstring). Note that a XMPP entity can have # several identities diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/memory/crypto.py --- a/libervia/backend/memory/crypto.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/memory/crypto.py Wed Jun 19 18:44:57 2024 +0200 @@ -57,7 +57,9 @@ cipher = Cipher(algorithms.AES(key), modes.CFB8(iv), backend=crypto_backend) encryptor = cipher.encryptor() - encrypted = encryptor.update(BlockCipher.pad(text.encode())) + encryptor.finalize() + encrypted = ( + encryptor.update(BlockCipher.pad(text.encode())) + encryptor.finalize() + ) return b64encode(iv + encrypted).decode() @staticmethod @@ -149,7 +151,7 @@ length=16, salt=salt, iterations=1000, - backend=crypto_backend + backend=crypto_backend, ) key = kdf.derive(password.encode()) return b64encode(salt + key).decode() diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/memory/disco.py --- a/libervia/backend/memory/disco.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/memory/disco.py Wed Jun 19 18:44:57 2024 +0200 @@ -111,7 +111,7 @@ class Discovery(object): - """ Manage capabilities of entities """ + """Manage capabilities of entities""" def __init__(self, host): self.host = host @@ -172,7 +172,7 @@ category: str, type_: str, jid_: Optional[jid.JID] = None, - node: str = "" + node: str = "", ) -> bool: """Tell if an entity has the requested identity @@ -264,9 +264,9 @@ if jid_ == client.server_jid and not node: # we cache items only for our own server and if node is not set try: - items = self.host.memory.entity_data_get( - client, jid_, ["DISCO_ITEMS"] - )["DISCO_ITEMS"] + items = self.host.memory.entity_data_get(client, jid_, ["DISCO_ITEMS"])[ + "DISCO_ITEMS" + ] log.debug("[%s] disco items are in cache" % jid_.full()) if not use_cache: # we ignore cache, so we pretend we haven't found it @@ -274,9 +274,7 @@ except (KeyError, exceptions.UnknownEntityError): log.debug("Caching [%s] disco items" % jid_.full()) items = yield client.disco.requestItems(jid_, nodeIdentifier=node) - self.host.memory.update_entity_data( - client, jid_, "DISCO_ITEMS", items - ) + self.host.memory.update_entity_data(client, jid_, "DISCO_ITEMS", items) else: try: items = yield client.disco.requestItems(jid_, nodeIdentifier=node) @@ -381,7 +379,7 @@ return d def generate_hash(self, services): - """ Generate a unique hash for given service + """Generate a unique hash for given service hash algorithm is the one described in XEP-0115 @param services: iterable of disco.DiscoIdentity/disco.DiscoFeature, as returned by discoHandler.info @@ -413,22 +411,22 @@ # extensions ext = list(services.extensions.values()) - ext.sort(key=lambda f: f.formNamespace.encode('utf-8')) + ext.sort(key=lambda f: f.formNamespace.encode("utf-8")) for extension in ext: - s.append(extension.formNamespace.encode('utf-8')) + s.append(extension.formNamespace.encode("utf-8")) s.append(b"<") fields = extension.fieldList - fields.sort(key=lambda f: f.var.encode('utf-8')) + fields.sort(key=lambda f: f.var.encode("utf-8")) for field in fields: - s.append(field.var.encode('utf-8')) + s.append(field.var.encode("utf-8")) s.append(b"<") - values = [v.encode('utf-8') for v in field.values] + values = [v.encode("utf-8") for v in field.values] values.sort() for value in values: s.append(value) s.append(b"<") - cap_hash = b64encode(sha1(b"".join(s)).digest()).decode('utf-8') + cap_hash = b64encode(sha1(b"".join(s)).digest()).decode("utf-8") log.debug(_("Capability hash generated: [{cap_hash}]").format(cap_hash=cap_hash)) return cap_hash @@ -464,11 +462,16 @@ extensions[form_type or ""] = fields - defer.returnValue(( - [str(f) for f in disco_infos.features], - [(cat, type_, name or "") - for (cat, type_), name in list(disco_infos.identities.items())], - extensions)) + defer.returnValue( + ( + [str(f) for f in disco_infos.features], + [ + (cat, type_, name or "") + for (cat, type_), name in list(disco_infos.identities.items()) + ], + extensions, + ) + ) def items2tuples(self, disco_items): """convert disco items to tuple of strings @@ -486,7 +489,7 @@ def _disco_items( self, entity_jid_s, node="", use_cache=True, profile_key=C.PROF_KEY_NONE ): - """ Discovery method for the bridge + """Discovery method for the bridge @param entity_jid_s: entity we want to discover @param node(unicode): optional node to use diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/memory/encryption.py --- a/libervia/backend/memory/encryption.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/memory/encryption.py Wed Jun 19 18:44:57 2024 +0200 @@ -23,7 +23,11 @@ from twisted.words.protocols.jabber import jid from twisted.internet import defer from twisted.python import failure -from libervia.backend.core.core_types import EncryptionPlugin, EncryptionSession, MessageData +from libervia.backend.core.core_types import ( + EncryptionPlugin, + EncryptionSession, + MessageData, +) from libervia.backend.core.i18n import D_, _ from libervia.backend.core.constants import Const as C from libervia.backend.core import exceptions @@ -38,13 +42,15 @@ class EncryptionHandler: """Class to handle encryption sessions for a client""" + plugins = [] # plugin able to encrypt messages def __init__(self, client): self.client = client self._sessions = {} # bare_jid ==> encryption_data self._stored_session = persistent.PersistentDict( - "core:encryption", profile=client.profile) + "core:encryption", profile=client.profile + ) @property def host(self): @@ -63,9 +69,11 @@ for idx, (success, err) in enumerate(result): if not success: entity_jid_s, namespace = list(self._stored_session.items())[idx] - log.warning(_( - "Could not restart {namespace!r} encryption with {entity}: {err}" - ).format(namespace=namespace, entity=entity_jid_s, err=err)) + log.warning( + _( + "Could not restart {namespace!r} encryption with {entity}: {err}" + ).format(namespace=namespace, entity=entity_jid_s, err=err) + ) log.info(_("encryption sessions restored")) @classmethod @@ -105,7 +113,8 @@ name=name, namespace=namespace, priority=priority, - directed=directed) + directed=directed, + ) cls.plugins.append(plugin) cls.plugins.sort(key=lambda p: p.priority) log.info(_("Encryption plugin registered: {name}").format(name=name)) @@ -119,9 +128,11 @@ try: return next(p for p in cls.plugins if p.namespace == namespace) except StopIteration: - raise exceptions.NotFound(_( - "Can't find requested encryption plugin: {namespace}").format( - namespace=namespace)) + raise exceptions.NotFound( + _("Can't find requested encryption plugin: {namespace}").format( + namespace=namespace + ) + ) @classmethod def get_namespaces(cls): @@ -139,9 +150,9 @@ for p in cls.plugins: if p.name.lower() == name.lower(): return p.namespace - raise exceptions.NotFound(_( - "Can't find a plugin with the name \"{name}\".".format( - name=name))) + raise exceptions.NotFound( + _('Can\'t find a plugin with the name "{name}".'.format(name=name)) + ) def get_bridge_data(self, session): """Retrieve session data serialized for bridge. @@ -150,12 +161,11 @@ @return (unicode): serialized data for bridge """ if session is None: - return '' - plugin = session['plugin'] - bridge_data = {'name': plugin.name, - 'namespace': plugin.namespace} - if 'directed_devices' in session: - bridge_data['directed_devices'] = session['directed_devices'] + return "" + plugin = session["plugin"] + bridge_data = {"name": plugin.name, "namespace": plugin.namespace} + if "directed_devices" in session: + bridge_data["directed_devices"] = session["directed_devices"] return data_format.serialise(bridge_data) @@ -205,8 +215,12 @@ it will be replaced by the new one """ if not self.plugins: - raise exceptions.NotFound(_("No encryption plugin is registered, " - "an encryption session can't be started")) + raise exceptions.NotFound( + _( + "No encryption plugin is registered, " + "an encryption session can't be started" + ) + ) if namespace is None: plugin = self.plugins[0] @@ -218,9 +232,12 @@ # we have already an encryption session with this contact former_plugin = self._sessions[bare_jid]["plugin"] if former_plugin.namespace == namespace: - log.info(_("Session with {bare_jid} is already encrypted with {name}. " - "Nothing to do.").format( - bare_jid=bare_jid, name=former_plugin.name)) + log.info( + _( + "Session with {bare_jid} is already encrypted with {name}. " + "Nothing to do." + ).format(bare_jid=bare_jid, name=former_plugin.name) + ) return if replace: @@ -229,9 +246,10 @@ del self._sessions[bare_jid] await self._stop_encryption(former_plugin, entity) else: - msg = (_("Session with {bare_jid} is already encrypted with {name}. " - "Please stop encryption session before changing algorithm.") - .format(bare_jid=bare_jid, name=plugin.name)) + msg = _( + "Session with {bare_jid} is already encrypted with {name}. " + "Please stop encryption session before changing algorithm." + ).format(bare_jid=bare_jid, name=plugin.name) log.warning(msg) raise exceptions.ConflictError(msg) @@ -241,34 +259,44 @@ entity.resource = self.host.memory.main_resource_get(self.client, entity) if not entity.resource: raise exceptions.NotFound( - _("No resource found for {destinee}, can't encrypt with {name}") - .format(destinee=entity.full(), name=plugin.name)) - log.info(_("No resource specified to encrypt with {name}, using " - "{destinee}.").format(destinee=entity.full(), - name=plugin.name)) + _( + "No resource found for {destinee}, can't encrypt with {name}" + ).format(destinee=entity.full(), name=plugin.name) + ) + log.info( + _( + "No resource specified to encrypt with {name}, using " + "{destinee}." + ).format(destinee=entity.full(), name=plugin.name) + ) # indicate that we encrypt only for some devices - directed_devices = data['directed_devices'] = [entity.resource] + directed_devices = data["directed_devices"] = [entity.resource] elif entity.resource: raise ValueError(_("{name} encryption must be used with bare jids.")) await self._start_encryption(plugin, entity) self._sessions[entity.userhostJID()] = data - log.info(_("Encryption session has been set for {entity_jid} with " - "{encryption_name}").format( - entity_jid=entity.full(), encryption_name=plugin.name)) + log.info( + _( + "Encryption session has been set for {entity_jid} with " + "{encryption_name}" + ).format(entity_jid=entity.full(), encryption_name=plugin.name) + ) self.host.bridge.message_encryption_started( - entity.full(), - self.get_bridge_data(data), - self.client.profile) - msg = D_("Encryption session started: your messages with {destinee} are " - "now end to end encrypted using {name} algorithm.").format( - destinee=entity.full(), name=plugin.name) - directed_devices = data.get('directed_devices') + entity.full(), self.get_bridge_data(data), self.client.profile + ) + msg = D_( + "Encryption session started: your messages with {destinee} are " + "now end to end encrypted using {name} algorithm." + ).format(destinee=entity.full(), name=plugin.name) + directed_devices = data.get("directed_devices") if directed_devices: - msg += "\n" + D_("Message are encrypted only for {nb_devices} device(s): " - "{devices_list}.").format( - nb_devices=len(directed_devices), - devices_list = ', '.join(directed_devices)) + msg += "\n" + D_( + "Message are encrypted only for {nb_devices} device(s): " + "{devices_list}." + ).format( + nb_devices=len(directed_devices), devices_list=", ".join(directed_devices) + ) self.client.feedback(bare_jid, msg) @@ -283,29 +311,38 @@ session = self.getSession(entity.userhostJID()) if not session: raise failure.Failure( - exceptions.NotFound(_("There is no encryption session with this " - "entity."))) - plugin = session['plugin'] + exceptions.NotFound( + _("There is no encryption session with this " "entity.") + ) + ) + plugin = session["plugin"] if namespace is not None and plugin.namespace != namespace: - raise exceptions.InternalError(_( - "The encryption session is not run with the expected plugin: encrypted " - "with {current_name} and was expecting {expected_name}").format( - current_name=session['plugin'].namespace, - expected_name=namespace)) + raise exceptions.InternalError( + _( + "The encryption session is not run with the expected plugin: encrypted " + "with {current_name} and was expecting {expected_name}" + ).format( + current_name=session["plugin"].namespace, expected_name=namespace + ) + ) if entity.resource: try: - directed_devices = session['directed_devices'] + directed_devices = session["directed_devices"] except KeyError: - raise exceptions.NotFound(_( - "There is a session for the whole entity (i.e. all devices of the " - "entity), not a directed one. Please use bare jid if you want to " - "stop the whole encryption with this entity.")) + raise exceptions.NotFound( + _( + "There is a session for the whole entity (i.e. all devices of the " + "entity), not a directed one. Please use bare jid if you want to " + "stop the whole encryption with this entity." + ) + ) try: directed_devices.remove(entity.resource) except ValueError: - raise exceptions.NotFound(_("There is no directed session with this " - "entity.")) + raise exceptions.NotFound( + _("There is no directed session with this " "entity.") + ) else: if not directed_devices: # if we have no more directed device sessions, @@ -319,18 +356,24 @@ del self._sessions[entity.userhostJID()] await self._stop_encryption(plugin, entity) - log.info(_("encryption session stopped with entity {entity}").format( - entity=entity.full())) + log.info( + _("encryption session stopped with entity {entity}").format( + entity=entity.full() + ) + ) self.host.bridge.message_encryption_stopped( entity.full(), - {'name': plugin.name, - 'namespace': plugin.namespace, + { + "name": plugin.name, + "namespace": plugin.namespace, }, - self.client.profile) - msg = D_("Encryption session finished: your messages with {destinee} are " - "NOT end to end encrypted anymore.\nYour server administrators or " - "{destinee} server administrators will be able to read them.").format( - destinee=entity.full()) + self.client.profile, + ) + msg = D_( + "Encryption session finished: your messages with {destinee} are " + "NOT end to end encrypted anymore.\nYour server administrators or " + "{destinee} server administrators will be able to read them." + ).format(destinee=entity.full()) self.client.feedback(entity, msg) @@ -375,16 +418,19 @@ session = self.getSession(entity_jid) if not session: raise exceptions.NotFound( - "No encryption session currently active for {entity_jid}" - .format(entity_jid=entity_jid.full())) - plugin = session['plugin'] + "No encryption session currently active for {entity_jid}".format( + entity_jid=entity_jid.full() + ) + ) + plugin = session["plugin"] else: plugin = self.get_plugin(namespace) try: get_trust_ui = plugin.instance.get_trust_ui except AttributeError: raise NotImplementedError( - "Encryption plugin doesn't handle trust management UI") + "Encryption plugin doesn't handle trust management UI" + ) else: return utils.as_deferred(get_trust_ui, self.client, entity_jid) @@ -393,32 +439,32 @@ @classmethod def _import_menus(cls, host): host.import_menu( - (D_("Encryption"), D_("unencrypted (plain text)")), - partial(cls._on_menu_unencrypted, host=host), - security_limit=0, - help_string=D_("End encrypted session"), - type_=C.MENU_SINGLE, + (D_("Encryption"), D_("unencrypted (plain text)")), + partial(cls._on_menu_unencrypted, host=host), + security_limit=0, + help_string=D_("End encrypted session"), + type_=C.MENU_SINGLE, ) for plg in cls.getPlugins(): host.import_menu( - (D_("Encryption"), plg.name), - partial(cls._on_menu_name, host=host, plg=plg), - security_limit=0, - help_string=D_("Start {name} session").format(name=plg.name), - type_=C.MENU_SINGLE, + (D_("Encryption"), plg.name), + partial(cls._on_menu_name, host=host, plg=plg), + security_limit=0, + help_string=D_("Start {name} session").format(name=plg.name), + type_=C.MENU_SINGLE, ) host.import_menu( - (D_("Encryption"), D_("⛨ {name} trust").format(name=plg.name)), - partial(cls._on_menu_trust, host=host, plg=plg), - security_limit=0, - help_string=D_("Manage {name} trust").format(name=plg.name), - type_=C.MENU_SINGLE, + (D_("Encryption"), D_("⛨ {name} trust").format(name=plg.name)), + partial(cls._on_menu_trust, host=host, plg=plg), + security_limit=0, + help_string=D_("Manage {name} trust").format(name=plg.name), + type_=C.MENU_SINGLE, ) @classmethod def _on_menu_unencrypted(cls, data, host, profile): client = host.get_client(profile) - peer_jid = jid.JID(data['jid']).userhostJID() + peer_jid = jid.JID(data["jid"]).userhostJID() d = defer.ensureDeferred(client.encryption.stop(peer_jid)) d.addCallback(lambda __: {}) return d @@ -426,11 +472,12 @@ @classmethod def _on_menu_name(cls, data, host, plg, profile): client = host.get_client(profile) - peer_jid = jid.JID(data['jid']) + peer_jid = jid.JID(data["jid"]) if not plg.directed: peer_jid = peer_jid.userhostJID() d = defer.ensureDeferred( - client.encryption.start(peer_jid, plg.namespace, replace=True)) + client.encryption.start(peer_jid, plg.namespace, replace=True) + ) d.addCallback(lambda __: {}) return d @@ -438,22 +485,23 @@ @defer.inlineCallbacks def _on_menu_trust(cls, data, host, plg, profile): client = host.get_client(profile) - peer_jid = jid.JID(data['jid']).userhostJID() + peer_jid = jid.JID(data["jid"]).userhostJID() ui = yield client.encryption.get_trust_ui(peer_jid, plg.namespace) - defer.returnValue({'xmlui': ui.toXml()}) + defer.returnValue({"xmlui": ui.toXml()}) ## Triggers ## def set_encryption_flag(self, mess_data): """Set "encryption" key in mess_data if session with destinee is encrypted""" - to_jid = mess_data['to'] + to_jid = mess_data["to"] encryption = self._sessions.get(to_jid.userhostJID()) if encryption is not None: - plugin = encryption['plugin'] + plugin = encryption["plugin"] if mess_data["type"] == "groupchat" and plugin.directed: raise exceptions.InternalError( - f"encryption flag must not be set for groupchat if encryption algorithm " - f"({encryption['plugin'].name}) is directed!") + f"encryption flag must not be set for groupchat if encryption algorithm " + f"({encryption['plugin'].name}) is directed!" + ) mess_data[C.MESS_KEY_ENCRYPTION] = encryption self.mark_as_encrypted(mess_data, plugin.namespace) @@ -467,26 +515,25 @@ @param mess_data(dict): message data as used in post treat workflow @param namespace(str): namespace of the algorithm used for encrypting the message """ - mess_data['extra'][C.MESS_KEY_ENCRYPTED] = True - from_bare_jid = mess_data['from'].userhostJID() + mess_data["extra"][C.MESS_KEY_ENCRYPTED] = True + from_bare_jid = mess_data["from"].userhostJID() if from_bare_jid != self.client.jid.userhostJID(): session = self.getSession(from_bare_jid) if session is None: # if we are currently unencrypted, we start a session automatically # to avoid sending unencrypted messages in an encrypted context - log.info(_( - "Starting e2e session with {peer_jid} as we receive encrypted " - "messages") - .format(peer_jid=from_bare_jid) + log.info( + _( + "Starting e2e session with {peer_jid} as we receive encrypted " + "messages" + ).format(peer_jid=from_bare_jid) ) defer.ensureDeferred(self.start(from_bare_jid, namespace)) return mess_data def is_encryption_requested( - self, - mess_data: MessageData, - namespace: Optional[str] = None + self, mess_data: MessageData, namespace: Optional[str] = None ) -> bool: """Helper method to check if encryption is requested in an outgoind message @@ -499,7 +546,7 @@ if encryption is None: return False # we get plugin even if namespace is None to be sure that the key exists - plugin = encryption['plugin'] + plugin = encryption["plugin"] if namespace is None: return True return plugin.namespace == namespace @@ -510,8 +557,7 @@ @param mess_data(dict): message data @return (bool): True if the encrypted flag is present """ - return mess_data['extra'].get(C.MESS_KEY_ENCRYPTED, False) - + return mess_data["extra"].get(C.MESS_KEY_ENCRYPTED, False) def mark_as_trusted(self, mess_data): """Helper methor to mark a message as sent from a trusted entity. @@ -530,5 +576,5 @@ the plugin @param mess_data(dict): message data as used in post treat workflow """ - mess_data['trusted'] = False + mess_data["trusted"] = False return mess_data diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/memory/memory.py --- a/libervia/backend/memory/memory.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/memory/memory.py Wed Jun 19 18:44:57 2024 +0200 @@ -155,7 +155,7 @@ raise NotImplementedError("You need do use new_session to create a session") def __delitem__(self, session_id): - """ delete the session data """ + """delete the session data""" self._purge_session(session_id) def keys(self): @@ -253,7 +253,6 @@ self.admin_jids = set() self._file_path_lock = defer.DeferredLock() - async def initialise(self): self.storage = Storage() await self.storage.initialise() @@ -281,15 +280,11 @@ else: self.admin_jids.add(admin_jid) - ## Configuration ## def config_get( - self, - section: str|None, - name: str, - default: Any = None - ) -> str|list|dict: + self, section: str | None, name: str, default: Any = None + ) -> str | list | dict: """Get the main configuration option @param section: section of the config file (None or '' for DEFAULT) @@ -348,7 +343,7 @@ ## Profiles/Sessions management ## def start_session(self, password, profile): - """"Iniatialise session for a profile + """ "Iniatialise session for a profile @param password(unicode): profile session password or empty string is no password is set @@ -375,8 +370,8 @@ session_d = self._entities_cache[profile] except KeyError: # else we do request the params - session_d = self._entities_cache[profile] = self.load_individual_params( - profile + session_d = self._entities_cache[profile] = ( + self.load_individual_params(profile) ) session_d.addCallback(create_session) finally: @@ -434,8 +429,9 @@ ) valid = PasswordHasher.verify(password, sat_cipher) if not valid: - log.warning(_("Authentication failure of profile {profile}").format( - profile=profile)) + log.warning( + _("Authentication failure of profile {profile}").format(profile=profile) + ) raise exceptions.PasswordError("The provided profile password doesn't match.") return await self.new_auth_session(password, profile) @@ -467,9 +463,7 @@ del self._entities_cache[profile] except KeyError: log.error( - _( - "Trying to purge roster status cache for a profile not in memory: [%s]" - ) + _("Trying to purge roster status cache for a profile not in memory: [%s]") % profile ) @@ -554,9 +548,7 @@ # be sure to call this after checking that the profile doesn't exist yet # generated once for all and saved in a PersistentDict - personal_key = BlockCipher.get_random_key( - base64=True - ).decode('utf-8') + personal_key = BlockCipher.get_random_key(base64=True).decode("utf-8") self.auth_sessions.new_session( {C.MEMORY_CRYPTO_KEY: personal_key}, profile=name ) # will be encrypted by param_set @@ -626,29 +618,42 @@ def _history_get_serialise(self, history_data): return [ - (uid, timestamp, from_jid, to_jid, message, subject, mess_type, - data_format.serialise(extra)) for uid, timestamp, from_jid, to_jid, message, - subject, mess_type, extra in history_data + ( + uid, + timestamp, + from_jid, + to_jid, + message, + subject, + mess_type, + data_format.serialise(extra), + ) + for uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra in history_data ] - def _history_get(self, from_jid_s, to_jid_s, limit=C.HISTORY_LIMIT_NONE, between=True, - filters=None, profile=C.PROF_KEY_NONE): + def _history_get( + self, + from_jid_s, + to_jid_s, + limit=C.HISTORY_LIMIT_NONE, + between=True, + filters=None, + profile=C.PROF_KEY_NONE, + ): from_jid = jid.JID(from_jid_s) if from_jid_s else None to_jid = jid.JID(to_jid_s) if to_jid_s else None - d = self.history_get( - from_jid, to_jid, limit, between, filters, profile - ) + d = self.history_get(from_jid, to_jid, limit, between, filters, profile) d.addCallback(self._history_get_serialise) return d def history_get( self, - from_jid: jid.JID|None, - to_jid: jid.JID|None, + from_jid: jid.JID | None, + to_jid: jid.JID | None, limit: int = C.HISTORY_LIMIT_NONE, between: bool = True, - filters: dict[str, str]|None = None, - profile: str = C.PROF_KEY_NONE + filters: dict[str, str] | None = None, + profile: str = C.PROF_KEY_NONE, ) -> defer.Deferred[list]: """Retrieve messages in history @@ -671,7 +676,9 @@ limit = None if limit == 0: return defer.succeed([]) - return self.storage.history_get(from_jid, to_jid, limit, between, filters, profile) + return self.storage.history_get( + from_jid, to_jid, limit, between, filters, profile + ) ## Statuses ## @@ -714,9 +721,7 @@ """ client = self.host.get_client(profile_key) presence_data = PresenceTuple(show, priority, statuses) - self.update_entity_data( - client, entity_jid, "presence", presence_data - ) + self.update_entity_data(client, entity_jid, "presence", presence_data) if entity_jid.resource and show != C.PRESENCE_UNAVAILABLE: # If a resource is available, bare jid should not have presence information try: @@ -743,7 +748,9 @@ # FIXME: is there a need to keep cache data for resources which are not connected anymore? if entity_jid.resource: raise ValueError( - "get_all_resources must be used with a bare jid (got {})".format(entity_jid) + "get_all_resources must be used with a bare jid (got {})".format( + entity_jid + ) ) profile_cache = self._get_profile_cache(client) try: @@ -791,7 +798,9 @@ """ if entity_jid.resource: raise ValueError( - "main_resource_get must be used with a bare jid (got {})".format(entity_jid) + "main_resource_get must be used with a bare jid (got {})".format( + entity_jid + ) ) try: if self.host.plugins["XEP-0045"].is_joined_room(client, entity_jid): @@ -857,9 +866,7 @@ full_jid.resource = resource yield full_jid - def update_entity_data( - self, client, entity_jid, key, value, silent=False - ): + def update_entity_data(self, client, entity_jid, key, value, silent=False): """Set a misc data for an entity If key was registered with set_signal_on_update, a signal will be sent to frontends @@ -884,10 +891,7 @@ entity_data[key] = value if key in self._key_signals and not silent: self.host.bridge.entity_data_updated( - jid_.full(), - key, - data_format.serialise(value), - client.profile + jid_.full(), key, data_format.serialise(value), client.profile ) def del_entity_datum(self, client, entity_jid, key): @@ -910,9 +914,7 @@ try: entity_data = profile_cache[jid_.userhostJID()][jid_.resource] except KeyError: - raise exceptions.UnknownEntityError( - "Entity {} not in cache".format(jid_) - ) + raise exceptions.UnknownEntityError("Entity {} not in cache".format(jid_)) try: del entity_data[key] except KeyError as e: @@ -927,7 +929,7 @@ client, [jid.JID(jid_) for jid_ in entities_jids], keys_list ) return { - jid_.full(): {k: data_format.serialise(v) for k,v in data.items()} + jid_.full(): {k: data_format.serialise(v) for k, v in data.items()} for jid_, data in ret.items() } @@ -980,7 +982,8 @@ def _get_entity_data(self, entity_jid_s, keys_list=None, profile=C.PROF_KEY_NONE): return self.entity_data_get( - self.host.get_client(profile), jid.JID(entity_jid_s), keys_list) + self.host.get_client(profile), jid.JID(entity_jid_s), keys_list + ) def entity_data_get(self, client, entity_jid, keys_list=None): """Get a list of cached values for entity @@ -999,9 +1002,7 @@ entity_data = profile_cache[entity_jid.userhostJID()][entity_jid.resource] except KeyError: raise exceptions.UnknownEntityError( - "Entity {} not in cache (was requesting {})".format( - entity_jid, keys_list - ) + "Entity {} not in cache (was requesting {})".format(entity_jid, keys_list) ) if keys_list is None: return entity_data @@ -1146,7 +1147,9 @@ ## Parameters ## - def get_string_param_a(self, name, category, attr="value", profile_key=C.PROF_KEY_NONE): + def get_string_param_a( + self, name, category, attr="value", profile_key=C.PROF_KEY_NONE + ): return self.params.get_string_param_a(name, category, attr, profile_key) def param_get_a(self, name, category, attr="value", profile_key=C.PROF_KEY_NONE): @@ -1172,13 +1175,20 @@ ) def async_get_string_param_a( - self, name, category, attribute="value", security_limit=C.NO_SECURITY_LIMIT, - profile_key=C.PROF_KEY_NONE): + self, + name, + category, + attribute="value", + security_limit=C.NO_SECURITY_LIMIT, + profile_key=C.PROF_KEY_NONE, + ): profile = self.get_profile_name(profile_key) - return defer.ensureDeferred(self.params.async_get_string_param_a( - name, category, attribute, security_limit, profile - )) + return defer.ensureDeferred( + self.params.async_get_string_param_a( + name, category, attribute, security_limit, profile + ) + ) def _get_params_ui(self, security_limit, app, extra_s, profile_key): return self.params._get_params_ui(security_limit, app, extra_s, profile_key) @@ -1211,31 +1221,38 @@ client = self.host.get_client(profile_key) # we accept any type data = data_format.deserialise(data_s, type_check=None) - return defer.ensureDeferred(self.storage.set_private_value( - namespace, key, data, binary=True, profile=client.profile)) + return defer.ensureDeferred( + self.storage.set_private_value( + namespace, key, data, binary=True, profile=client.profile + ) + ) def _private_data_get(self, namespace, key, profile_key): client = self.host.get_client(profile_key) d = defer.ensureDeferred( self.storage.get_privates( - namespace, [key], binary=True, profile=client.profile) + namespace, [key], binary=True, profile=client.profile + ) ) d.addCallback(lambda data_dict: data_format.serialise(data_dict.get(key))) return d def _private_data_delete(self, namespace, key, profile_key): client = self.host.get_client(profile_key) - return defer.ensureDeferred(self.storage.del_private_value( - namespace, key, binary=True, profile=client.profile)) + return defer.ensureDeferred( + self.storage.del_private_value( + namespace, key, binary=True, profile=client.profile + ) + ) ## Files ## def check_file_permission( - self, - file_data: dict, - peer_jid: Optional[jid.JID], - perms_to_check: Optional[Tuple[str]], - set_affiliation: bool = False + self, + file_data: dict, + peer_jid: Optional[jid.JID], + perms_to_check: Optional[Tuple[str]], + set_affiliation: bool = False, ) -> None: """Check that an entity has the right permission on a file @@ -1256,7 +1273,7 @@ peer_jid = peer_jid.userhostJID() if peer_jid == file_data["owner"]: if set_affiliation: - file_data['affiliation'] = 'owner' + file_data["affiliation"] = "owner" # the owner has all rights, nothing to check return if not C.ACCESS_PERMS.issuperset(perms_to_check): @@ -1274,7 +1291,7 @@ # otherwise, we use public permission, as the parent directory will # block anyway, this avoid to have to recursively change permissions for # all sub directories/files when modifying a permission - if not file_data.get('parent'): + if not file_data.get("parent"): raise exceptions.PermissionError() else: perm_type = C.ACCESS_TYPE_PUBLIC @@ -1358,44 +1375,41 @@ def get_file_affiliations(self, file_data: dict) -> Dict[jid.JID, str]: """Convert file access to pubsub like affiliations""" affiliations = {} - access_data = file_data['access'] + access_data = file_data["access"] read_data = access_data.get(C.ACCESS_PERM_READ, {}) - if read_data.get('type') == C.ACCESS_TYPE_WHITELIST: - for entity_jid_s in read_data['jids']: + if read_data.get("type") == C.ACCESS_TYPE_WHITELIST: + for entity_jid_s in read_data["jids"]: entity_jid = jid.JID(entity_jid_s) - affiliations[entity_jid] = 'member' + affiliations[entity_jid] = "member" write_data = access_data.get(C.ACCESS_PERM_WRITE, {}) - if write_data.get('type') == C.ACCESS_TYPE_WHITELIST: - for entity_jid_s in write_data['jids']: + if write_data.get("type") == C.ACCESS_TYPE_WHITELIST: + for entity_jid_s in write_data["jids"]: entity_jid = jid.JID(entity_jid_s) - affiliations[entity_jid] = 'publisher' + affiliations[entity_jid] = "publisher" - owner = file_data.get('owner') + owner = file_data.get("owner") if owner: - affiliations[owner] = 'owner' + affiliations[owner] = "owner" return affiliations def _set_file_affiliations_update( - self, - access: dict, - file_data: dict, - affiliations: Dict[jid.JID, str] + self, access: dict, file_data: dict, affiliations: Dict[jid.JID, str] ) -> None: read_data = access.setdefault(C.ACCESS_PERM_READ, {}) - if read_data.get('type') != C.ACCESS_TYPE_WHITELIST: - read_data['type'] = C.ACCESS_TYPE_WHITELIST - if 'jids' not in read_data: - read_data['jids'] = [] - read_whitelist = read_data['jids'] + if read_data.get("type") != C.ACCESS_TYPE_WHITELIST: + read_data["type"] = C.ACCESS_TYPE_WHITELIST + if "jids" not in read_data: + read_data["jids"] = [] + read_whitelist = read_data["jids"] write_data = access.setdefault(C.ACCESS_PERM_WRITE, {}) - if write_data.get('type') != C.ACCESS_TYPE_WHITELIST: - write_data['type'] = C.ACCESS_TYPE_WHITELIST - if 'jids' not in write_data: - write_data['jids'] = [] - write_whitelist = write_data['jids'] + if write_data.get("type") != C.ACCESS_TYPE_WHITELIST: + write_data["type"] = C.ACCESS_TYPE_WHITELIST + if "jids" not in write_data: + write_data["jids"] = [] + write_whitelist = write_data["jids"] for entity_jid, affiliation in affiliations.items(): entity_jid_s = entity_jid.full() if affiliation == "none": @@ -1428,10 +1442,7 @@ raise ValueError(f"unknown affiliation: {affiliation!r}") async def set_file_affiliations( - self, - client, - file_data: dict, - affiliations: Dict[jid.JID, str] + self, client, file_data: dict, affiliations: Dict[jid.JID, str] ) -> None: """Apply pubsub like affiliation to file_data @@ -1442,22 +1453,19 @@ - "member" gives read permission only - "none" removes both read and write permissions """ - file_id = file_data['id'] + file_id = file_data["id"] await self.file_update( file_id, - 'access', + "access", update_cb=partial( self._set_file_affiliations_update, file_data=file_data, - affiliations=affiliations + affiliations=affiliations, ), ) def _set_file_access_model_update( - self, - access: dict, - file_data: dict, - access_model: str + self, access: dict, file_data: dict, access_model: str ) -> None: read_data = access.setdefault(C.ACCESS_PERM_READ, {}) if access_model == "open": @@ -1467,9 +1475,9 @@ else: raise ValueError(f"unknown access model: {access_model}") - read_data['type'] = requested_type - if requested_type == C.ACCESS_TYPE_WHITELIST and 'jids' not in read_data: - read_data['jids'] = [] + read_data["type"] = requested_type + if requested_type == C.ACCESS_TYPE_WHITELIST and "jids" not in read_data: + read_data["jids"] = [] async def set_file_access_model( self, @@ -1483,24 +1491,24 @@ - "open": set public access to file/dir - "whitelist": set whitelist to file/dir """ - file_id = file_data['id'] + file_id = file_data["id"] await self.file_update( file_id, - 'access', + "access", update_cb=partial( self._set_file_access_model_update, file_data=file_data, - access_model=access_model + access_model=access_model, ), ) def get_files_owner( - self, - client, - owner: Optional[jid.JID], - peer_jid: Optional[jid.JID], - file_id: Optional[str] = None, - parent: Optional[str] = None + self, + client, + owner: Optional[jid.JID], + peer_jid: Optional[jid.JID], + file_id: Optional[str] = None, + parent: Optional[str] = None, ) -> jid.JID: """Get owner to use for a file operation @@ -1526,10 +1534,26 @@ return peer_jid.userhostJID() async def get_files( - 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, public_id=None, owner=None, access=None, projection=None, - unique=False, perms_to_check=(C.ACCESS_PERM_READ,)): + 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, + public_id=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 @@ -1628,11 +1652,27 @@ return files async def set_file( - self, client, name, file_id=None, version="", parent=None, path=None, - type_=C.FILE_TYPE_FILE, file_hash=None, hash_algo=None, size=None, - namespace=None, mime_type=None, public_id=None, created=None, modified=None, - owner=None, access=None, extra=None, peer_jid=None, - perms_to_check=(C.ACCESS_PERM_WRITE,) + self, + client, + name, + file_id=None, + version="", + parent=None, + path=None, + type_=C.FILE_TYPE_FILE, + file_hash=None, + hash_algo=None, + size=None, + namespace=None, + mime_type=None, + public_id=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 @@ -1694,7 +1734,7 @@ else: mime_type = mime_type.lower() if public_id is not None: - assert len(public_id)>0 + assert len(public_id) > 0 if created is None: created = time.time() if namespace is not None: @@ -1761,10 +1801,7 @@ ) async def file_get_used_space( - self, - client, - peer_jid: jid.JID, - owner: Optional[jid.JID] = None + self, client, peer_jid: jid.JID, owner: Optional[jid.JID] = None ) -> int: """Get space taken by all files owned by an entity @@ -1797,7 +1834,7 @@ peer_jid: jid.JID, recursive: bool, files_path: Path, - file_data: dict + file_data: dict, ): """Internal method to delete files/directories recursively @@ -1807,43 +1844,56 @@ @param files_path(unicode): path of the directory containing the actual files @param file_data(dict): data of the file to delete """ - if file_data['owner'] != peer_jid: + if file_data["owner"] != peer_jid: raise exceptions.PermissionError( - "file {file_name} can't be deleted, {peer_jid} is not the owner" - .format(file_name=file_data['name'], peer_jid=peer_jid.full())) - if file_data['type'] == C.FILE_TYPE_DIRECTORY: - sub_files = yield self.get_files(client, peer_jid, parent=file_data['id']) + "file {file_name} can't be deleted, {peer_jid} is not the owner".format( + file_name=file_data["name"], peer_jid=peer_jid.full() + ) + ) + if file_data["type"] == C.FILE_TYPE_DIRECTORY: + sub_files = yield self.get_files(client, peer_jid, parent=file_data["id"]) if sub_files and not recursive: raise exceptions.DataError(_("Can't delete directory, it is not empty")) # we first delete the sub-files for sub_file_data in sub_files: - if sub_file_data['type'] == C.FILE_TYPE_DIRECTORY: - sub_file_path = files_path / sub_file_data['name'] + if sub_file_data["type"] == C.FILE_TYPE_DIRECTORY: + sub_file_path = files_path / sub_file_data["name"] else: sub_file_path = files_path yield self._delete_file( - client, peer_jid, recursive, sub_file_path, sub_file_data) + client, peer_jid, recursive, sub_file_path, sub_file_data + ) # then the directory itself - yield self.storage.file_delete(file_data['id']) - elif file_data['type'] == C.FILE_TYPE_FILE: - log.info(_("deleting file {name} with hash {file_hash}").format( - name=file_data['name'], file_hash=file_data['file_hash'])) - yield self.storage.file_delete(file_data['id']) + yield self.storage.file_delete(file_data["id"]) + elif file_data["type"] == C.FILE_TYPE_FILE: + log.info( + _("deleting file {name} with hash {file_hash}").format( + name=file_data["name"], file_hash=file_data["file_hash"] + ) + ) + yield self.storage.file_delete(file_data["id"]) references = yield self.get_files( - client, peer_jid, file_hash=file_data['file_hash']) + client, peer_jid, file_hash=file_data["file_hash"] + ) if references: log.debug("there are still references to the file, we keep it") else: - file_path = os.path.join(files_path, file_data['file_hash']) - log.info(_("no reference left to {file_path}, deleting").format( - file_path=file_path)) + file_path = os.path.join(files_path, file_data["file_hash"]) + log.info( + _("no reference left to {file_path}, deleting").format( + file_path=file_path + ) + ) try: os.unlink(file_path) except FileNotFoundError: - log.error(f"file at {file_path!r} doesn't exist but it was referenced in files database") + log.error( + f"file at {file_path!r} doesn't exist but it was referenced in files database" + ) else: - raise exceptions.InternalError('Unexpected file type: {file_type}' - .format(file_type=file_data['type'])) + raise exceptions.InternalError( + "Unexpected file type: {file_type}".format(file_type=file_data["type"]) + ) async def file_delete(self, client, peer_jid, file_id, recursive=False): """Delete a single file or a directory and all its sub-files @@ -1857,8 +1907,9 @@ # should be checked too files_data = await self.get_files(client, peer_jid, file_id) if not files_data: - raise exceptions.NotFound("Can't find the file with id {file_id}".format( - file_id=file_id)) + raise exceptions.NotFound( + "Can't find the file with id {file_id}".format(file_id=file_id) + ) file_data = files_data[0] if file_data["type"] != C.FILE_TYPE_DIRECTORY and recursive: raise ValueError("recursive can only be set for directories") @@ -1879,12 +1930,11 @@ return Path( self._cache_path, regex.path_escape(namespace), - *(regex.path_escape(a) for a in args) + *(regex.path_escape(a) for a in args), ) ## Notifications ## - def _add_notification( self, type_: str, @@ -1896,7 +1946,7 @@ priority: str, expire_at: float, extra_s: str, - profile_key: str + profile_key: str, ) -> defer.Deferred: client = self.host.get_client(profile_key) @@ -1907,9 +1957,7 @@ notification_type = NotificationType[type_] notification_priority = NotificationPriority[priority] except KeyError as e: - raise exceptions.DataError( - f"invalid notification type or priority data: {e}" - ) + raise exceptions.DataError(f"invalid notification type or priority data: {e}") return defer.ensureDeferred( self.add_notification( @@ -1922,7 +1970,7 @@ requires_action, notification_priority, expire_at or None, - data_format.deserialise(extra_s) + data_format.deserialise(extra_s), ) ) @@ -1955,21 +2003,28 @@ @param extra: additional data. """ notification = await self.storage.add_notification( - None if is_global else client, type_, body_plain, body_rich, title, - requires_action, priority, expire_at, extra + None if is_global else client, + type_, + body_plain, + body_rich, + title, + requires_action, + priority, + expire_at, + extra, ) self.host.bridge.notification_new( str(notification.id), notification.timestamp, type_.value, body_plain, - body_rich or '', - title or '', + body_rich or "", + title or "", requires_action, priority.value, expire_at or 0, - data_format.serialise(extra) if extra else '', - C.PROF_KEY_ALL if is_global else client.profile + data_format.serialise(extra) if extra else "", + C.PROF_KEY_ALL if is_global else client.profile, ) def _get_notifications(self, filters_s: str, profile_key: str) -> defer.Deferred: @@ -1992,12 +2047,14 @@ filters = data_format.deserialise(filters_s) try: - if 'type' in filters: - filters['type_'] = NotificationType[filters.pop('type')] - if 'status' in filters: - filters['status'] = NotificationStatus[filters['status']] - if 'min_priority' in filters: - filters['min_priority'] = NotificationPriority[filters['min_priority']].value + if "type" in filters: + filters["type_"] = NotificationType[filters.pop("type")] + if "status" in filters: + filters["status"] = NotificationStatus[filters["status"]] + if "min_priority" in filters: + filters["min_priority"] = NotificationPriority[ + filters["min_priority"] + ].value except KeyError as e: raise exceptions.DataError(f"invalid filter data: {e}") @@ -2010,10 +2067,7 @@ return d def _delete_notification( - self, - id_: str, - is_global: bool, - profile_key: str + self, id_: str, is_global: bool, profile_key: str ) -> defer.Deferred: client = self.host.get_client(profile_key) if is_global and not client.is_admin: @@ -2023,10 +2077,7 @@ return defer.ensureDeferred(self.delete_notification(client, id_, is_global)) async def delete_notification( - self, - client: SatXMPPEntity, - id_: str, - is_global: bool=False + self, client: SatXMPPEntity, id_: str, is_global: bool = False ) -> None: """Delete a notification @@ -2036,8 +2087,7 @@ """ await self.storage.delete_notification(None if is_global else client, id_) self.host.bridge.notification_deleted( - id_, - C.PROF_KEY_ALL if is_global else client.profile + id_, C.PROF_KEY_ALL if is_global else client.profile ) def _notifications_expired_clean( @@ -2050,12 +2100,10 @@ return defer.ensureDeferred( self.storage.clean_expired_notifications( - client, - None if limit_timestamp == -1.0 else limit_timestamp + client, None if limit_timestamp == -1.0 else limit_timestamp ) ) - ## Misc ## def is_entity_available(self, client, entity_jid): diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/memory/migration/env.py --- a/libervia/backend/memory/migration/env.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/memory/migration/env.py Wed Jun 19 18:44:57 2024 +0200 @@ -62,7 +62,7 @@ connection=connection, target_metadata=target_metadata, render_as_batch=True, - include_name=include_name + include_name=include_name, ) with context.begin_transaction(): diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/memory/migration/versions/129ac51807e4_create_virtual_table_for_full_text_.py --- a/libervia/backend/memory/migration/versions/129ac51807e4_create_virtual_table_for_full_text_.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/memory/migration/versions/129ac51807e4_create_virtual_table_for_full_text_.py Wed Jun 19 18:44:57 2024 +0200 @@ -5,13 +5,14 @@ Create Date: 2021-08-13 19:13:54.112538 """ + from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = '129ac51807e4' -down_revision = '8974efc51d22' +revision = "129ac51807e4" +down_revision = "8974efc51d22" branch_labels = None depends_on = None @@ -32,7 +33,7 @@ "('delete', old.id, old.data);" " INSERT INTO pubsub_items_fts(rowid, data) VALUES(new.id, new.data);" "END", - "INSERT INTO pubsub_items_fts(rowid, data) SELECT id, data from pubsub_items" + "INSERT INTO pubsub_items_fts(rowid, data) SELECT id, data from pubsub_items", ] for q in queries: op.execute(sa.DDL(q)) diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/memory/migration/versions/2ab01aa1f686_create_table_for_notifications.py --- a/libervia/backend/memory/migration/versions/2ab01aa1f686_create_table_for_notifications.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/memory/migration/versions/2ab01aa1f686_create_table_for_notifications.py Wed Jun 19 18:44:57 2024 +0200 @@ -5,42 +5,68 @@ Create Date: 2023-10-16 12:11:43.507295 """ + from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = '2ab01aa1f686' -down_revision = '4b002773cf92' +revision = "2ab01aa1f686" +down_revision = "4b002773cf92" branch_labels = None depends_on = None def upgrade(): - op.create_table('notifications', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('timestamp', sa.Float(), nullable=False), - sa.Column('expire_at', sa.Float(), nullable=True), - sa.Column('profile_id', sa.Integer(), nullable=True), - sa.Column('type', sa.Enum('chat', 'blog', 'calendar', 'file', 'call', 'service', 'other', name='notificationtype'), nullable=False), - sa.Column('title', sa.Text(), nullable=True), - sa.Column('body_plain', sa.Text(), nullable=False), - sa.Column('body_rich', sa.Text(), nullable=True), - sa.Column('requires_action', sa.Boolean(), nullable=True), - sa.Column('priority', sa.Integer(), nullable=True), - sa.Column('extra_data', sa.JSON(), nullable=True), - sa.Column('status', sa.Enum('new', 'read', name='notificationstatus'), nullable=True), - sa.ForeignKeyConstraint(['profile_id'], ['profiles.id'], name=op.f('fk_notifications_profile_id_profiles'), ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id', name=op.f('pk_notifications')) + op.create_table( + "notifications", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("timestamp", sa.Float(), nullable=False), + sa.Column("expire_at", sa.Float(), nullable=True), + sa.Column("profile_id", sa.Integer(), nullable=True), + sa.Column( + "type", + sa.Enum( + "chat", + "blog", + "calendar", + "file", + "call", + "service", + "other", + name="notificationtype", + ), + nullable=False, + ), + sa.Column("title", sa.Text(), nullable=True), + sa.Column("body_plain", sa.Text(), nullable=False), + sa.Column("body_rich", sa.Text(), nullable=True), + sa.Column("requires_action", sa.Boolean(), nullable=True), + sa.Column("priority", sa.Integer(), nullable=True), + sa.Column("extra_data", sa.JSON(), nullable=True), + sa.Column( + "status", sa.Enum("new", "read", name="notificationstatus"), nullable=True + ), + sa.ForeignKeyConstraint( + ["profile_id"], + ["profiles.id"], + name=op.f("fk_notifications_profile_id_profiles"), + ondelete="CASCADE", + ), + sa.PrimaryKeyConstraint("id", name=op.f("pk_notifications")), ) - with op.batch_alter_table('notifications', schema=None) as batch_op: - batch_op.create_index(batch_op.f('ix_notifications_profile_id'), ['profile_id'], unique=False) - batch_op.create_index('notifications_profile_id_status', ['profile_id', 'status'], unique=False) + with op.batch_alter_table("notifications", schema=None) as batch_op: + batch_op.create_index( + batch_op.f("ix_notifications_profile_id"), ["profile_id"], unique=False + ) + batch_op.create_index( + "notifications_profile_id_status", ["profile_id", "status"], unique=False + ) def downgrade(): - with op.batch_alter_table('notifications', schema=None) as batch_op: - batch_op.drop_index('notifications_profile_id_status') - batch_op.drop_index(batch_op.f('ix_notifications_profile_id')) + with op.batch_alter_table("notifications", schema=None) as batch_op: + batch_op.drop_index("notifications_profile_id_status") + batch_op.drop_index(batch_op.f("ix_notifications_profile_id")) - op.drop_table('notifications') + op.drop_table("notifications") diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/memory/migration/versions/4b002773cf92_add_origin_id_column_to_history_and_.py --- a/libervia/backend/memory/migration/versions/4b002773cf92_add_origin_id_column_to_history_and_.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/memory/migration/versions/4b002773cf92_add_origin_id_column_to_history_and_.py Wed Jun 19 18:44:57 2024 +0200 @@ -5,56 +5,43 @@ Create Date: 2022-06-13 16:10:39.711634 """ + from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = '4b002773cf92' -down_revision = '79e5f3313fa4' +revision = "4b002773cf92" +down_revision = "79e5f3313fa4" branch_labels = None depends_on = None def upgrade(): - with op.batch_alter_table('history', schema=None) as batch_op: - batch_op.add_column(sa.Column('origin_id', sa.Text(), nullable=True)) - batch_op.create_unique_constraint('uq_origin_id', ['profile_id', 'origin_id', 'source']) + with op.batch_alter_table("history", schema=None) as batch_op: + batch_op.add_column(sa.Column("origin_id", sa.Text(), nullable=True)) + batch_op.create_unique_constraint( + "uq_origin_id", ["profile_id", "origin_id", "source"] + ) - with op.batch_alter_table('message', schema=None) as batch_op: - batch_op.alter_column('history_uid', - existing_type=sa.TEXT(), - nullable=False) - batch_op.alter_column('message', - existing_type=sa.TEXT(), - nullable=False) + with op.batch_alter_table("message", schema=None) as batch_op: + batch_op.alter_column("history_uid", existing_type=sa.TEXT(), nullable=False) + batch_op.alter_column("message", existing_type=sa.TEXT(), nullable=False) - with op.batch_alter_table('subject', schema=None) as batch_op: - batch_op.alter_column('history_uid', - existing_type=sa.TEXT(), - nullable=False) - batch_op.alter_column('subject', - existing_type=sa.TEXT(), - nullable=False) + with op.batch_alter_table("subject", schema=None) as batch_op: + batch_op.alter_column("history_uid", existing_type=sa.TEXT(), nullable=False) + batch_op.alter_column("subject", existing_type=sa.TEXT(), nullable=False) def downgrade(): - with op.batch_alter_table('subject', schema=None) as batch_op: - batch_op.alter_column('subject', - existing_type=sa.TEXT(), - nullable=True) - batch_op.alter_column('history_uid', - existing_type=sa.TEXT(), - nullable=True) + with op.batch_alter_table("subject", schema=None) as batch_op: + batch_op.alter_column("subject", existing_type=sa.TEXT(), nullable=True) + batch_op.alter_column("history_uid", existing_type=sa.TEXT(), nullable=True) - with op.batch_alter_table('message', schema=None) as batch_op: - batch_op.alter_column('message', - existing_type=sa.TEXT(), - nullable=True) - batch_op.alter_column('history_uid', - existing_type=sa.TEXT(), - nullable=True) + with op.batch_alter_table("message", schema=None) as batch_op: + batch_op.alter_column("message", existing_type=sa.TEXT(), nullable=True) + batch_op.alter_column("history_uid", existing_type=sa.TEXT(), nullable=True) - with op.batch_alter_table('history', schema=None) as batch_op: - batch_op.drop_constraint('uq_origin_id', type_='unique') - batch_op.drop_column('origin_id') + with op.batch_alter_table("history", schema=None) as batch_op: + batch_op.drop_constraint("uq_origin_id", type_="unique") + batch_op.drop_column("origin_id") diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/memory/migration/versions/602caf848068_drop_message_types_table_fix_nullable.py --- a/libervia/backend/memory/migration/versions/602caf848068_drop_message_types_table_fix_nullable.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/memory/migration/versions/602caf848068_drop_message_types_table_fix_nullable.py Wed Jun 19 18:44:57 2024 +0200 @@ -5,6 +5,7 @@ Create Date: 2021-06-26 12:42:54.148313 """ + from alembic import op from sqlalchemy import ( Table, diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/memory/migration/versions/610345f77e75_add_version_id_to_history.py --- a/libervia/backend/memory/migration/versions/610345f77e75_add_version_id_to_history.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/memory/migration/versions/610345f77e75_add_version_id_to_history.py Wed Jun 19 18:44:57 2024 +0200 @@ -5,22 +5,27 @@ Create Date: 2023-11-20 17:33:53.544032 """ + from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = '610345f77e75' -down_revision = '2ab01aa1f686' +revision = "610345f77e75" +down_revision = "2ab01aa1f686" branch_labels = None depends_on = None def upgrade(): - with op.batch_alter_table('history', schema=None) as batch_op: - batch_op.add_column(sa.Column('version_id', sa.Integer(), server_default=sa.text('1'), nullable=False)) + with op.batch_alter_table("history", schema=None) as batch_op: + batch_op.add_column( + sa.Column( + "version_id", sa.Integer(), server_default=sa.text("1"), nullable=False + ) + ) def downgrade(): - with op.batch_alter_table('history', schema=None) as batch_op: - batch_op.drop_column('version_id') + with op.batch_alter_table("history", schema=None) as batch_op: + batch_op.drop_column("version_id") diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/memory/migration/versions/79e5f3313fa4_create_table_for_pubsub_subscriptions.py --- a/libervia/backend/memory/migration/versions/79e5f3313fa4_create_table_for_pubsub_subscriptions.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/memory/migration/versions/79e5f3313fa4_create_table_for_pubsub_subscriptions.py Wed Jun 19 18:44:57 2024 +0200 @@ -5,29 +5,36 @@ Create Date: 2022-03-14 17:15:00.689871 """ + from alembic import op import sqlalchemy as sa from libervia.backend.memory.sqla_mapping import JID # revision identifiers, used by Alembic. -revision = '79e5f3313fa4' -down_revision = '129ac51807e4' +revision = "79e5f3313fa4" +down_revision = "129ac51807e4" branch_labels = None depends_on = None def upgrade(): - op.create_table('pubsub_subs', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('node_id', sa.Integer(), nullable=False), - sa.Column('subscriber', JID(), nullable=True), - sa.Column('state', sa.Enum('SUBSCRIBED', 'PENDING', name='state'), nullable=True), - sa.ForeignKeyConstraint(['node_id'], ['pubsub_nodes.id'], name=op.f('fk_pubsub_subs_node_id_pubsub_nodes'), ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id', name=op.f('pk_pubsub_subs')), - sa.UniqueConstraint('node_id', 'subscriber', name=op.f('uq_pubsub_subs_node_id')) + op.create_table( + "pubsub_subs", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("node_id", sa.Integer(), nullable=False), + sa.Column("subscriber", JID(), nullable=True), + sa.Column("state", sa.Enum("SUBSCRIBED", "PENDING", name="state"), nullable=True), + sa.ForeignKeyConstraint( + ["node_id"], + ["pubsub_nodes.id"], + name=op.f("fk_pubsub_subs_node_id_pubsub_nodes"), + ondelete="CASCADE", + ), + sa.PrimaryKeyConstraint("id", name=op.f("pk_pubsub_subs")), + sa.UniqueConstraint("node_id", "subscriber", name=op.f("uq_pubsub_subs_node_id")), ) def downgrade(): - op.drop_table('pubsub_subs') + op.drop_table("pubsub_subs") diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/memory/migration/versions/8974efc51d22_create_tables_for_pubsub_caching.py --- a/libervia/backend/memory/migration/versions/8974efc51d22_create_tables_for_pubsub_caching.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/memory/migration/versions/8974efc51d22_create_tables_for_pubsub_caching.py Wed Jun 19 18:44:57 2024 +0200 @@ -5,53 +5,93 @@ Create Date: 2021-07-27 16:38:54.658212 """ + from alembic import op import sqlalchemy as sa from libervia.backend.memory.sqla_mapping import JID, Xml # revision identifiers, used by Alembic. -revision = '8974efc51d22' -down_revision = '602caf848068' +revision = "8974efc51d22" +down_revision = "602caf848068" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('pubsub_nodes', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('profile_id', sa.Integer(), nullable=True), - sa.Column('service', JID(), nullable=True), - sa.Column('name', sa.Text(), nullable=False), - sa.Column('subscribed', sa.Boolean(create_constraint=True, name='subscribed_bool'), nullable=False), - sa.Column('analyser', sa.Text(), nullable=True), - sa.Column('sync_state', sa.Enum('IN_PROGRESS', 'COMPLETED', 'ERROR', 'NO_SYNC', name='sync_state', create_constraint=True), nullable=True), - sa.Column('sync_state_updated', sa.Float(), nullable=False), - sa.Column('type', sa.Text(), nullable=True), - sa.Column('subtype', sa.Text(), nullable=True), - sa.Column('extra', sa.JSON(), nullable=True), - sa.ForeignKeyConstraint(['profile_id'], ['profiles.id'], name=op.f('fk_pubsub_nodes_profile_id_profiles'), ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id', name=op.f('pk_pubsub_nodes')), - sa.UniqueConstraint('profile_id', 'service', 'name', name=op.f('uq_pubsub_nodes_profile_id')) + op.create_table( + "pubsub_nodes", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("profile_id", sa.Integer(), nullable=True), + sa.Column("service", JID(), nullable=True), + sa.Column("name", sa.Text(), nullable=False), + sa.Column( + "subscribed", + sa.Boolean(create_constraint=True, name="subscribed_bool"), + nullable=False, + ), + sa.Column("analyser", sa.Text(), nullable=True), + sa.Column( + "sync_state", + sa.Enum( + "IN_PROGRESS", + "COMPLETED", + "ERROR", + "NO_SYNC", + name="sync_state", + create_constraint=True, + ), + nullable=True, + ), + sa.Column("sync_state_updated", sa.Float(), nullable=False), + sa.Column("type", sa.Text(), nullable=True), + sa.Column("subtype", sa.Text(), nullable=True), + sa.Column("extra", sa.JSON(), nullable=True), + sa.ForeignKeyConstraint( + ["profile_id"], + ["profiles.id"], + name=op.f("fk_pubsub_nodes_profile_id_profiles"), + ondelete="CASCADE", + ), + sa.PrimaryKeyConstraint("id", name=op.f("pk_pubsub_nodes")), + sa.UniqueConstraint( + "profile_id", "service", "name", name=op.f("uq_pubsub_nodes_profile_id") + ), ) - op.create_table('pubsub_items', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('node_id', sa.Integer(), nullable=False), - sa.Column('name', sa.Text(), nullable=False), - sa.Column('data', Xml(), nullable=False), - sa.Column('created', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False), - sa.Column('updated', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False), - sa.Column('parsed', sa.JSON(), nullable=True), - sa.ForeignKeyConstraint(['node_id'], ['pubsub_nodes.id'], name=op.f('fk_pubsub_items_node_id_pubsub_nodes'), ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id', name=op.f('pk_pubsub_items')), - sa.UniqueConstraint('node_id', 'name', name=op.f('uq_pubsub_items_node_id')) + op.create_table( + "pubsub_items", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("node_id", sa.Integer(), nullable=False), + sa.Column("name", sa.Text(), nullable=False), + sa.Column("data", Xml(), nullable=False), + sa.Column( + "created", + sa.DateTime(), + server_default=sa.text("(CURRENT_TIMESTAMP)"), + nullable=False, + ), + sa.Column( + "updated", + sa.DateTime(), + server_default=sa.text("(CURRENT_TIMESTAMP)"), + nullable=False, + ), + sa.Column("parsed", sa.JSON(), nullable=True), + sa.ForeignKeyConstraint( + ["node_id"], + ["pubsub_nodes.id"], + name=op.f("fk_pubsub_items_node_id_pubsub_nodes"), + ondelete="CASCADE", + ), + sa.PrimaryKeyConstraint("id", name=op.f("pk_pubsub_items")), + sa.UniqueConstraint("node_id", "name", name=op.f("uq_pubsub_items_node_id")), ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('pubsub_items') - op.drop_table('pubsub_nodes') + op.drop_table("pubsub_items") + op.drop_table("pubsub_nodes") # ### end Alembic commands ### diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/memory/migration/versions/fe3a02cb4bec_convert_legacypickle_columns_to_json.py --- a/libervia/backend/memory/migration/versions/fe3a02cb4bec_convert_legacypickle_columns_to_json.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/memory/migration/versions/fe3a02cb4bec_convert_legacypickle_columns_to_json.py Wed Jun 19 18:44:57 2024 +0200 @@ -5,10 +5,12 @@ Create Date: 2024-02-22 14:55:59.993983 """ + from alembic import op import sqlalchemy as sa import pickle import json + try: from libervia.backend.plugins.plugin_xep_0373 import PublicKeyMetadata except Exception: @@ -76,10 +78,10 @@ "Warning: Failed to convert Trust Management cache with value " f" {deserialized!r}, using empty array instead: {e}" ) - deserialized=[] + deserialized = [] ret = json.dumps(deserialized, ensure_ascii=False, default=str) - if table == 'history' and ret == "{}": + if table == "history" and ret == "{}": # For history, we can remove empty data, but for other tables it may be # significant. ret = None diff -r 64a85ce8be70 -r 0d7bb4df2343 libervia/backend/memory/params.py --- a/libervia/backend/memory/params.py Tue Jun 18 12:06:45 2024 +0200 +++ b/libervia/backend/memory/params.py Wed Jun 19 18:44:57 2024 +0200 @@ -227,15 +227,13 @@ if not default: log.info(_("No default profile, returning first one")) try: - default = self.host.memory.memory_data[ - "Profile_default" - ] = self.storage.get_profiles_list()[0] + default = self.host.memory.memory_data["Profile_default"] = ( + self.storage.get_profiles_list()[0] + ) except IndexError: 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]: @@ -284,8 +282,8 @@ to_remove.append(cat_node) continue to_remove_count = ( - 0 - ) # count the params to be removed from current category + 0 # count the params to be removed from current category + ) for node in cat_node.childNodes: if node.nodeName != "param" or not self.check_security_limit( node, security_limit @@ -382,9 +380,7 @@ node = self._get_param_node(name, category, "@ALL@") if not node: log.error( - _( - "Requested param [%(name)s] in category [%(category)s] doesn't exist !" - ) + _("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {"name": name, "category": category} ) return @@ -526,16 +522,18 @@ return defer.succeed(password) def _type_to_str(self, result): - """Convert result to string, according to its type """ + """Convert result to string, according to its type""" if isinstance(result, bool): return C.bool_const(result) elif isinstance(result, (list, set, tuple)): - return ', '.join(self._type_to_str(r) for r in result) + return ", ".join(self._type_to_str(r) for r in result) else: return str(result) - def get_string_param_a(self, name, category, attr="value", profile_key=C.PROF_KEY_NONE): - """ Same as param_get_a but for bridge: convert non string value to string """ + def get_string_param_a( + self, name, category, attr="value", profile_key=C.PROF_KEY_NONE + ): + """Same as param_get_a but for bridge: convert non string value to string""" return self._type_to_str( self.param_get_a(name, category, attr, profile_key=profile_key) ) @@ -560,9 +558,7 @@ node = self._get_param_node(name, category) if not node: log.error( - _( - "Requested param [%(name)s] in category [%(category)s] doesn't exist !" - ) + _("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {"name": name, "category": category} ) raise exceptions.NotFound @@ -596,10 +592,16 @@ return self._get_attr(node[1], attr, value) async def async_get_string_param_a( - self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, - profile=C.PROF_KEY_NONE): + self, + name, + category, + attr="value", + security_limit=C.NO_SECURITY_LIMIT, + profile=C.PROF_KEY_NONE, + ): value = await self.param_get_a_async( - name, category, attr, security_limit, profile_key=profile) + name, category, attr, security_limit, profile_key=profile + ) return self._type_to_str(value) def param_get_a_async( @@ -621,9 +623,7 @@ node = self._get_param_node(name, category) if not node: log.error( - _( - "Requested param [%(name)s] in category [%(category)s] doesn't exist !" - ) + _("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {"name": name, "category": category} ) raise ValueError("Requested param doesn't exist") @@ -662,14 +662,19 @@ ) def _get_params_values_from_category( - self, category, security_limit, app, extra_s, profile_key): + self, category, security_limit, app, extra_s, profile_key + ): client = self.host.get_client(profile_key) extra = data_format.deserialise(extra_s) - return defer.ensureDeferred(self.get_params_values_from_category( - client, category, security_limit, app, extra)) + return defer.ensureDeferred( + self.get_params_values_from_category( + client, category, security_limit, app, extra + ) + ) async def get_params_values_from_category( - self, client, category, security_limit, app='', extra=None): + self, client, category, security_limit, app="", extra=None + ): """Get all parameters "attribute" for a category @param category(unicode): the desired category @@ -697,8 +702,11 @@ ) continue value = await self.async_get_string_param_a( - name, category, security_limit=security_limit, - profile=client.profile) + name, + category, + security_limit=security_limit, + profile=client.profile, + ) ret[name] = value break @@ -751,9 +759,11 @@ def check_node(node): """Check the node against security_limit, app and extra""" - return (self.check_security_limit(node, security_limit) - and self.check_app(node, app) - and self.check_extra(node, extra)) + return ( + self.check_security_limit(node, security_limit) + and self.check_app(node, app) + and self.check_extra(node, extra) + ) if profile in self.params: profile_cache = self.params[profile] @@ -828,9 +838,7 @@ pass elif dest_params[name].getAttribute("type") == "jids_list": jids = profile_value.split("\t") - for jid_elt in dest_params[name].getElementsByTagName( - "jid" - ): + for jid_elt in dest_params[name].getElementsByTagName("jid"): dest_params[name].removeChild( jid_elt ) # remove all default @@ -866,7 +874,6 @@ return prof_xml - def _get_params_ui(self, security_limit, app, extra_s, profile_key): client = self.host.get_client(profile_key) extra = data_format.deserialise(extra_s) @@ -940,8 +947,14 @@ categories.append(cat.getAttribute("name")) return categories - def param_set(self, name, value, category, security_limit=C.NO_SECURITY_LIMIT, - profile_key=C.PROF_KEY_NONE): + def param_set( + 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 @@ -973,11 +986,8 @@ if not self.check_security_limit(node[1], security_limit): msg = _( "{profile!r} is trying to set parameter {name!r} in category " - "{category!r} without authorization!!!").format( - profile=repr(profile), - name=repr(name), - category=repr(category) - ) + "{category!r} without authorization!!!" + ).format(profile=repr(profile), name=repr(name), category=repr(category)) log.warning(msg) raise exceptions.PermissionError(msg) @@ -989,13 +999,12 @@ try: int(value) except ValueError: - log.warning(_( - "Trying to set parameter {name} in category {category} with" - "an non-integer value" - ).format( - name=repr(name), - category=repr(category) - )) + log.warning( + _( + "Trying to set parameter {name} in category {category} with" + "an non-integer value" + ).format(name=repr(name), category=repr(category)) + ) return defer.succeed(None) if node[1].hasAttribute("constraint"): constraint = node[1].getAttribute("constraint") @@ -1135,11 +1144,11 @@ @param app: name of the frontend requesting the parameters, or '' to get all parameters @return: True if node doesn't match category/name of extra['ignore'] list """ - ignore_list = extra.get('ignore') + ignore_list = extra.get("ignore") if not ignore_list: return True - category = node.parentNode.getAttribute('name') - name = node.getAttribute('name') + category = node.parentNode.getAttribute("name") + name = node.getAttribute("name") ignore = [category, name] in ignore_list if ignore: log.debug(f"Ignoring parameter {category}/{name} as requested") @@ -1164,10 +1173,10 @@ selected = 'selected="true"' selected_found = True else: - selected = '' + selected = "" str_list.append( - f'