diff --git a/libervia/backend/bridge/bridge_constructor/base_constructor.py b/libervia/backend/bridge/bridge_constructor/base_constructor.py
index f3fb3e70..547df982 100644
--- a/libervia/backend/bridge/bridge_constructor/base_constructor.py
+++ b/libervia/backend/bridge/bridge_constructor/base_constructor.py
@@ -77,7 +77,8 @@ class Constructor(object):
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 @@ class Constructor(object):
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 @@ class Constructor(object):
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 @@ class Constructor(object):
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 @@ class Constructor(object):
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 --git a/libervia/backend/bridge/bridge_constructor/constructors/dbus/constructor.py b/libervia/backend/bridge/bridge_constructor/constructors/dbus/constructor.py
index fc92ac47..02941eb7 100644
--- a/libervia/backend/bridge/bridge_constructor/constructors/dbus/constructor.py
+++ b/libervia/backend/bridge/bridge_constructor/constructors/dbus/constructor.py
@@ -27,14 +27,11 @@ class DbusConstructor(base_constructor.Constructor):
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 @@ class DbusConstructor(base_constructor.Constructor):
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 @@ class DbusConstructor(base_constructor.Constructor):
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 @@ class DbusConstructor(base_constructor.Constructor):
)
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 --git a/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py b/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py
index ec31ef31..53e45f2a 100644
--- a/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py
+++ b/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py
@@ -31,10 +31,8 @@ log = getLogger(__name__)
# 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 @@ class DBusObject(objects.DBusObject):
core_iface = DBusInterface(
const_INT_PREFIX + const_CORE_SUFFIX,
-##METHODS_DECLARATIONS_PART##
-##SIGNALS_DECLARATIONS_PART##
- )
- plugin_iface = DBusInterface(
- const_INT_PREFIX + const_PLUGIN_SUFFIX
+ ##METHODS_DECLARATIONS_PART##
+ ##SIGNALS_DECLARATIONS_PART##
)
+ plugin_iface = DBusInterface(const_INT_PREFIX + const_PLUGIN_SUFFIX)
dbusInterfaces = [core_iface, plugin_iface]
@@ -116,8 +112,10 @@ class DBusObject(objects.DBusObject):
d.addErrback(GenericException.create_and_raise)
return d
+
##METHODS_PART##
+
class bridge:
def __init__(self):
@@ -140,7 +138,7 @@ class bridge:
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 @@ class bridge:
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 @@ class bridge:
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 --git a/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py b/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py
index bdf896cc..c452f7e6 100644
--- a/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py
+++ b/libervia/backend/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py
@@ -33,12 +33,10 @@ log = getLogger(__name__)
# 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 @@ def dbus_to_bridge_exception(dbus_e):
"""
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 @@ def dbus_to_bridge_exception(dbus_e):
try:
message, condition = ast.literal_eval(message)
except (SyntaxError, ValueError, TypeError):
- condition = ''
+ condition = ""
return BridgeException(name, message, condition)
@@ -71,24 +69,33 @@ class bridge:
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 @@ class bridge:
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 @@ class bridge:
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 @@ class bridge:
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 @@ class bridge:
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 @@ class AIOBridge(bridge):
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 @@ class AIOBridge(bridge):
**kwargs,
timeout=const_TIMEOUT,
reply_handler=reply_handler,
- error_handler=error_handler
+ error_handler=error_handler,
)
else:
@@ -216,8 +237,9 @@ class AIOBridge(bridge):
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 --git a/libervia/backend/bridge/bridge_constructor/constructors/embedded/constructor.py b/libervia/backend/bridge/bridge_constructor/constructors/embedded/constructor.py
index 276be1c3..dfacd27d 100644
--- a/libervia/backend/bridge/bridge_constructor/constructors/embedded/constructor.py
+++ b/libervia/backend/bridge/bridge_constructor/constructors/embedded/constructor.py
@@ -48,9 +48,11 @@ class EmbeddedConstructor(base_constructor.Constructor):
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 @@ class EmbeddedConstructor(base_constructor.Constructor):
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 @@ class EmbeddedConstructor(base_constructor.Constructor):
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 @@ class EmbeddedConstructor(base_constructor.Constructor):
return ret
else:
callback({ret_or_nothing})""".format(
- **completion
+ **completion
+ )
)
def core_completion_signal(self, completion, function, default, arg_doc, async_):
diff --git a/libervia/backend/bridge/bridge_constructor/constructors/embedded/embedded_template.py b/libervia/backend/bridge/bridge_constructor/constructors/embedded/embedded_template.py
index 8d1cf78c..bfc08167 100644
--- a/libervia/backend/bridge/bridge_constructor/constructors/embedded/embedded_template.py
+++ b/libervia/backend/bridge/bridge_constructor/constructors/embedded/embedded_template.py
@@ -85,7 +85,9 @@ class _Bridge(object):
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 --git a/libervia/backend/bridge/bridge_constructor/constructors/mediawiki/constructor.py b/libervia/backend/bridge/bridge_constructor/constructors/mediawiki/constructor.py
index 864990a4..88acc08a 100644
--- a/libervia/backend/bridge/bridge_constructor/constructors/mediawiki/constructor.py
+++ b/libervia/backend/bridge/bridge_constructor/constructors/mediawiki/constructor.py
@@ -108,21 +108,21 @@ class MediawikiConstructor(base_constructor.Constructor):
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 --git a/libervia/backend/bridge/bridge_constructor/constructors/pb/constructor.py b/libervia/backend/bridge/bridge_constructor/constructors/pb/constructor.py
index fc1db348..444a11b4 100644
--- a/libervia/backend/bridge/bridge_constructor/constructors/pb/constructor.py
+++ b/libervia/backend/bridge/bridge_constructor/constructors/pb/constructor.py
@@ -61,11 +61,13 @@ class pbConstructor(base_constructor.Constructor):
{
"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 --git a/libervia/backend/bridge/bridge_constructor/constructors/pb/pb_core_template.py b/libervia/backend/bridge/bridge_constructor/constructors/pb/pb_core_template.py
index 1350fa2c..95f1e8b3 100644
--- a/libervia/backend/bridge/bridge_constructor/constructors/pb/pb_core_template.py
+++ b/libervia/backend/bridge/bridge_constructor/constructors/pb/pb_core_template.py
@@ -99,6 +99,7 @@ class PBRoot(pb.Root):
del self.signals_paused
log.debug("bridge signals have been reactivated")
+
##METHODS_PART##
@@ -136,7 +137,7 @@ class bridge(object):
# 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 @@ class bridge(object):
"""
self.root._bridge_reactivate_signals()
+
##SIGNALS_PART##
diff --git a/libervia/backend/bridge/bridge_constructor/constructors/pb/pb_frontend_template.py b/libervia/backend/bridge/bridge_constructor/constructors/pb/pb_frontend_template.py
index 95ede106..20027f5d 100644
--- a/libervia/backend/bridge/bridge_constructor/constructors/pb/pb_frontend_template.py
+++ b/libervia/backend/bridge/bridge_constructor/constructors/pb/pb_frontend_template.py
@@ -48,9 +48,7 @@ class SignalsHandler(pb.Referenceable):
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 @@ class bridge(object):
"""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 @@ class bridge(object):
##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 @@ class AIOBridge(bridge):
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 @@ class AIOBridge(bridge):
d = super().bridge_connect(callback=None, errback=None)
return await d.asFuture(asyncio.get_event_loop())
+
##ASYNC_METHODS_PART##
diff --git a/libervia/backend/bridge/dbus_bridge.py b/libervia/backend/bridge/dbus_bridge.py
index 2a7f2806..2e428bce 100644
--- a/libervia/backend/bridge/dbus_bridge.py
+++ b/libervia/backend/bridge/dbus_bridge.py
@@ -31,10 +31,8 @@ log = getLogger(__name__)
# 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 @@ class DBusObject(objects.DBusObject):
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'),
- )
- plugin_iface = DBusInterface(
- const_INT_PREFIX + const_PLUGIN_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"),
)
+ plugin_iface = DBusInterface(const_INT_PREFIX + const_PLUGIN_SUFFIX)
dbusInterfaces = [core_iface, plugin_iface]
@@ -213,7 +215,7 @@ class DBusObject(objects.DBusObject):
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 @@ class DBusObject(objects.DBusObject):
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 @@ class DBusObject(objects.DBusObject):
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 @@ class DBusObject(objects.DBusObject):
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 @@ class DBusObject(objects.DBusObject):
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 @@ class DBusObject(objects.DBusObject):
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 @@ class DBusObject(objects.DBusObject):
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 @@ class DBusObject(objects.DBusObject):
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 @@ class DBusObject(objects.DBusObject):
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 @@ class DBusObject(objects.DBusObject):
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 @@ class DBusObject(objects.DBusObject):
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 @@ class DBusObject(objects.DBusObject):
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 @@ class DBusObject(objects.DBusObject):
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 @@ class bridge:
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 @@ class bridge:
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 @@ class bridge:
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 @@ class bridge:
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 @@ class bridge:
"""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 --git a/libervia/backend/bridge/pb.py b/libervia/backend/bridge/pb.py
index 10f7ec5b..3d84267a 100644
--- a/libervia/backend/bridge/pb.py
+++ b/libervia/backend/bridge/pb.py
@@ -99,6 +99,7 @@ class PBRoot(pb.Root):
del self.signals_paused
log.debug("bridge signals have been reactivated")
+
##METHODS_PART##
@@ -136,7 +137,7 @@ class bridge(object):
# 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 @@ class bridge(object):
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 @@ class bridge(object):
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 --git a/libervia/backend/core/constants.py b/libervia/backend/core/constants.py
index b92cc5b6..e5dc15dc 100644
--- a/libervia/backend/core/constants.py
+++ b/libervia/backend/core/constants.py
@@ -46,9 +46,7 @@ class Const(object):
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 @@ class Const(object):
## 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 @@ class Const(object):
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 @@ class Const(object):
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 @@ class Const(object):
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 @@ if (
# 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 @@ if (
]
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 @@ if (
# 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 @@ if (
realpath(expanduser(path) + "sat.conf")
for path in Const.CONFIG_PATHS
]
-
diff --git a/libervia/backend/core/core_types.py b/libervia/backend/core/core_types.py
index 7c45d219..00d3ac16 100644
--- a/libervia/backend/core/core_types.py
+++ b/libervia/backend/core/core_types.py
@@ -34,11 +34,10 @@ class SatXMPPEntity:
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 @@ class EncryptionSession(TypedDict):
# 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 --git a/libervia/backend/core/exceptions.py b/libervia/backend/core/exceptions.py
index 887981f0..c3586bbf 100644
--- a/libervia/backend/core/exceptions.py
+++ b/libervia/backend/core/exceptions.py
@@ -62,6 +62,7 @@ class MissingModule(Exception):
class MissingPlugin(Exception):
"""A SàT plugin needed for a feature/method is missing"""
+
pass
@@ -129,6 +130,7 @@ class ParsingError(ValueError):
class EncryptionError(Exception):
"""Invalid encryption"""
+
pass
@@ -143,6 +145,7 @@ class NetworkError(Exception):
class InvalidCertificate(Exception):
"""A TLS certificate is not valid"""
+
pass
diff --git a/libervia/backend/core/i18n.py b/libervia/backend/core/i18n.py
index bc0609ab..b195b06a 100644
--- a/libervia/backend/core/i18n.py
+++ b/libervia/backend/core/i18n.py
@@ -37,7 +37,6 @@ try:
)
_translators[lang].install()
-
except ImportError:
log.warning("gettext support disabled")
diff --git a/libervia/backend/core/launcher.py b/libervia/backend/core/launcher.py
index 22de51c2..bcd844e9 100644
--- a/libervia/backend/core/launcher.py
+++ b/libervia/backend/core/launcher.py
@@ -201,11 +201,7 @@ class Launcher:
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 --git a/libervia/backend/core/log.py b/libervia/backend/core/log.py
index c50dcc7c..777d770f 100644
--- a/libervia/backend/core/log.py
+++ b/libervia/backend/core/log.py
@@ -36,8 +36,8 @@ import traceback
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 Filtered(Exception):
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 @@ class Logger:
message: object,
level: Optional[str] = None,
exc_info: _ExcInfoType = False,
- **kwargs
+ **kwargs,
) -> None:
"""Actually log the message
@@ -83,11 +84,7 @@ class Logger:
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 @@ class Logger:
"""
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 @@ class Logger:
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 @@ class FilterName(object):
"""
assert name_re
import re
+
self.name_re = re.compile(name_re)
def filter(self, record):
@@ -180,10 +182,12 @@ class FilterName(object):
@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 @@ class ConfigureBase:
# 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 @@ class ConfigureBase:
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 @@ class ConfigureBase:
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 @@ class ConfigureBase:
# 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 @@ class ConfigureBase:
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 @@ class ConfigureBase:
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 @@ class ConfigureBase:
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 @@ class ConfigureCustom(ConfigureBase):
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 @@ def configure(backend_, **options):
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 --git a/libervia/backend/core/log_config.py b/libervia/backend/core/log_config.py
index e4d4843d..c2ccf95c 100644
--- a/libervia/backend/core/log_config.py
+++ b/libervia/backend/core/log_config.py
@@ -31,6 +31,7 @@ class TwistedLogger(log.Logger):
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 @@ class ConfigureBasic(log.ConfigureBase):
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 @@ class ConfigureTwisted(ConfigureBasic):
def pre_treatment(self):
from twisted import logger
+
global logger
self.level_map = {
C.LOG_LVL_DEBUG: logger.LogLevel.debug,
@@ -125,6 +127,7 @@ class ConfigureTwisted(ConfigureBasic):
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 @@ class ConfigureTwisted(ConfigureBasic):
"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 @@ class ConfigureTwisted(ConfigureBasic):
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 @@ class ConfigureTwisted(ConfigureBasic):
"""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 ConfigureStandard(ConfigureBasic):
class SatFormatter(logging.Formatter):
"""Formatter which manage SàT specificities"""
+
_format = fmt
_with_profile = "%(profile)s" in fmt
@@ -310,11 +317,9 @@ class ConfigureStandard(ConfigureBasic):
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 --git a/libervia/backend/core/main.py b/libervia/backend/core/main.py
index 66a87d2f..25b0ce72 100644
--- a/libervia/backend/core/main.py
+++ b/libervia/backend/core/main.py
@@ -29,6 +29,7 @@ from wokkel.data_form import Option
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 @@ import libervia.backend.plugins
log = getLogger(__name__)
+
class LiberviaBackend(service.Service):
def _init(self):
@@ -67,7 +69,7 @@ class LiberviaBackend(service.Service):
# 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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
else:
self.local_shared_path = None
-
defer.ensureDeferred(self._post_init())
@property
@@ -174,40 +172,58 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
# 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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
"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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
"""
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
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 @@ class LiberviaBackend(service.Service):
"""
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 @@ class LiberviaBackend(service.Service):
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 --git a/libervia/backend/core/xmpp.py b/libervia/backend/core/xmpp.py
index c4ff2b7f..a5228dd4 100644
--- a/libervia/backend/core/xmpp.py
+++ b/libervia/backend/core/xmpp.py
@@ -91,6 +91,7 @@ class ClientPluginWrapper:
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 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
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 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
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 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
# 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 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
@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 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
)
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 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
# 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 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
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 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
# 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 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
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 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
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 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
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 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
# 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 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
):
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 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
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 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
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 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
# 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 @@ class SatXMPPClient(SatXMPPEntity, wokkel_client.XMPPClient):
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 @@ class SatXMPPClient(SatXMPPEntity, wokkel_client.XMPPClient):
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 @@ class SatXMPPClient(SatXMPPEntity, wokkel_client.XMPPClient):
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 @@ class SatXMPPClient(SatXMPPEntity, wokkel_client.XMPPClient):
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 @@ class SatXMPPComponent(SatXMPPEntity, component.Component):
"""
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 @@ class SatXMPPComponent(SatXMPPEntity, component.Component):
@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 @@ class SatXMPPComponent(SatXMPPEntity, component.Component):
@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 @@ class SatMessageProtocol(xmppim.MessageProtocol):
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 @@ class SatMessageProtocol(xmppim.MessageProtocol):
@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 @@ class SatMessageProtocol(xmppim.MessageProtocol):
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 @@ class SatMessageProtocol(xmppim.MessageProtocol):
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 @@ class SatMessageProtocol(xmppim.MessageProtocol):
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 @@ class SatMessageProtocol(xmppim.MessageProtocol):
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 @@ class SatMessageProtocol(xmppim.MessageProtocol):
# 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 @@ class SatMessageProtocol(xmppim.MessageProtocol):
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 @@ class SatMessageProtocol(xmppim.MessageProtocol):
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 @@ class SatMessageProtocol(xmppim.MessageProtocol):
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 @@ class LiberviaRosterProtocol(xmppim.RosterClientProtocol):
@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 @@ class LiberviaRosterProtocol(xmppim.RosterClientProtocol):
@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 @@ class LiberviaRosterProtocol(xmppim.RosterClientProtocol):
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 @@ class LiberviaRosterProtocol(xmppim.RosterClientProtocol):
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 @@ class LiberviaRosterProtocol(xmppim.RosterClientProtocol):
# 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 @@ class LiberviaRosterProtocol(xmppim.RosterClientProtocol):
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 @@ class LiberviaRosterProtocol(xmppim.RosterClientProtocol):
"""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 @@ class SatPresenceProtocol(xmppim.PresenceClientProtocol):
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 @@ class SatPresenceProtocol(xmppim.PresenceClientProtocol):
# 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 @@ class SatDiscoProtocol(disco.DiscoClientProtocol):
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 @@ class SatVersionHandler(generic.VersionHandler):
@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 --git a/libervia/backend/memory/crypto.py b/libervia/backend/memory/crypto.py
index 4fe906c2..a69bde68 100644
--- a/libervia/backend/memory/crypto.py
+++ b/libervia/backend/memory/crypto.py
@@ -57,7 +57,9 @@ class BlockCipher:
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 @@ class PasswordHasher:
length=16,
salt=salt,
iterations=1000,
- backend=crypto_backend
+ backend=crypto_backend,
)
key = kdf.derive(password.encode())
return b64encode(salt + key).decode()
diff --git a/libervia/backend/memory/disco.py b/libervia/backend/memory/disco.py
index aa5eb0c8..f048754c 100755
--- a/libervia/backend/memory/disco.py
+++ b/libervia/backend/memory/disco.py
@@ -111,7 +111,7 @@ class HashManager(object):
class Discovery(object):
- """ Manage capabilities of entities """
+ """Manage capabilities of entities"""
def __init__(self, host):
self.host = host
@@ -172,7 +172,7 @@ class Discovery(object):
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 @@ class Discovery(object):
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 @@ class Discovery(object):
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 @@ class Discovery(object):
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 @@ class Discovery(object):
# 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 @@ class Discovery(object):
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 @@ class Discovery(object):
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 --git a/libervia/backend/memory/encryption.py b/libervia/backend/memory/encryption.py
index 73291069..b442e805 100644
--- a/libervia/backend/memory/encryption.py
+++ b/libervia/backend/memory/encryption.py
@@ -23,7 +23,11 @@ from typing import Optional
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 @@ log = getLogger(__name__)
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 @@ class EncryptionHandler:
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 @@ class EncryptionHandler:
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 @@ class EncryptionHandler:
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 @@ class EncryptionHandler:
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 @@ class EncryptionHandler:
@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 @@ class EncryptionHandler:
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 @@ class EncryptionHandler:
# 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 @@ class EncryptionHandler:
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 @@ class EncryptionHandler:
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 @@ class EncryptionHandler:
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 @@ class EncryptionHandler:
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 @@ class EncryptionHandler:
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 @@ class EncryptionHandler:
@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 @@ class EncryptionHandler:
@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 @@ class EncryptionHandler:
@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 @@ class EncryptionHandler:
@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 @@ class EncryptionHandler:
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 @@ class EncryptionHandler:
@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 @@ class EncryptionHandler:
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 --git a/libervia/backend/memory/memory.py b/libervia/backend/memory/memory.py
index dbfde9e8..b9ca036e 100644
--- a/libervia/backend/memory/memory.py
+++ b/libervia/backend/memory/memory.py
@@ -155,7 +155,7 @@ class Sessions(object):
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 @@ class Memory:
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 @@ class Memory:
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 @@ class Memory:
## 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 @@ class Memory:
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 @@ class Memory:
)
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 @@ class Memory:
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 @@ class Memory:
# 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 @@ class Memory:
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 @@ class Memory:
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 @@ class Memory:
"""
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 @@ class Memory:
# 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 @@ class Memory:
"""
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 @@ class Memory:
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 @@ class Memory:
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 @@ class Memory:
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 @@ class Memory:
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 @@ class Memory:
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 @@ class Memory:
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 @@ class Memory:
## 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 @@ class Memory:
)
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 @@ class Memory:
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 @@ class Memory:
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 @@ class Memory:
# 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 @@ class Memory:
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 @@ class Memory:
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 @@ class Memory:
- "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 @@ class Memory:
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 @@ class Memory:
- "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 @@ class Memory:
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 @@ class Memory:
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 @@ class Memory:
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 @@ class Memory:
)
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 @@ class Memory:
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 @@ class Memory:
@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 @@ class Memory:
# 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 @@ class Memory:
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 @@ class Memory:
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 @@ class Memory:
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 @@ class Memory:
requires_action,
notification_priority,
expire_at or None,
- data_format.deserialise(extra_s)
+ data_format.deserialise(extra_s),
)
)
@@ -1955,21 +2003,28 @@ class Memory:
@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 @@ class Memory:
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 @@ class Memory:
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 @@ class Memory:
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 @@ class Memory:
"""
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 @@ class Memory:
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 --git a/libervia/backend/memory/migration/env.py b/libervia/backend/memory/migration/env.py
index 83b95cfb..5d28d48b 100644
--- a/libervia/backend/memory/migration/env.py
+++ b/libervia/backend/memory/migration/env.py
@@ -62,7 +62,7 @@ def do_run_migrations(connection):
connection=connection,
target_metadata=target_metadata,
render_as_batch=True,
- include_name=include_name
+ include_name=include_name,
)
with context.begin_transaction():
diff --git a/libervia/backend/memory/migration/versions/129ac51807e4_create_virtual_table_for_full_text_.py b/libervia/backend/memory/migration/versions/129ac51807e4_create_virtual_table_for_full_text_.py
index 5d1fca0b..d054c1fb 100644
--- a/libervia/backend/memory/migration/versions/129ac51807e4_create_virtual_table_for_full_text_.py
+++ b/libervia/backend/memory/migration/versions/129ac51807e4_create_virtual_table_for_full_text_.py
@@ -5,13 +5,14 @@ Revises: 8974efc51d22
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 @@ def upgrade():
"('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 --git a/libervia/backend/memory/migration/versions/2ab01aa1f686_create_table_for_notifications.py b/libervia/backend/memory/migration/versions/2ab01aa1f686_create_table_for_notifications.py
index c85e36ed..01140fbe 100644
--- a/libervia/backend/memory/migration/versions/2ab01aa1f686_create_table_for_notifications.py
+++ b/libervia/backend/memory/migration/versions/2ab01aa1f686_create_table_for_notifications.py
@@ -5,42 +5,68 @@ Revises: 4b002773cf92
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 --git a/libervia/backend/memory/migration/versions/4b002773cf92_add_origin_id_column_to_history_and_.py b/libervia/backend/memory/migration/versions/4b002773cf92_add_origin_id_column_to_history_and_.py
index ca3e6080..e0cbe171 100644
--- a/libervia/backend/memory/migration/versions/4b002773cf92_add_origin_id_column_to_history_and_.py
+++ b/libervia/backend/memory/migration/versions/4b002773cf92_add_origin_id_column_to_history_and_.py
@@ -5,56 +5,43 @@ Revises: 79e5f3313fa4
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 --git a/libervia/backend/memory/migration/versions/602caf848068_drop_message_types_table_fix_nullable.py b/libervia/backend/memory/migration/versions/602caf848068_drop_message_types_table_fix_nullable.py
index 20d81042..5e39ff27 100644
--- a/libervia/backend/memory/migration/versions/602caf848068_drop_message_types_table_fix_nullable.py
+++ b/libervia/backend/memory/migration/versions/602caf848068_drop_message_types_table_fix_nullable.py
@@ -5,6 +5,7 @@ Revises:
Create Date: 2021-06-26 12:42:54.148313
"""
+
from alembic import op
from sqlalchemy import (
Table,
diff --git a/libervia/backend/memory/migration/versions/610345f77e75_add_version_id_to_history.py b/libervia/backend/memory/migration/versions/610345f77e75_add_version_id_to_history.py
index c2104853..00a2656e 100644
--- a/libervia/backend/memory/migration/versions/610345f77e75_add_version_id_to_history.py
+++ b/libervia/backend/memory/migration/versions/610345f77e75_add_version_id_to_history.py
@@ -5,22 +5,27 @@ Revises: 2ab01aa1f686
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 --git a/libervia/backend/memory/migration/versions/79e5f3313fa4_create_table_for_pubsub_subscriptions.py b/libervia/backend/memory/migration/versions/79e5f3313fa4_create_table_for_pubsub_subscriptions.py
index 3df7a4ff..9321e6df 100644
--- a/libervia/backend/memory/migration/versions/79e5f3313fa4_create_table_for_pubsub_subscriptions.py
+++ b/libervia/backend/memory/migration/versions/79e5f3313fa4_create_table_for_pubsub_subscriptions.py
@@ -5,29 +5,36 @@ Revises: 129ac51807e4
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 --git a/libervia/backend/memory/migration/versions/8974efc51d22_create_tables_for_pubsub_caching.py b/libervia/backend/memory/migration/versions/8974efc51d22_create_tables_for_pubsub_caching.py
index 5497d264..68863bc3 100644
--- a/libervia/backend/memory/migration/versions/8974efc51d22_create_tables_for_pubsub_caching.py
+++ b/libervia/backend/memory/migration/versions/8974efc51d22_create_tables_for_pubsub_caching.py
@@ -5,53 +5,93 @@ Revises: 602caf848068
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 --git a/libervia/backend/memory/migration/versions/fe3a02cb4bec_convert_legacypickle_columns_to_json.py b/libervia/backend/memory/migration/versions/fe3a02cb4bec_convert_legacypickle_columns_to_json.py
index 1c29f0ce..85264c08 100644
--- a/libervia/backend/memory/migration/versions/fe3a02cb4bec_convert_legacypickle_columns_to_json.py
+++ b/libervia/backend/memory/migration/versions/fe3a02cb4bec_convert_legacypickle_columns_to_json.py
@@ -5,10 +5,12 @@ Revises: 610345f77e75
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 @@ def convert_pickle_to_json(value, table, primary_keys):
"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 --git a/libervia/backend/memory/params.py b/libervia/backend/memory/params.py
index 83c70017..c40cf3d4 100644
--- a/libervia/backend/memory/params.py
+++ b/libervia/backend/memory/params.py
@@ -227,15 +227,13 @@ class Params(object):
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 @@ class Params(object):
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 @@ class Params(object):
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 @@ class Params(object):
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 @@ class Params(object):
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 @@ class Params(object):
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 @@ class Params(object):
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 @@ class Params(object):
)
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 @@ class Params(object):
)
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 @@ class Params(object):
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 @@ class Params(object):
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 @@ class Params(object):
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 @@ class Params(object):
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 @@ class Params(object):
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 @@ class Params(object):
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 @@ class Params(object):
@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 @@ def make_options(options, selected=None):
selected = 'selected="true"'
selected_found = True
else:
- selected = ''
+ selected = ""
str_list.append(
- f''
+ f""
)
if not selected_found:
raise ValueError(f"selected value ({selected}) not found in options")
- return '\n'.join(str_list)
+ return "\n".join(str_list)
diff --git a/libervia/backend/memory/persistent.py b/libervia/backend/memory/persistent.py
index 23f48fce..07d473bb 100644
--- a/libervia/backend/memory/persistent.py
+++ b/libervia/backend/memory/persistent.py
@@ -34,7 +34,9 @@ class PersistentDict:
r"""A dictionary which save persistently each value assigned
/!\ be careful, each assignment means a database write
- /!\ Memory must be initialised before loading/setting value with instances of this class"""
+ /!\ Memory must be initialised before loading/setting value with instances of this class
+ """
+
storage = None
binary = False
@@ -60,9 +62,11 @@ class PersistentDict:
need to be called before any other operation
@return: defers the PersistentDict instance itself
"""
- d = defer.ensureDeferred(self.storage.get_privates(
- self.namespace, binary=self.binary, profile=self.profile
- ))
+ d = defer.ensureDeferred(
+ self.storage.get_privates(
+ self.namespace, binary=self.binary, profile=self.profile
+ )
+ )
d.addCallback(self._set_cache)
d.addCallback(lambda __: self)
return d
@@ -130,7 +134,9 @@ class PersistentDict:
def clear(self):
"""Delete all values from this namespace"""
self._cache.clear()
- return self.storage.del_private_namespace(self.namespace, self.binary, self.profile)
+ return self.storage.del_private_namespace(
+ self.namespace, self.binary, self.profile
+ )
def get(self, key, default=None):
return self._cache.get(key, default)
@@ -148,7 +154,8 @@ class PersistentDict:
"""Async del, return a Deferred fired when value is actually deleted"""
self._cache.__delitem__(key)
return self.storage.del_private_value(
- self.namespace, key, self.binary, self.profile)
+ self.namespace, key, self.binary, self.profile
+ )
def setdefault(self, key, default):
try:
@@ -171,6 +178,7 @@ class PersistentDict:
class PersistentBinaryDict(PersistentDict):
"""Persistent dict where value can be any python data (instead of string only)"""
+
binary = True
@@ -181,6 +189,7 @@ class LazyPersistentBinaryDict(PersistentBinaryDict):
to save memory.
/!\ most of methods return a Deferred
"""
+
# TODO: missing methods should be implemented using database access
# TODO: a cache would be useful (which is deleted after a timeout)
@@ -192,16 +201,20 @@ class LazyPersistentBinaryDict(PersistentBinaryDict):
raise NotImplementedError
def items(self):
- d = defer.ensureDeferred(self.storage.get_privates(
- self.namespace, binary=self.binary, profile=self.profile
- ))
+ d = defer.ensureDeferred(
+ self.storage.get_privates(
+ self.namespace, binary=self.binary, profile=self.profile
+ )
+ )
d.addCallback(lambda data_dict: data_dict.items())
return d
def all(self):
- return defer.ensureDeferred(self.storage.get_privates(
- self.namespace, binary=self.binary, profile=self.profile
- ))
+ return defer.ensureDeferred(
+ self.storage.get_privates(
+ self.namespace, binary=self.binary, profile=self.profile
+ )
+ )
def __repr__(self):
return self.__str__()
@@ -231,7 +244,7 @@ class LazyPersistentBinaryDict(PersistentBinaryDict):
raise NotImplementedError
def __hash__(self):
- return hash(str(self.__class__) + self.namespace + (self.profile or ''))
+ return hash(str(self.__class__) + self.namespace + (self.profile or ""))
def __bool__(self):
raise NotImplementedError
@@ -252,9 +265,11 @@ class LazyPersistentBinaryDict(PersistentBinaryDict):
def __getitem__(self, key):
"""get the value as a Deferred"""
- d = defer.ensureDeferred(self.storage.get_privates(
- self.namespace, keys=[key], binary=self.binary, profile=self.profile
- ))
+ d = defer.ensureDeferred(
+ self.storage.get_privates(
+ self.namespace, keys=[key], binary=self.binary, profile=self.profile
+ )
+ )
d.addCallback(self._data2value, key)
return d
@@ -291,7 +306,8 @@ class LazyPersistentBinaryDict(PersistentBinaryDict):
"""Async del, return a Deferred fired when value is actually deleted"""
# XXX: similar as PersistentDict.adel, but doesn't use cache
return self.storage.del_private_value(
- self.namespace, key, self.binary, self.profile)
+ self.namespace, key, self.binary, self.profile
+ )
def setdefault(self, key, default):
raise NotImplementedError
@@ -314,4 +330,6 @@ class LazyPersistentBinaryDict(PersistentBinaryDict):
@param key(unicode): key to delete
@return (D): A deferred fired when delete is done
"""
- return self.storage.del_private_value(self.namespace, key, self.binary, self.profile)
+ return self.storage.del_private_value(
+ self.namespace, key, self.binary, self.profile
+ )
diff --git a/libervia/backend/memory/sqla.py b/libervia/backend/memory/sqla.py
index b8bead42..3a731fe7 100644
--- a/libervia/backend/memory/sqla.py
+++ b/libervia/backend/memory/sqla.py
@@ -215,7 +215,7 @@ class Storage:
engine = create_async_engine(
db_config["url"],
future=True,
- json_serializer=lambda obj: json.dumps(obj, ensure_ascii=False)
+ json_serializer=lambda obj: json.dumps(obj, ensure_ascii=False),
)
new_base = not db_config["path"].exists()
@@ -1778,7 +1778,7 @@ class Storage:
type_: Optional[NotificationType] = None,
status: Optional[NotificationStatus] = None,
requires_action: Optional[bool] = None,
- min_priority: Optional[int] = None
+ min_priority: Optional[int] = None,
) -> List[Notification]:
"""Retrieve all notifications for a given profile with optional filters.
@@ -1790,7 +1790,9 @@ class Storage:
@return: list of matching Notification instances.
"""
profile_id = self.profiles[client.profile]
- filters = [or_(Notification.profile_id == profile_id, Notification.profile_id.is_(None))]
+ filters = [
+ or_(Notification.profile_id == profile_id, Notification.profile_id.is_(None))
+ ]
if type_:
filters.append(Notification.type == type_)
@@ -1803,9 +1805,7 @@ class Storage:
async with self.session() as session:
result = await session.execute(
- select(Notification)
- .where(and_(*filters))
- .order_by(Notification.id)
+ select(Notification).where(and_(*filters)).order_by(Notification.id)
)
return result.scalars().all()
diff --git a/libervia/backend/memory/sqla_mapping.py b/libervia/backend/memory/sqla_mapping.py
index 55aeca2a..bf773dc9 100644
--- a/libervia/backend/memory/sqla_mapping.py
+++ b/libervia/backend/memory/sqla_mapping.py
@@ -89,7 +89,7 @@ class Profiles(dict):
profiles = Profiles()
-def get_profile_by_id( profile_id):
+def get_profile_by_id(profile_id):
return profiles.id_to_profile.get(profile_id)
@@ -263,23 +263,17 @@ class History(Base):
profile = relationship("Profile")
messages = relationship(
- "Message",
- backref="history",
- cascade="all, delete-orphan",
- passive_deletes=True
+ "Message", backref="history", cascade="all, delete-orphan", passive_deletes=True
)
subjects = relationship(
- "Subject",
- backref="history",
- cascade="all, delete-orphan",
- passive_deletes=True
+ "Subject", backref="history", cascade="all, delete-orphan", passive_deletes=True
)
thread = relationship(
"Thread",
uselist=False,
back_populates="history",
cascade="all, delete-orphan",
- passive_deletes=True
+ passive_deletes=True,
)
__mapper_args__ = {"version_id_col": version_id}
@@ -332,9 +326,9 @@ class History(Base):
extra["thread_parent"] = self.thread.parent_id
return {
- "from": f"{self.source}/{self.source_res}"
- if self.source_res
- else self.source,
+ "from": (
+ f"{self.source}/{self.source_res}" if self.source_res else self.source
+ ),
"to": f"{self.dest}/{self.dest_res}" if self.dest_res else self.dest,
"uid": self.uid,
"message": {m.language or "": m.message for m in self.messages},
@@ -456,7 +450,9 @@ class Notification(Base):
timestamp = Column(Float, nullable=False, default=time.time)
expire_at = Column(Float, nullable=True)
- profile_id = Column(ForeignKey("profiles.id", ondelete="CASCADE"), index=True, nullable=True)
+ profile_id = Column(
+ ForeignKey("profiles.id", ondelete="CASCADE"), index=True, nullable=True
+ )
profile = relationship("Profile")
type = Column(Enum(NotificationType), nullable=False)
diff --git a/libervia/backend/models/core.py b/libervia/backend/models/core.py
index ec7ad77c..8c230a48 100644
--- a/libervia/backend/models/core.py
+++ b/libervia/backend/models/core.py
@@ -40,6 +40,7 @@ class MessageReactionData(MessageUpdateData):
class MessageEdition(BaseModel):
"""Data used to send a message edition"""
+
message: dict[str, str]
subject: dict[str, str] = Field(default_factory=dict)
extra: dict[str, str] = Field(default_factory=dict)
diff --git a/libervia/backend/plugins/plugin_adhoc_dbus.py b/libervia/backend/plugins/plugin_adhoc_dbus.py
index 704c9bfb..027bb03a 100644
--- a/libervia/backend/plugins/plugin_adhoc_dbus.py
+++ b/libervia/backend/plugins/plugin_adhoc_dbus.py
@@ -37,8 +37,10 @@ try:
from lxml import etree
except ImportError:
etree = None
- log.warning("Missing module lxml, please download/install it from http://lxml.de/ ."
- "Auto D-Bus discovery will be disabled")
+ log.warning(
+ "Missing module lxml, please download/install it from http://lxml.de/ ."
+ "Auto D-Bus discovery will be disabled"
+ )
try:
import txdbus
@@ -59,23 +61,26 @@ MPRIS_PREFIX = "org.mpris.MediaPlayer2"
CMD_GO_BACK = "GoBack"
CMD_GO_FWD = "GoFW"
SEEK_OFFSET = 5 * 1000 * 1000
-MPRIS_COMMANDS = ["org.mpris.MediaPlayer2.Player." + cmd for cmd in (
- "Previous", CMD_GO_BACK, "PlayPause", CMD_GO_FWD, "Next")]
+MPRIS_COMMANDS = [
+ "org.mpris.MediaPlayer2.Player." + cmd
+ for cmd in ("Previous", CMD_GO_BACK, "PlayPause", CMD_GO_FWD, "Next")
+]
MPRIS_PATH = "/org/mpris/MediaPlayer2"
-MPRIS_PROPERTIES = OrderedDict((
- ("org.mpris.MediaPlayer2", (
- "Identity",
- )),
- ("org.mpris.MediaPlayer2.Player", (
- "Metadata",
- "PlaybackStatus",
- "Volume",
- )),
- ))
+MPRIS_PROPERTIES = OrderedDict(
+ (
+ ("org.mpris.MediaPlayer2", ("Identity",)),
+ (
+ "org.mpris.MediaPlayer2.Player",
+ (
+ "Metadata",
+ "PlaybackStatus",
+ "Volume",
+ ),
+ ),
+ )
+)
MPRIS_METADATA_KEY = "Metadata"
-MPRIS_METADATA_MAP = OrderedDict((
- ("xesam:title", "Title"),
- ))
+MPRIS_METADATA_MAP = OrderedDict((("xesam:title", "Title"),))
INTROSPECT_METHOD = "Introspect"
IGNORED_IFACES_START = (
@@ -126,18 +131,20 @@ class AdHocDBus:
async def profile_connected(self, client):
if txdbus is not None:
if self.session_con is None:
- self.session_con = await dbus_client.connect(reactor, 'session')
+ self.session_con = await dbus_client.connect(reactor, "session")
self.fd_object = await self.session_con.getRemoteObject(FD_NAME, FD_PATH)
self._c.add_ad_hoc_command(
- client, self.local_media_cb, D_("Media Players"),
+ client,
+ self.local_media_cb,
+ D_("Media Players"),
node=NS_MEDIA_PLAYER,
- timeout=60*60*6 # 6 hours timeout, to avoid breaking remote
- # in the middle of a movie
+ timeout=60 * 60 * 6, # 6 hours timeout, to avoid breaking remote
+ # in the middle of a movie
)
async def _dbus_async_call(self, proxy, method, *args, **kwargs):
- """ Call a DBus method asynchronously and return a deferred
+ """Call a DBus method asynchronously and return a deferred
@param proxy: DBus object proxy, as returner by get_object
@param method: name of the method to call
@@ -152,17 +159,19 @@ class AdHocDBus:
async def _dbus_get_property(self, proxy, interface, name):
return await self._dbus_async_call(
- proxy, "Get", interface, name, interface="org.freedesktop.DBus.Properties")
-
+ proxy, "Get", interface, name, interface="org.freedesktop.DBus.Properties"
+ )
async def _dbus_list_names(self):
return await self.fd_object.callRemote("ListNames")
async def _dbus_introspect(self, proxy):
- return await self._dbus_async_call(proxy, INTROSPECT_METHOD, interface=INTROSPECT_IFACE)
+ return await self._dbus_async_call(
+ proxy, INTROSPECT_METHOD, interface=INTROSPECT_IFACE
+ )
def _accept_method(self, method):
- """ Return True if we accept the method for a command
+ """Return True if we accept the method for a command
@param method: etree.Element
@return: True if the method is acceptable
@@ -181,9 +190,7 @@ class AdHocDBus:
for node in el.iterchildren("node", "interface"):
if node.tag == "node":
new_path = os.path.join(proxy.object_path, node.get("name"))
- new_proxy = await self.session_con.getRemoteObject(
- bus_name, new_path
- )
+ new_proxy = await self.session_con.getRemoteObject(bus_name, new_path)
await self._introspect(methods, bus_name, new_proxy)
elif node.tag == "interface":
name = node.get("name")
@@ -197,16 +204,40 @@ class AdHocDBus:
log.debug("method accepted: [%s]" % method_name)
methods.add((proxy.object_path, name, method_name))
- def _ad_hoc_dbus_add_auto(self, prog_name, allowed_jids, allowed_groups, allowed_magics,
- forbidden_jids, forbidden_groups, flags, profile_key):
+ def _ad_hoc_dbus_add_auto(
+ self,
+ prog_name,
+ allowed_jids,
+ allowed_groups,
+ allowed_magics,
+ forbidden_jids,
+ forbidden_groups,
+ flags,
+ profile_key,
+ ):
client = self.host.get_client(profile_key)
return self.ad_hoc_dbus_add_auto(
- client, prog_name, allowed_jids, allowed_groups, allowed_magics,
- forbidden_jids, forbidden_groups, flags)
+ client,
+ prog_name,
+ allowed_jids,
+ allowed_groups,
+ allowed_magics,
+ forbidden_jids,
+ forbidden_groups,
+ flags,
+ )
- async def ad_hoc_dbus_add_auto(self, client, prog_name, allowed_jids=None, allowed_groups=None,
- allowed_magics=None, forbidden_jids=None, forbidden_groups=None,
- flags=None):
+ async def ad_hoc_dbus_add_auto(
+ self,
+ client,
+ prog_name,
+ allowed_jids=None,
+ allowed_groups=None,
+ allowed_magics=None,
+ forbidden_jids=None,
+ forbidden_groups=None,
+ flags=None,
+ ):
bus_names = await self._dbus_list_names()
bus_names = [bus_name for bus_name in bus_names if "." + prog_name in bus_name]
if not bus_names:
@@ -241,9 +272,19 @@ class AdHocDBus:
return (str(bus_name), methods)
- def _add_command(self, client, adhoc_name, bus_name, methods, allowed_jids=None,
- allowed_groups=None, allowed_magics=None, forbidden_jids=None,
- forbidden_groups=None, flags=None):
+ def _add_command(
+ self,
+ client,
+ adhoc_name,
+ bus_name,
+ methods,
+ allowed_jids=None,
+ allowed_groups=None,
+ allowed_magics=None,
+ forbidden_jids=None,
+ forbidden_groups=None,
+ flags=None,
+ ):
if flags is None:
flags = set()
@@ -331,9 +372,16 @@ class AdHocDBus:
- device name
- device label
"""
- found_data = await defer.ensureDeferred(self.host.find_by_features(
- client, [self.host.ns_map['commands']], service=False, roster=False,
- own_jid=True, local_device=True))
+ found_data = await defer.ensureDeferred(
+ self.host.find_by_features(
+ client,
+ [self.host.ns_map["commands"]],
+ service=False,
+ roster=False,
+ own_jid=True,
+ local_device=True,
+ )
+ )
remotes = []
@@ -344,39 +392,47 @@ class AdHocDBus:
for cmd in cmd_list:
if cmd.nodeIdentifier == NS_MEDIA_PLAYER:
try:
- result_elt = await self._c.do(client, device_jid,
- NS_MEDIA_PLAYER, timeout=5)
+ result_elt = await self._c.do(
+ client, device_jid, NS_MEDIA_PLAYER, timeout=5
+ )
command_elt = self._c.get_command_elt(result_elt)
form = data_form.findForm(command_elt, NS_MEDIA_PLAYER)
if form is None:
continue
- mp_options = form.fields['media_player'].options
- session_id = command_elt.getAttribute('sessionid')
+ mp_options = form.fields["media_player"].options
+ session_id = command_elt.getAttribute("sessionid")
if mp_options and session_id:
# we just want to discover player, so we cancel the
# session
- self._c.do(client, device_jid, NS_MEDIA_PLAYER,
- action=self._c.ACTION.CANCEL,
- session_id=session_id)
+ self._c.do(
+ client,
+ device_jid,
+ NS_MEDIA_PLAYER,
+ action=self._c.ACTION.CANCEL,
+ session_id=session_id,
+ )
for opt in mp_options:
- remotes.append((device_jid_s,
- opt.value,
- opt.label or opt.value))
+ remotes.append(
+ (device_jid_s, opt.value, opt.label or opt.value)
+ )
except Exception as e:
- log.warning(_(
- "Can't retrieve remote controllers on {device_jid}: "
- "{reason}".format(device_jid=device_jid, reason=e)))
+ log.warning(
+ _(
+ "Can't retrieve remote controllers on {device_jid}: "
+ "{reason}".format(device_jid=device_jid, reason=e)
+ )
+ )
break
return remotes
async def do_mpris_command(self, proxy, command):
iface, command = command.rsplit(".", 1)
if command == CMD_GO_BACK:
- command = 'Seek'
+ command = "Seek"
args = [-SEEK_OFFSET]
elif command == CMD_GO_FWD:
- command = 'Seek'
+ command = "Seek"
args = [SEEK_OFFSET]
else:
args = []
@@ -387,9 +443,7 @@ class AdHocDBus:
for mpris_key, name in MPRIS_METADATA_MAP.items():
if mpris_key in metadata:
value = str(metadata[mpris_key])
- form.addField(data_form.Field(fieldType="fixed",
- var=name,
- value=value))
+ form.addField(data_form.Field(fieldType="fixed", var=name, value=value))
async def local_media_cb(self, client, command_elt, session_data, action, node):
assert txdbus is not None
@@ -408,10 +462,11 @@ class AdHocDBus:
return (None, self._c.STATUS.COMPLETED, None, note)
options = []
status = self._c.STATUS.EXECUTING
- form = data_form.Form("form", title=D_("Media Player Selection"),
- formNamespace=NS_MEDIA_PLAYER)
+ form = data_form.Form(
+ "form", title=D_("Media Player Selection"), formNamespace=NS_MEDIA_PLAYER
+ )
for bus in bus_names:
- player_name = bus[len(MPRIS_PREFIX)+1:]
+ player_name = bus[len(MPRIS_PREFIX) + 1 :]
if not player_name:
log.warning(_("Ignoring MPRIS bus without suffix"))
continue
@@ -430,9 +485,12 @@ class AdHocDBus:
raise ValueError(_("missing media_player value"))
if not bus_name.startswith(MPRIS_PREFIX):
- log.warning(_("Media player ad-hoc command trying to use non MPRIS bus. "
- "Hack attempt? Refused bus: {bus_name}").format(
- bus_name=bus_name))
+ log.warning(
+ _(
+ "Media player ad-hoc command trying to use non MPRIS bus. "
+ "Hack attempt? Refused bus: {bus_name}"
+ ).format(bus_name=bus_name)
+ )
note = (self._c.NOTE.ERROR, D_("Invalid player name."))
return (None, self._c.STATUS.COMPLETED, None, note)
@@ -453,29 +511,36 @@ class AdHocDBus:
# we construct the remote control form
form = data_form.Form("form", title=D_("Media Player Selection"))
- form.addField(data_form.Field(fieldType="hidden",
- var="media_player",
- value=bus_name))
+ form.addField(
+ data_form.Field(fieldType="hidden", var="media_player", value=bus_name)
+ )
for iface, properties_names in MPRIS_PROPERTIES.items():
for name in properties_names:
try:
value = await self._dbus_get_property(proxy, iface, name)
except Exception as e:
- log.warning(_("Can't retrieve attribute {name}: {reason}")
- .format(name=name, reason=e))
+ log.warning(
+ _("Can't retrieve attribute {name}: {reason}").format(
+ name=name, reason=e
+ )
+ )
continue
if name == MPRIS_METADATA_KEY:
self.add_mpris_metadata(form, value)
else:
- form.addField(data_form.Field(fieldType="fixed",
- var=name,
- value=str(value)))
+ form.addField(
+ data_form.Field(fieldType="fixed", var=name, value=str(value))
+ )
commands = [data_form.Option(c, c.rsplit(".", 1)[1]) for c in MPRIS_COMMANDS]
- form.addField(data_form.Field(fieldType="list-single",
- var="command",
- options=commands,
- required=True))
+ form.addField(
+ data_form.Field(
+ fieldType="list-single",
+ var="command",
+ options=commands,
+ required=True,
+ )
+ )
payload = form.toElement()
status = self._c.STATUS.EXECUTING
diff --git a/libervia/backend/plugins/plugin_app_manager_docker/__init__.py b/libervia/backend/plugins/plugin_app_manager_docker/__init__.py
index 088e5d60..f40cc745 100644
--- a/libervia/backend/plugins/plugin_app_manager_docker/__init__.py
+++ b/libervia/backend/plugins/plugin_app_manager_docker/__init__.py
@@ -36,8 +36,7 @@ PLUGIN_INFO = {
C.PI_DEPENDENCIES: ["APP_MANAGER"],
C.PI_MAIN: "AppManagerDocker",
C.PI_HANDLER: "no",
- C.PI_DESCRIPTION: _(
- """Applications Manager for Docker"""),
+ C.PI_DESCRIPTION: _("""Applications Manager for Docker"""),
}
@@ -48,18 +47,19 @@ class AppManagerDocker(AppManagerBackend):
def __init__(self, host):
log.info(_("Docker App Manager initialization"))
try:
- self.docker_compose_path = which('docker-compose')[0]
+ self.docker_compose_path = which("docker-compose")[0]
except IndexError:
raise exceptions.NotFound(
'"docker-compose" executable not found, Docker can\'t be used with '
- 'application manager')
+ "application manager"
+ )
super().__init__(host)
async def start(self, app_data: dict) -> None:
await self._am.start_common(app_data)
- working_dir = app_data['_instance_dir_path']
+ working_dir = app_data["_instance_dir_path"]
try:
- override = app_data['override']
+ override = app_data["override"]
except KeyError:
pass
else:
@@ -75,7 +75,7 @@ class AppManagerDocker(AppManagerBackend):
)
async def stop(self, app_data: dict) -> None:
- working_dir = app_data['_instance_dir_path']
+ working_dir = app_data["_instance_dir_path"]
await async_process.run(
self.docker_compose_path,
"down",
@@ -83,17 +83,18 @@ class AppManagerDocker(AppManagerBackend):
)
async def compute_expose(self, app_data: dict) -> None:
- working_dir = app_data['_instance_dir_path']
- expose = app_data['expose']
- ports = expose.get('ports', {})
+ working_dir = app_data["_instance_dir_path"]
+ expose = app_data["expose"]
+ ports = expose.get("ports", {})
for name, port_data in list(ports.items()):
try:
- service = port_data['service']
- private = port_data['private']
+ service = port_data["service"]
+ private = port_data["private"]
int(private)
except (KeyError, ValueError):
log.warning(
- f"invalid value found for {name!r} port in {app_data['_file_path']}")
+ f"invalid value found for {name!r} port in {app_data['_file_path']}"
+ )
continue
exposed_port = await async_process.run(
self.docker_compose_path,
@@ -104,11 +105,12 @@ class AppManagerDocker(AppManagerBackend):
)
exposed_port = exposed_port.decode().strip()
try:
- addr, port = exposed_port.split(':')
+ addr, port = exposed_port.split(":")
int(port)
except ValueError:
log.warning(
- f"invalid exposed port for {name}, ignoring: {exposed_port!r}")
+ f"invalid exposed port for {name}, ignoring: {exposed_port!r}"
+ )
del ports[name]
else:
ports[name] = exposed_port
diff --git a/libervia/backend/plugins/plugin_blog_import_dokuwiki.py b/libervia/backend/plugins/plugin_blog_import_dokuwiki.py
index 1c721a08..3d3e99b2 100644
--- a/libervia/backend/plugins/plugin_blog_import_dokuwiki.py
+++ b/libervia/backend/plugins/plugin_blog_import_dokuwiki.py
@@ -377,9 +377,9 @@ class DokuwikiImport(object):
try:
media_repo = options["media_repo"]
if opt_upload_images:
- options[
- self._blog_import.OPT_UPLOAD_IMAGES
- ] = False # force using --no-images-upload
+ options[self._blog_import.OPT_UPLOAD_IMAGES] = (
+ False # force using --no-images-upload
+ )
info_msg = _(
"DokuWiki media files will be *downloaded* to {temp_dir} - to finish the import you have to upload them *manually* to {media_repo}"
)
diff --git a/libervia/backend/plugins/plugin_comp_ap_gateway/__init__.py b/libervia/backend/plugins/plugin_comp_ap_gateway/__init__.py
index 1fd73900..f60f6d9b 100644
--- a/libervia/backend/plugins/plugin_comp_ap_gateway/__init__.py
+++ b/libervia/backend/plugins/plugin_comp_ap_gateway/__init__.py
@@ -87,7 +87,7 @@ from .constants import (
TYPE_REACTION,
TYPE_TOMBSTONE,
TYPE_JOIN,
- TYPE_LEAVE
+ TYPE_LEAVE,
)
from .http_server import HTTPServer
from .pubsub_service import APPubsubService
@@ -105,9 +105,23 @@ PLUGIN_INFO = {
C.PI_TYPE: C.PLUG_TYPE_ENTRY_POINT,
C.PI_PROTOCOLS: [],
C.PI_DEPENDENCIES: [
- "XEP-0050", "XEP-0054", "XEP-0060", "XEP-0084", "XEP-0106", "XEP-0277",
- "XEP-0292", "XEP-0329", "XEP-0372", "XEP-0424", "XEP-0465", "XEP-0470",
- "XEP-0447", "XEP-0471", "PUBSUB_CACHE", "TEXT_SYNTAXES", "IDENTITY"
+ "XEP-0050",
+ "XEP-0054",
+ "XEP-0060",
+ "XEP-0084",
+ "XEP-0106",
+ "XEP-0277",
+ "XEP-0292",
+ "XEP-0329",
+ "XEP-0372",
+ "XEP-0424",
+ "XEP-0465",
+ "XEP-0470",
+ "XEP-0447",
+ "XEP-0471",
+ "PUBSUB_CACHE",
+ "TEXT_SYNTAXES",
+ "IDENTITY",
],
C.PI_RECOMMENDATIONS: [],
C.PI_MAIN: "APGateway",
@@ -160,26 +174,19 @@ class APGateway:
# we want to be sure that the callbacks are launched before pubsub cache's
# one, as we need to inspect items before they are actually removed from cache
# or updated
- priority=1000
+ priority=1000,
)
self.pubsub_service = APPubsubService(self)
self.ad_hoc = APAdHocService(self)
self.ap_events = APEvents(self)
host.trigger.add_with_check(
- "message_received",
- self,
- self._message_received_trigger,
- priority=-1000
+ "message_received", self, self._message_received_trigger, priority=-1000
)
host.trigger.add_with_check(
- "XEP-0424_retract_received",
- self,
- self._on_message_retract
+ "XEP-0424_retract_received", self, self._on_message_retract
)
host.trigger.add_with_check(
- "XEP-0372_ref_received",
- self,
- self._on_reference_received
+ "XEP-0372_ref_received", self, self._on_reference_received
)
host.bridge.add_method(
@@ -215,7 +222,7 @@ class APGateway:
private_key_pem = self.private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
- encryption_algorithm=serialization.NoEncryption()
+ encryption_algorithm=serialization.NoEncryption(),
).decode()
await self.host.memory.storage.set_private_value(
IMPORT_NAME, "rsa_key", private_key_pem, profile=client.profile
@@ -228,16 +235,14 @@ class APGateway:
self.public_key = self.private_key.public_key()
self.public_key_pem = self.public_key.public_bytes(
encoding=serialization.Encoding.PEM,
- format=serialization.PublicFormat.SubjectPublicKeyInfo
+ format=serialization.PublicFormat.SubjectPublicKeyInfo,
).decode()
# params
# URL and port
self.public_url = self.host.memory.config_get(
CONF_SECTION, "public_url"
- ) or self.host.memory.config_get(
- CONF_SECTION, "xmpp_domain"
- )
+ ) or self.host.memory.config_get(CONF_SECTION, "xmpp_domain")
if self.public_url is None:
log.error(
'"public_url" not set in configuration, this is mandatory to have'
@@ -247,15 +252,15 @@ class APGateway:
return
if parse.urlparse(self.public_url).scheme:
log.error(
- "Scheme must not be specified in \"public_url\", please remove it from "
- "\"public_url\" configuration option. ActivityPub Gateway won't be run."
+ 'Scheme must not be specified in "public_url", please remove it from '
+ '"public_url" configuration option. ActivityPub Gateway won\'t be run.'
)
return
- self.http_port = int(self.host.memory.config_get(
- CONF_SECTION, 'http_port', 8123))
+ self.http_port = int(self.host.memory.config_get(CONF_SECTION, "http_port", 8123))
connection_type = self.host.memory.config_get(
- CONF_SECTION, 'http_connection_type', 'https')
- if connection_type not in ('http', 'https'):
+ CONF_SECTION, "http_connection_type", "https"
+ )
+ if connection_type not in ("http", "https"):
raise exceptions.ConfigError(
'bad ap-gateay http_connection_type, you must use one of "http" or '
'"https"'
@@ -263,18 +268,17 @@ class APGateway:
self.http_sign_get = C.bool(
self.host.memory.config_get(CONF_SECTION, "http_sign_get", C.BOOL_TRUE)
)
- self.max_items = int(self.host.memory.config_get(
- CONF_SECTION, 'new_node_max_items', 50
-
- ))
- self.comments_max_depth = int(self.host.memory.config_get(
- CONF_SECTION, 'comments_max_depth', 0
- ))
- self.ap_path = self.host.memory.config_get(CONF_SECTION, 'ap_path', '_ap')
+ self.max_items = int(
+ self.host.memory.config_get(CONF_SECTION, "new_node_max_items", 50)
+ )
+ self.comments_max_depth = int(
+ self.host.memory.config_get(CONF_SECTION, "comments_max_depth", 0)
+ )
+ self.ap_path = self.host.memory.config_get(CONF_SECTION, "ap_path", "_ap")
self.base_ap_url = parse.urljoin(f"https://{self.public_url}", f"{self.ap_path}/")
# True (default) if we provide gateway only to entities/services from our server
self.local_only = C.bool(
- self.host.memory.config_get(CONF_SECTION, 'local_only', C.BOOL_TRUE)
+ self.host.memory.config_get(CONF_SECTION, "local_only", C.BOOL_TRUE)
)
# if True (default), mention will be parsed in non-private content coming from
# XMPP. This is necessary as XEP-0372 are coming separately from item where the
@@ -285,7 +289,7 @@ class APGateway:
)
html_redirect: Dict[str, Union[str, dict]] = self.host.memory.config_get(
- CONF_SECTION, 'html_redirect_dict', {}
+ CONF_SECTION, "html_redirect_dict", {}
)
self.html_redirect: Dict[str, List[dict]] = {}
for url_type, target in html_redirect.items():
@@ -307,11 +311,10 @@ class APGateway:
# HTTP server launch
self.server = HTTPServer(self)
- if connection_type == 'http':
+ if connection_type == "http":
reactor.listenTCP(self.http_port, self.server)
else:
- options = tls.get_options_from_config(
- self.host.memory.config, CONF_SECTION)
+ options = tls.get_options_from_config(self.host.memory.config, CONF_SECTION)
tls.tls_options_check(options)
context_factory = tls.get_tls_context_factory(options)
reactor.listenSSL(self.http_port, self.server, context_factory)
@@ -320,8 +323,7 @@ class APGateway:
self.client = client
client.sendHistory = True
client._ap_storage = persistent.LazyPersistentBinaryDict(
- IMPORT_NAME,
- client.profile
+ IMPORT_NAME, client.profile
)
await self.init(client)
@@ -329,9 +331,7 @@ class APGateway:
self.ad_hoc.init(client)
async def _items_received(
- self,
- client: SatXMPPEntity,
- itemsEvent: pubsub.ItemsEvent
+ self, client: SatXMPPEntity, itemsEvent: pubsub.ItemsEvent
) -> None:
"""Callback called when pubsub items are received
@@ -355,19 +355,23 @@ class APGateway:
if self._pa.is_attachment_node(itemsEvent.nodeIdentifier):
await self.convert_and_post_attachments(
- client, ap_account, itemsEvent.sender, itemsEvent.nodeIdentifier,
- itemsEvent.items
+ client,
+ ap_account,
+ itemsEvent.sender,
+ itemsEvent.nodeIdentifier,
+ itemsEvent.items,
)
else:
await self.convert_and_post_items(
- client, ap_account, itemsEvent.sender, itemsEvent.nodeIdentifier,
- itemsEvent.items
+ client,
+ ap_account,
+ itemsEvent.sender,
+ itemsEvent.nodeIdentifier,
+ itemsEvent.items,
)
async def get_virtual_client(
- self,
- requestor_actor_id: str,
- actor_id: str
+ self, requestor_actor_id: str, actor_id: str
) -> SatXMPPEntity:
"""Get client for this component with a specified jid
@@ -413,8 +417,7 @@ class APGateway:
return await treq.json_content(resp)
except Exception as e:
raise error.StanzaError(
- "service-unavailable",
- text=f"Can't get AP data at {url}: {e}"
+ "service-unavailable", text=f"Can't get AP data at {url}: {e}"
)
async def ap_post(self, url: str, requestor_actor_id: str, doc: dict) -> TReqResponse:
@@ -429,11 +432,13 @@ class APGateway:
actor_account = actor_args[0]
to_log = [
"",
- f">>> {actor_account} is signing and posting to {url}:\n{pformat(doc)}"
+ f">>> {actor_account} is signing and posting to {url}:\n{pformat(doc)}",
]
body = json.dumps(doc).encode()
- headers = self._generate_signed_headers(url, requestor_actor_id, method="post", body=body)
+ headers = self._generate_signed_headers(
+ url, requestor_actor_id, method="post", body=body
+ )
headers["Content-Type"] = MEDIA_TYPE_AP
if self.verbose:
@@ -443,11 +448,7 @@ class APGateway:
to_log.append("---")
log.info("\n".join(to_log))
- resp = await treq.post(
- url,
- body,
- headers=headers
- )
+ resp = await treq.post(url, body, headers=headers)
if resp.code >= 300:
text = await resp.text()
log.warning(f"POST request to {url} failed [{resp.code}]: {text}")
@@ -456,11 +457,7 @@ class APGateway:
return resp
def _generate_signed_headers(
- self,
- url: str,
- actor_id: str,
- method: str,
- body: bytes|None = None
+ self, url: str, actor_id: str, method: str, body: bytes | None = None
) -> dict[str, str]:
"""Generate HTTP headers with signature for a given request
@@ -474,7 +471,7 @@ class APGateway:
headers = {
"(request-target)": f"{method} {p_url.path}",
"Host": p_url.hostname,
- "Date": http.datetimeToString().decode()
+ "Date": http.datetimeToString().decode(),
}
if body:
@@ -487,16 +484,14 @@ class APGateway:
@overload
async def ap_get_object(
self, requestor_actor_id: str, data: dict, key: str
- ) -> dict|None:
- ...
+ ) -> dict | None: ...
@overload
async def ap_get_object(
self, requestor_actor_id: str, data: Union[str, dict], key: None = None
- ) -> dict:
- ...
+ ) -> dict: ...
- async def ap_get_object(self, requestor_actor_id: str, data, key = None) -> dict|None:
+ async def ap_get_object(self, requestor_actor_id: str, data, key=None) -> dict | None:
"""Retrieve an AP object, dereferencing when necessary
This method is to be used with attributes marked as "Functional" in
@@ -526,10 +521,7 @@ class APGateway:
"was expecting a string or a dict, got {type(value)}: {value!r}}"
)
- async def ap_get_local_object(
- self,
- url: str
- ) -> dict:
+ async def ap_get_local_object(self, url: str) -> dict:
"""Retrieve or generate local object
for now, only handle XMPP items to convert to AP
@@ -596,11 +588,7 @@ class APGateway:
)
async def ap_get_list(
- self,
- requestor_actor_id: str,
- data: dict,
- key: str,
- only_ids: bool = False
+ self, requestor_actor_id: str, data: dict, key: str, only_ids: bool = False
) -> Optional[List[Dict[str, Any]]]:
"""Retrieve a list of objects from AP data, dereferencing when necessary
@@ -626,19 +614,12 @@ class APGateway:
if not isinstance(value, list):
raise ValueError(f"A list was expected, got {type(value)}: {value!r}")
if only_ids:
- return [
- {"id": v["id"]} if isinstance(v, dict) else {"id": v}
- for v in value
- ]
+ return [{"id": v["id"]} if isinstance(v, dict) else {"id": v} for v in value]
else:
return [await self.ap_get_object(requestor_actor_id, i) for i in value]
async def ap_get_actors(
- self,
- requestor_actor_id: str,
- data: dict,
- key: str,
- as_account: bool = True
+ self, requestor_actor_id: str, data: dict, key: str, as_account: bool = True
) -> List[str]:
"""Retrieve AP actors from data
@@ -671,9 +652,7 @@ class APGateway:
f"invalid actors list to object {data.get('id')!r}: {value!r}"
)
if not value:
- raise exceptions.DataError(
- f"list of actors is empty"
- )
+ raise exceptions.DataError(f"list of actors is empty")
if as_account:
return [
await self.get_ap_account_from_id(requestor_actor_id, actor_id)
@@ -697,12 +676,16 @@ class APGateway:
@raise exceptions.NotFound: no actor has been found in data
"""
try:
- actors = await self.ap_get_actors(requestor_actor_id, data, "actor", as_account=False)
+ actors = await self.ap_get_actors(
+ requestor_actor_id, data, "actor", as_account=False
+ )
except exceptions.DataError:
actors = None
if not actors:
try:
- actors = await self.ap_get_actors(requestor_actor_id, data, "attributedTo", as_account=False)
+ actors = await self.ap_get_actors(
+ requestor_actor_id, data, "attributedTo", as_account=False
+ )
except exceptions.DataError:
raise exceptions.NotFound(
'actor not specified in "actor" or "attributedTo"'
@@ -715,9 +698,7 @@ class APGateway:
def must_encode(self, text: str) -> bool:
"""Indicate if a text must be period encoded"""
return (
- not RE_ALLOWED_UNQUOTED.match(text)
- or text.startswith("___")
- or "---" in text
+ not RE_ALLOWED_UNQUOTED.match(text) or text.startswith("___") or "---" in text
)
def period_encode(self, text: str) -> str:
@@ -735,9 +716,7 @@ class APGateway:
)
async def get_ap_account_from_jid_and_node(
- self,
- jid_: jid.JID,
- node: Optional[str]
+ self, jid_: jid.JID, node: Optional[str]
) -> str:
"""Construct AP account from JID and node
@@ -778,26 +757,26 @@ class APGateway:
if node:
account_elts.extend((node, "---"))
- account_elts.extend((
- user, "@", jid_.host if is_local else self.client.jid.userhost()
- ))
+ account_elts.extend(
+ (user, "@", jid_.host if is_local else self.client.jid.userhost())
+ )
return "".join(account_elts)
def is_local(self, jid_: jid.JID) -> bool:
"""Returns True if jid_ use a domain or subdomain of gateway's host"""
local_host = self.client.host.split(".")
assert local_host
- return jid_.host.split(".")[-len(local_host):] == local_host
+ return jid_.host.split(".")[-len(local_host) :] == local_host
async def is_pubsub(self, jid_: jid.JID) -> bool:
"""Indicate if a JID is a Pubsub service"""
host_disco = await self.host.get_disco_infos(self.client, jid_)
- return (
- ("pubsub", "service") in host_disco.identities
- and not ("pubsub", "pep") in host_disco.identities
- )
+ return ("pubsub", "service") in host_disco.identities and not (
+ "pubsub",
+ "pep",
+ ) in host_disco.identities
- async def get_jid_and_node(self, ap_account: str) -> tuple[jid.JID, str|None]:
+ async def get_jid_and_node(self, ap_account: str) -> tuple[jid.JID, str | None]:
"""Decode raw AP account handle to get XMPP JID and Pubsub Node
Username are case insensitive.
@@ -856,13 +835,11 @@ class APGateway:
if encoded:
username = parse.unquote(
- RE_PERIOD_ENC.sub(r"%\g", username),
- errors="strict"
+ RE_PERIOD_ENC.sub(r"%\g", username), errors="strict"
)
if node:
node = parse.unquote(
- RE_PERIOD_ENC.sub(r"%\g", node),
- errors="strict"
+ RE_PERIOD_ENC.sub(r"%\g", node), errors="strict"
)
if "@" in username:
@@ -899,14 +876,7 @@ class APGateway:
The local jid is computer by escaping AP actor handle and using it as local part
of JID, where domain part is this gateway own JID
"""
- return jid.JID(
- None,
- (
- self._e.escape(account),
- self.client.jid.host,
- None
- )
- )
+ return jid.JID(None, (self._e.escape(account), self.client.jid.host, None))
async def get_jid_from_id(self, requestor_actor_id: str, actor_id: str) -> jid.JID:
"""Compute JID linking to an AP Actor ID
@@ -937,10 +907,10 @@ class APGateway:
@return: endpoint type and extra arguments
"""
path = parse.urlparse(url).path.lstrip("/")
- type_, *extra_args = path[len(self.ap_path):].lstrip("/").split("/")
+ type_, *extra_args = path[len(self.ap_path) :].lstrip("/").split("/")
return type_, [parse.unquote(a) for a in extra_args]
- def build_apurl(self, type_:str , *args: str) -> str:
+ def build_apurl(self, type_: str, *args: str) -> str:
"""Build an AP endpoint URL
@param type_: type of AP endpoing
@@ -948,7 +918,7 @@ class APGateway:
"""
return parse.urljoin(
self.base_ap_url,
- str(Path(type_).joinpath(*(parse.quote_plus(a, safe="@") for a in args)))
+ str(Path(type_).joinpath(*(parse.quote_plus(a, safe="@") for a in args))),
)
def is_local_url(self, url: str) -> bool:
@@ -994,9 +964,7 @@ class APGateway:
@async_lru(maxsize=LRU_MAX_SIZE)
async def get_actor_pub_key_data(
- self,
- requestor_actor_id: str,
- actor_id: str
+ self, requestor_actor_id: str, actor_id: str
) -> Tuple[str, str, rsa.RSAPublicKey]:
"""Retrieve Public Key data from actor ID
@@ -1067,7 +1035,7 @@ class APGateway:
requestor_actor_id: str,
signature: str,
key_id: str,
- headers: Dict[str, str]
+ headers: Dict[str, str],
) -> str:
"""Verify that signature matches given headers
@@ -1081,7 +1049,7 @@ class APGateway:
@raise InvalidSignature: signature doesn't match headers
"""
- to_sign = "\n".join(f"{k.lower()}: {v}" for k,v in headers.items())
+ to_sign = "\n".join(f"{k.lower()}: {v}" for k, v in headers.items())
if key_id.startswith("acct:"):
actor = key_id[5:]
actor_id = await self.get_ap_actor_id_from_account(actor)
@@ -1089,8 +1057,7 @@ class APGateway:
actor_id = key_id.split("#", 1)[0]
pub_key_id, pub_key_owner, pub_key = await self.get_actor_pub_key_data(
- requestor_actor_id,
- actor_id
+ requestor_actor_id, actor_id
)
if pub_key_id != key_id or pub_key_owner != actor_id:
raise exceptions.EncryptionError("Public Key mismatch")
@@ -1101,7 +1068,7 @@ class APGateway:
to_sign.encode(),
# we have to use PKCS1v15 padding to be compatible with Mastodon
padding.PKCS1v15(), # type: ignore
- hashes.SHA256() # type: ignore
+ hashes.SHA256(), # type: ignore
)
except InvalidSignature:
raise exceptions.EncryptionError(
@@ -1111,9 +1078,7 @@ class APGateway:
return actor_id
def get_signature_data(
- self,
- key_id: str,
- headers: Dict[str, str]
+ self, key_id: str, headers: Dict[str, str]
) -> Tuple[Dict[str, str], Dict[str, str]]:
"""Generate and return signature and corresponding headers
@@ -1130,20 +1095,22 @@ class APGateway:
"""
# headers must be lower case
l_headers: Dict[str, str] = {k.lower(): v for k, v in headers.items()}
- to_sign = "\n".join(f"{k}: {v}" for k,v in l_headers.items())
- signature = base64.b64encode(self.private_key.sign(
- to_sign.encode(),
- # we have to use PKCS1v15 padding to be compatible with Mastodon
- padding.PKCS1v15(), # type: ignore
- hashes.SHA256() # type: ignore
- )).decode()
+ to_sign = "\n".join(f"{k}: {v}" for k, v in l_headers.items())
+ signature = base64.b64encode(
+ self.private_key.sign(
+ to_sign.encode(),
+ # we have to use PKCS1v15 padding to be compatible with Mastodon
+ padding.PKCS1v15(), # type: ignore
+ hashes.SHA256(), # type: ignore
+ )
+ ).decode()
sign_data = {
"keyId": key_id,
"Algorithm": "rsa-sha256",
"headers": " ".join(l_headers.keys()),
- "signature": signature
+ "signature": signature,
}
- new_headers = {k: v for k,v in headers.items() if not k.startswith("(")}
+ new_headers = {k: v for k, v in headers.items() if not k.startswith("(")}
new_headers["Signature"] = self.build_signature_header(sign_data)
return new_headers, sign_data
@@ -1167,18 +1134,19 @@ class APGateway:
"""
actor_id = await self.get_ap_actor_id_from_account(ap_account)
requestor_actor_id = self.build_apurl(
- TYPE_ACTOR,
- await self.get_ap_account_from_jid_and_node(service, node)
+ TYPE_ACTOR, await self.get_ap_account_from_jid_and_node(service, node)
)
inbox = await self.get_ap_inbox_from_id(requestor_actor_id, actor_id)
for item in items:
if item.name == "item":
- cached_item = await self.host.memory.storage.search_pubsub_items({
- "profiles": [self.client.profile],
- "services": [service],
- "nodes": [node],
- "names": [item["id"]]
- })
+ cached_item = await self.host.memory.storage.search_pubsub_items(
+ {
+ "profiles": [self.client.profile],
+ "services": [service],
+ "nodes": [node],
+ "names": [item["id"]],
+ }
+ )
is_new = not bool(cached_item)
if node.startswith(self._events.namespace):
# event item
@@ -1259,7 +1227,7 @@ class APGateway:
service: jid.JID,
node: str,
items: List[domish.Element],
- publisher: Optional[jid.JID] = None
+ publisher: Optional[jid.JID] = None,
) -> None:
"""Convert XMPP item attachments to AP activities and post them to actor inbox
@@ -1284,8 +1252,7 @@ class APGateway:
actor_id = await self.get_ap_actor_id_from_account(ap_account)
requestor_actor_id = self.build_apurl(
- TYPE_ACTOR,
- await self.get_ap_account_from_jid_and_node(service, node)
+ TYPE_ACTOR, await self.get_ap_account_from_jid_and_node(service, node)
)
inbox = await self.get_ap_inbox_from_id(requestor_actor_id, actor_id)
@@ -1327,12 +1294,14 @@ class APGateway:
else:
item_url = self.build_apurl(TYPE_ITEM, item_account, item_id)
- old_attachment_pubsub_items = await self.host.memory.storage.search_pubsub_items({
- "profiles": [self.client.profile],
- "services": [service],
- "nodes": [node],
- "names": [item_elt["id"]]
- })
+ old_attachment_pubsub_items = await self.host.memory.storage.search_pubsub_items(
+ {
+ "profiles": [self.client.profile],
+ "services": [service],
+ "nodes": [node],
+ "names": [item_elt["id"]],
+ }
+ )
if not old_attachment_pubsub_items:
old_attachment = {}
else:
@@ -1345,10 +1314,7 @@ class APGateway:
except IndexError:
# no known element was present in attachments
old_attachment = {}
- publisher_account = await self.get_ap_account_from_jid_and_node(
- publisher,
- None
- )
+ publisher_account = await self.get_ap_account_from_jid_and_node(publisher, None)
publisher_actor_id = self.build_apurl(TYPE_ACTOR, publisher_account)
try:
attachments = self._pa.items_2_attachment_data(client, [item_elt])[0]
@@ -1390,8 +1356,7 @@ class APGateway:
"reaction", item_account, item_id, reaction.encode().hex()
)
reaction_activity = self.create_activity(
- TYPE_REACTION, publisher_actor_id, item_url,
- activity_id=activity_id
+ TYPE_REACTION, publisher_actor_id, item_url, activity_id=activity_id
)
reaction_activity["content"] = reaction
reaction_activity["to"] = [ap_account]
@@ -1410,7 +1375,9 @@ class APGateway:
old_attending = old_attachment.get("rsvp", {}).get("attending", "no")
if attending != old_attending:
activity_type = TYPE_JOIN if attending == "yes" else TYPE_LEAVE
- activity_id = self.build_apurl(activity_type.lower(), item_account, item_id)
+ activity_id = self.build_apurl(
+ activity_type.lower(), item_account, item_id
+ )
activity = self.create_activity(
activity_type, publisher_actor_id, item_url, activity_id=activity_id
)
@@ -1421,7 +1388,9 @@ class APGateway:
if "rsvp" in old_attachment:
old_attending = old_attachment.get("rsvp", {}).get("attending", "no")
if old_attending == "yes":
- activity_id = self.build_apurl(TYPE_LEAVE.lower(), item_account, item_id)
+ activity_id = self.build_apurl(
+ TYPE_LEAVE.lower(), item_account, item_id
+ )
activity = self.create_activity(
TYPE_LEAVE, publisher_actor_id, item_url, activity_id=activity_id
)
@@ -1436,14 +1405,11 @@ class APGateway:
client, service, node, with_subscriptions=True, create=True
)
await self.host.memory.storage.cache_pubsub_items(
- self.client,
- cached_node,
- [item_elt],
- [attachments]
+ self.client, cached_node, [item_elt], [attachments]
)
def _publish_message(self, mess_data_s: str, service_s: str, profile: str):
- mess_data: dict = data_format.deserialise(mess_data_s) # type: ignore
+ mess_data: dict = data_format.deserialise(mess_data_s) # type: ignore
service = jid.JID(service_s)
client = self.host.get_client(profile)
return defer.ensureDeferred(self.publish_message(client, mess_data, service))
@@ -1463,10 +1429,12 @@ class APGateway:
raise ValueError(f"Invalid account: {account!r}")
host = account.split("@")[1]
try:
- finger_data = await treq.json_content(await treq.get(
- f"https://{host}/.well-known/webfinger?"
- f"resource=acct:{parse.quote_plus(account)}",
- ))
+ finger_data = await treq.json_content(
+ await treq.get(
+ f"https://{host}/.well-known/webfinger?"
+ f"resource=acct:{parse.quote_plus(account)}",
+ )
+ )
except Exception as e:
raise exceptions.DataError(f"Can't get webfinger data for {account!r}: {e}")
for link in finger_data.get("links", []):
@@ -1481,15 +1449,11 @@ class APGateway:
)
break
else:
- raise ValueError(
- f"No ActivityPub link found for {account!r}"
- )
+ raise ValueError(f"No ActivityPub link found for {account!r}")
return href
async def get_ap_actor_data_from_account(
- self,
- requestor_actor_id: str,
- account: str
+ self, requestor_actor_id: str, account: str
) -> dict:
"""Retrieve ActivityPub Actor data
@@ -1499,10 +1463,7 @@ class APGateway:
return await self.ap_get(href, requestor_actor_id)
async def get_ap_inbox_from_id(
- self,
- requestor_actor_id: str,
- actor_id: str,
- use_shared: bool = True
+ self, requestor_actor_id: str, actor_id: str, use_shared: bool = True
) -> str:
"""Retrieve inbox of an actor_id
@@ -1531,16 +1492,12 @@ class APGateway:
if self.is_local_url(actor_id):
url_type, url_args = self.parse_apurl(actor_id)
if url_type != "actor" or not url_args:
- raise exceptions.DataError(
- f"invalid local actor ID: {actor_id}"
- )
+ raise exceptions.DataError(f"invalid local actor ID: {actor_id}")
account = url_args[0]
try:
- account_user, account_host = account.split('@')
+ account_user, account_host = account.split("@")
except ValueError:
- raise exceptions.DataError(
- f"invalid account from url: {actor_id}"
- )
+ raise exceptions.DataError(f"invalid account from url: {actor_id}")
if not account_user or account_host != self.public_url:
raise exceptions.DataError(
f"{account!r} is not a valid local account (from {actor_id})"
@@ -1636,7 +1593,7 @@ class APGateway:
raise error.StanzaError(
"feature-not-implemented",
text="Maximum limit for previous_index has been reached, this limit"
- "is set to avoid DoS"
+ "is set to avoid DoS",
)
else:
# we'll convert "start_index" to "after_id", thus we need the item just
@@ -1665,7 +1622,7 @@ class APGateway:
raise error.StanzaError(
"service-unavailable",
"Error while retrieving previous page from AP service at "
- f"{current_page}"
+ f"{current_page}",
)
init_page = "last" if chronological_pagination else "first"
@@ -1698,10 +1655,11 @@ class APGateway:
found_after_id = True
if chronological_pagination:
start_index = retrieved_items - len(page_items) + limit_idx + 1
- page_items = page_items[limit_idx+1:]
+ page_items = page_items[limit_idx + 1 :]
else:
- start_index = count - (retrieved_items - len(page_items) +
- limit_idx + 1)
+ start_index = count - (
+ retrieved_items - len(page_items) + limit_idx + 1
+ )
page_items = page_items[:limit_idx]
items.extend(page_items)
else:
@@ -1730,14 +1688,13 @@ class APGateway:
rsm_resp["index"] = 0
else:
rsm_resp["index"] = count - len(items)
- rsm_resp.update({
- "first": items[0]["id"],
- "last": items[-1]["id"]
- })
+ rsm_resp.update({"first": items[0]["id"], "last": items[-1]["id"]})
return items, rsm.RSMResponse(**rsm_resp)
- async def ap_item_2_mb_data_and_elt(self, requestor_actor_id: str, ap_item: dict) -> tuple[dict, domish.Element]:
+ async def ap_item_2_mb_data_and_elt(
+ self, requestor_actor_id: str, ap_item: dict
+ ) -> tuple[dict, domish.Element]:
"""Convert AP item to parsed microblog data and corresponding item element
@param requestor_actor_id: ID of the actor requesting the conversion.
@@ -1754,7 +1711,9 @@ class APGateway:
item_elt["publisher"] = mb_data["author_jid"]
return mb_data, item_elt
- async def ap_item_2_mb_elt(self, requestor_actor_id: str, ap_item: dict) -> domish.Element:
+ async def ap_item_2_mb_elt(
+ self, requestor_actor_id: str, ap_item: dict
+ ) -> domish.Element:
"""Convert AP item to XMPP item element
@param requestor_actor_id: ID of the actor requesting the conversion.
@@ -1769,7 +1728,7 @@ class APGateway:
requestor_actor_id: str,
page: Union[str, dict],
parser: Callable[[str, dict], Awaitable[domish.Element]],
- only_ids: bool = False
+ only_ids: bool = False,
) -> Tuple[dict, List[domish.Element]]:
"""Convert AP objects from an AP page to XMPP items
@@ -1781,18 +1740,20 @@ class APGateway:
"""
page_data = await self.ap_get_object(requestor_actor_id, page)
if page_data is None:
- log.warning('No data found in collection')
+ log.warning("No data found in collection")
return {}, []
- ap_items = await self.ap_get_list(requestor_actor_id, page_data, "orderedItems", only_ids=only_ids)
+ ap_items = await self.ap_get_list(
+ requestor_actor_id, page_data, "orderedItems", only_ids=only_ids
+ )
if ap_items is None:
- ap_items = await self.ap_get_list(requestor_actor_id, page_data, "items", only_ids=only_ids)
+ ap_items = await self.ap_get_list(
+ requestor_actor_id, page_data, "items", only_ids=only_ids
+ )
if not ap_items:
- log.warning(f'No item field found in collection: {page_data!r}')
+ log.warning(f"No item field found in collection: {page_data!r}")
return page_data, []
else:
- log.warning(
- "Items are not ordered, this is not spec compliant"
- )
+ log.warning("Items are not ordered, this is not spec compliant")
items = []
# AP Collections are in antichronological order, but we expect chronological in
# Pubsub, thus we reverse it
@@ -1805,10 +1766,7 @@ class APGateway:
return page_data, items
async def get_comments_nodes(
- self,
- requestor_actor_id: str,
- item_id: str,
- parent_id: Optional[str]
+ self, requestor_actor_id: str, item_id: str, parent_id: Optional[str]
) -> Tuple[Optional[str], Optional[str]]:
"""Get node where this item is and node to use for comments
@@ -1827,7 +1785,7 @@ class APGateway:
if parent_id is None or not self.comments_max_depth:
return (
self._m.get_comments_node(parent_id) if parent_id is not None else None,
- self._m.get_comments_node(item_id)
+ self._m.get_comments_node(item_id),
)
parent_url = parent_id
parents = []
@@ -1837,18 +1795,15 @@ class APGateway:
parent_url = parent_item.get("inReplyTo")
if parent_url is None:
break
- parent_limit = self.comments_max_depth-1
+ parent_limit = self.comments_max_depth - 1
if len(parents) <= parent_limit:
return (
self._m.get_comments_node(parents[-1]["id"]),
- self._m.get_comments_node(item_id)
+ self._m.get_comments_node(item_id),
)
else:
last_level_item = parents[parent_limit]
- return (
- self._m.get_comments_node(last_level_item["id"]),
- None
- )
+ return (self._m.get_comments_node(last_level_item["id"]), None)
async def ap_item_2_mb_data(self, requestor_actor_id: str, ap_item: dict) -> dict:
"""Convert AP activity or object to microblog data
@@ -1925,7 +1880,9 @@ class APGateway:
if is_activity:
authors = await self.ap_get_actors(requestor_actor_id, ap_item, "actor")
else:
- authors = await self.ap_get_actors(requestor_actor_id, ap_object, "attributedTo")
+ authors = await self.ap_get_actors(
+ requestor_actor_id, ap_object, "attributedTo"
+ )
if len(authors) > 1:
# we only keep first item as author
# TODO: handle multiple actors
@@ -1963,22 +1920,14 @@ class APGateway:
comments_data = {
"service": author_jid,
"node": comments_node,
- "uri": uri.build_xmpp_uri(
- "pubsub",
- path=author_jid,
- node=comments_node
- )
+ "uri": uri.build_xmpp_uri("pubsub", path=author_jid, node=comments_node),
}
mb_data["comments"] = [comments_data]
return mb_data
async def get_reply_to_id_from_xmpp_node(
- self,
- client: SatXMPPEntity,
- ap_account: str,
- parent_item: str,
- mb_data: dict
+ self, client: SatXMPPEntity, ap_account: str, parent_item: str, mb_data: dict
) -> str:
"""Get URL to use for ``inReplyTo`` field in AP item.
@@ -1995,18 +1944,16 @@ class APGateway:
"""
# FIXME: propose a protoXEP to properly get parent item, node and service
- found_items = await self.host.memory.storage.search_pubsub_items({
- "profiles": [client.profile],
- "names": [parent_item]
- })
+ found_items = await self.host.memory.storage.search_pubsub_items(
+ {"profiles": [client.profile], "names": [parent_item]}
+ )
if not found_items:
log.warning(f"parent item {parent_item!r} not found in cache")
parent_ap_account = ap_account
elif len(found_items) == 1:
cached_node = found_items[0].node
parent_ap_account = await self.get_ap_account_from_jid_and_node(
- cached_node.service,
- cached_node.name
+ cached_node.service, cached_node.name
)
else:
# we found several cached item with given ID, we check if there is one
@@ -2014,32 +1961,25 @@ class APGateway:
try:
author = jid.JID(mb_data["author_jid"]).userhostJID()
cached_item = next(
- i for i in found_items
- if jid.JID(i.data["publisher"]).userhostJID()
- == author
+ i
+ for i in found_items
+ if jid.JID(i.data["publisher"]).userhostJID() == author
)
except StopIteration:
# no item corresponding to this author, we use ap_account
log.warning(
- "Can't find a single cached item for parent item "
- f"{parent_item!r}"
+ "Can't find a single cached item for parent item " f"{parent_item!r}"
)
parent_ap_account = ap_account
else:
cached_node = cached_item.node
parent_ap_account = await self.get_ap_account_from_jid_and_node(
- cached_node.service,
- cached_node.name
+ cached_node.service, cached_node.name
)
- return self.build_apurl(
- TYPE_ITEM, parent_ap_account, parent_item
- )
+ return self.build_apurl(TYPE_ITEM, parent_ap_account, parent_item)
- async def repeated_mb_2_ap_item(
- self,
- mb_data: dict
- ) -> dict:
+ async def repeated_mb_2_ap_item(self, mb_data: dict) -> dict:
"""Convert repeated blog item to suitable AP Announce activity
@param mb_data: microblog metadata of an item repeating an other blog post
@@ -2047,10 +1987,7 @@ class APGateway:
"""
repeated = mb_data["extra"]["repeated"]
repeater = jid.JID(repeated["by"])
- repeater_account = await self.get_ap_account_from_jid_and_node(
- repeater,
- None
- )
+ repeater_account = await self.get_ap_account_from_jid_and_node(repeater, None)
repeater_id = self.build_apurl(TYPE_ACTOR, repeater_account)
repeated_uri = repeated["uri"]
@@ -2095,7 +2032,7 @@ class APGateway:
announce["to"] = [NS_AP_PUBLIC]
announce["cc"] = [
self.build_apurl(TYPE_FOLLOWERS, repeater_account),
- await self.get_ap_actor_id_from_account(repeated_account)
+ await self.get_ap_actor_id_from_account(repeated_account),
]
return announce
@@ -2103,7 +2040,7 @@ class APGateway:
self,
client: SatXMPPEntity,
mb_data: dict,
- public: bool =True,
+ public: bool = True,
is_new: bool = True,
) -> dict:
"""Convert Libervia Microblog Data to ActivityPub item
@@ -2131,8 +2068,7 @@ class APGateway:
if not mb_data.get("author_jid"):
mb_data["author_jid"] = client.jid.userhost()
ap_account = await self.get_ap_account_from_jid_and_node(
- jid.JID(mb_data["author_jid"]),
- None
+ jid.JID(mb_data["author_jid"]), None
)
url_actor = self.build_apurl(TYPE_ACTOR, ap_account)
url_item = self.build_apurl(TYPE_ITEM, ap_account, mb_data["id"])
@@ -2153,17 +2089,11 @@ class APGateway:
ap_attachments = ap_object["attachment"] = []
for attachment in attachments:
try:
- url = next(
- s['url'] for s in attachment["sources"] if 'url' in s
- )
+ url = next(s["url"] for s in attachment["sources"] if "url" in s)
except (StopIteration, KeyError):
- log.warning(
- f"Ignoring attachment without URL: {attachment}"
- )
+ log.warning(f"Ignoring attachment without URL: {attachment}")
continue
- ap_attachment = {
- "url": url
- }
+ ap_attachment = {"url": url}
for key, ap_key in (
("media_type", "mediaType"),
# XXX: yes "name", cf. [ap_item_2_mb_data]
@@ -2191,19 +2121,19 @@ class APGateway:
log.warning(f"Can't add mention to {mentioned!r}: {e}")
else:
ap_object["to"].append(mentioned_id)
- ap_object.setdefault("tag", []).append({
- "type": TYPE_MENTION,
- "href": mentioned_id,
- "name": mention,
- })
+ ap_object.setdefault("tag", []).append(
+ {
+ "type": TYPE_MENTION,
+ "href": mentioned_id,
+ "name": mention,
+ }
+ )
try:
node = mb_data["node"]
service = jid.JID(mb_data["service"])
except KeyError:
# node and service must always be specified when this method is used
- raise exceptions.InternalError(
- "node or service is missing in mb_data"
- )
+ raise exceptions.InternalError("node or service is missing in mb_data")
try:
target_ap_account = await self.get_ap_account_from_jid_and_node(
service, node
@@ -2226,8 +2156,7 @@ class APGateway:
if self.is_virtual_jid(service):
# service is a proxy JID for AP account
actor_data = await self.get_ap_actor_data_from_account(
- url_actor,
- target_ap_account
+ url_actor, target_ap_account
)
followers = actor_data.get("followers")
else:
@@ -2244,10 +2173,7 @@ class APGateway:
else:
# the publication is from a followed real XMPP node
ap_object["inReplyTo"] = await self.get_reply_to_id_from_xmpp_node(
- client,
- ap_account,
- parent_item,
- mb_data
+ client, ap_account, parent_item, mb_data
)
return self.create_activity(
@@ -2255,10 +2181,7 @@ class APGateway:
)
async def publish_message(
- self,
- client: SatXMPPEntity,
- mess_data: dict,
- service: jid.JID
+ self, client: SatXMPPEntity, mess_data: dict, service: jid.JID
) -> None:
"""Send an AP message
@@ -2292,11 +2215,7 @@ class APGateway:
await self.ap_post(inbox_url, url_actor, item_data)
async def ap_delete_item(
- self,
- jid_: jid.JID,
- node: Optional[str],
- item_id: str,
- public: bool = True
+ self, jid_: jid.JID, node: Optional[str], item_id: str, public: bool = True
) -> Tuple[str, Dict[str, Any]]:
"""Build activity to delete an AP item
@@ -2314,11 +2233,9 @@ class APGateway:
author_account = await self.get_ap_account_from_jid_and_node(jid_, node)
author_actor_id = self.build_apurl(TYPE_ACTOR, author_account)
- items = await self.host.memory.storage.search_pubsub_items({
- "profiles": [self.client.profile],
- "services": [jid_],
- "names": [item_id]
- })
+ items = await self.host.memory.storage.search_pubsub_items(
+ {"profiles": [self.client.profile], "services": [jid_], "names": [item_id]}
+ )
if not items:
log.warning(
f"Deleting an unknown item at service {jid_}, node {node} and id "
@@ -2326,7 +2243,9 @@ class APGateway:
)
else:
try:
- mb_data = await self._m.item_2_mb_data(self.client, items[0].data, jid_, node)
+ mb_data = await self._m.item_2_mb_data(
+ self.client, items[0].data, jid_, node
+ )
if "repeated" in mb_data["extra"]:
# we are deleting a repeated item, we must translate this to an
# "Undo" of the "Announce" activity instead of a "Delete" one
@@ -2341,12 +2260,7 @@ class APGateway:
url_item = self.build_apurl(TYPE_ITEM, author_account, item_id)
ap_item = self.create_activity(
- "Delete",
- author_actor_id,
- {
- "id": url_item,
- "type": TYPE_TOMBSTONE
- }
+ "Delete", author_actor_id, {"id": url_item, "type": TYPE_TOMBSTONE}
)
if public:
ap_item["to"] = [NS_AP_PUBLIC]
@@ -2356,7 +2270,7 @@ class APGateway:
self,
client: SatXMPPEntity,
message_elt: domish.Element,
- post_treat: defer.Deferred
+ post_treat: defer.Deferred,
) -> bool:
"""add the gateway workflow on post treatment"""
if self.client is None:
@@ -2381,9 +2295,7 @@ class APGateway:
log.warning(f"ignoring non local message: {mess_data}")
return mess_data
if not mess_data["to"].user:
- log.warning(
- f"ignoring message addressed to gateway itself: {mess_data}"
- )
+ log.warning(f"ignoring message addressed to gateway itself: {mess_data}")
return mess_data
requestor_actor_id = self.build_apurl(TYPE_ACTOR, mess_data["from"].userhost())
@@ -2391,9 +2303,7 @@ class APGateway:
try:
actor_id = await self.get_ap_actor_id_from_account(actor_account)
except Exception as e:
- log.warning(
- f"Can't retrieve data on actor {actor_account}: {e}"
- )
+ log.warning(f"Can't retrieve data on actor {actor_account}: {e}")
# TODO: send an error
return mess_data
inbox = await self.get_ap_inbox_from_id(
@@ -2417,9 +2327,7 @@ class APGateway:
mb_data["id"] = origin_id
attachments = mess_data["extra"].get(C.KEY_ATTACHMENTS)
if attachments:
- mb_data["extra"] = {
- C.KEY_ATTACHMENTS: attachments
- }
+ mb_data["extra"] = {C.KEY_ATTACHMENTS: attachments}
client = self.client.get_virtual_client(mess_data["from"])
ap_item = await self.mb_data_2_ap_item(client, mb_data, public=False)
@@ -2427,19 +2335,19 @@ class APGateway:
ap_object["to"] = ap_item["to"] = [actor_id]
# we add a mention to direct message, otherwise peer is not notified in some AP
# implementations (notably Mastodon), and the message may be missed easily.
- ap_object.setdefault("tag", []).append({
- "type": TYPE_MENTION,
- "href": actor_id,
- "name": f"@{actor_account}",
- })
+ ap_object.setdefault("tag", []).append(
+ {
+ "type": TYPE_MENTION,
+ "href": actor_id,
+ "name": f"@{actor_account}",
+ }
+ )
try:
await self.ap_post(inbox, ap_item["actor"], ap_item)
except Exception as e:
# TODO: send an error
- log.warning(
- f"Can't send message to {inbox}: {e}"
- )
+ log.warning(f"Can't send message to {inbox}: {e}")
return mess_data
async def _on_message_retract(
@@ -2447,29 +2355,24 @@ class APGateway:
client: SatXMPPEntity,
message_elt: domish.Element,
retract_elt: domish.Element,
- history: History
+ history: History,
) -> bool:
if client != self.client:
return True
from_jid = jid.JID(message_elt["from"])
if not self.is_local(from_jid):
- log.debug(
- f"ignoring retract request from non local jid {from_jid}"
- )
+ log.debug(f"ignoring retract request from non local jid {from_jid}")
return False
- requestor_actor_id = self.build_apurl(
- TYPE_ACTOR,
- from_jid.userhost()
- )
+ requestor_actor_id = self.build_apurl(TYPE_ACTOR, from_jid.userhost())
to_jid = jid.JID(message_elt["to"])
- if (to_jid.host != self.client.jid.full() or not to_jid.user):
+ if to_jid.host != self.client.jid.full() or not to_jid.user:
# to_jid should be a virtual JID from this gateway
- raise exceptions.InternalError(
- f"Invalid destinee's JID: {to_jid.full()}"
- )
+ raise exceptions.InternalError(f"Invalid destinee's JID: {to_jid.full()}")
ap_account = self._e.unescape(to_jid.user)
actor_id = await self.get_ap_actor_id_from_account(ap_account)
- inbox = await self.get_ap_inbox_from_id(requestor_actor_id, actor_id, use_shared=False)
+ inbox = await self.get_ap_inbox_from_id(
+ requestor_actor_id, actor_id, use_shared=False
+ )
url_actor, ap_item = await self.ap_delete_item(
from_jid.userhostJID(), None, retract_elt["id"], public=False
)
@@ -2480,7 +2383,7 @@ class APGateway:
self,
client: SatXMPPEntity,
message_elt: domish.Element,
- reference_data: Dict[str, Union[str, int]]
+ reference_data: Dict[str, Union[str, int]],
) -> bool:
parsed_uri: dict = reference_data.get("parsed_uri")
if not parsed_uri:
@@ -2553,14 +2456,18 @@ class APGateway:
ap_item = await self.mb_data_2_ap_item(client, mb_data)
ap_object = ap_item["object"]
ap_object["to"] = [actor_id]
- ap_object.setdefault("tag", []).append({
- "type": TYPE_MENTION,
- "href": actor_id,
- "name": ap_account,
- })
+ ap_object.setdefault("tag", []).append(
+ {
+ "type": TYPE_MENTION,
+ "href": actor_id,
+ "name": ap_account,
+ }
+ )
requestor_actor_id = ap_item["actor"]
- inbox = await self.get_ap_inbox_from_id(requestor_actor_id, actor_id, use_shared=False)
+ inbox = await self.get_ap_inbox_from_id(
+ requestor_actor_id, actor_id, use_shared=False
+ )
await self.ap_post(inbox, requestor_actor_id, ap_item)
@@ -2583,7 +2490,7 @@ class APGateway:
)
return
try:
- parent_item_account, parent_item_id = url_args[0], '/'.join(url_args[1:])
+ parent_item_account, parent_item_id = url_args[0], "/".join(url_args[1:])
except (IndexError, ValueError):
log.warning(
"Ignoring AP item replying to an XMPP item with invalid inReplyTo URL "
@@ -2603,7 +2510,8 @@ class APGateway:
except IndexError:
log.warning(
f"Can't find parent item at {parent_item_service} (node "
- f"{parent_item_node!r})\n{pformat(ap_item)}")
+ f"{parent_item_node!r})\n{pformat(ap_item)}"
+ )
return
parent_item_parsed = await self._m.item_2_mb_data(
client, parent_item_elt, parent_item_service, parent_item_node
@@ -2614,16 +2522,18 @@ class APGateway:
except (KeyError, IndexError):
# we don't have a comment node set for this item
from libervia.backend.tools.xml_tools import pp_elt
+
log.info(f"{pp_elt(parent_item_elt.toXml())}")
raise NotImplementedError()
else:
requestor_actor_id = self.build_apurl(
TYPE_ACTOR,
- await self.get_ap_account_from_jid_and_node(comment_service, comment_node)
+ await self.get_ap_account_from_jid_and_node(
+ comment_service, comment_node
+ ),
)
__, item_elt = await self.ap_item_2_mb_data_and_elt(
- requestor_actor_id,
- ap_item
+ requestor_actor_id, ap_item
)
await self._p.publish(client, comment_service, comment_node, [item_elt])
await self.notify_mentions(
@@ -2631,8 +2541,7 @@ class APGateway:
)
def get_ap_item_targets(
- self,
- item: Dict[str, Any]
+ self, item: Dict[str, Any]
) -> Tuple[bool, Dict[str, Set[str]], List[Dict[str, str]]]:
"""Retrieve targets of an AP item, and indicate if it's a public one
@@ -2704,18 +2613,13 @@ class APGateway:
is_public, targets, mentions = self.get_ap_item_targets(item)
if not is_public and targets.keys() == {TYPE_ACTOR}:
# this is a direct message
- await self.handle_message_ap_item(
- client, targets, mentions, destinee, item
- )
+ await self.handle_message_ap_item(client, targets, mentions, destinee, item)
else:
await self.handle_pubsub_ap_item(
client, targets, mentions, destinee, node, item, is_public
)
- def get_requestor_actor_id_from_targets(
- self,
- targets: set[str]
- ) -> str:
+ def get_requestor_actor_id_from_targets(self, targets: set[str]) -> str:
"""Find local actor to use as requestor_actor_id from request targets.
A local actor must be used to sign HTTP request, notably HTTP GET request for AP
@@ -2731,19 +2635,15 @@ class APGateway:
try:
return next(t for t in targets if self.is_local_url(t))
except StopIteration:
- log.warning(
- f"Can't find local target to use as requestor ID: {targets!r}"
- )
- return self.build_apurl(
- TYPE_ACTOR, f"libervia@{self.public_url}"
- )
+ log.warning(f"Can't find local target to use as requestor ID: {targets!r}")
+ return self.build_apurl(TYPE_ACTOR, f"libervia@{self.public_url}")
async def handle_message_ap_item(
self,
client: SatXMPPEntity,
targets: dict[str, Set[str]],
mentions: list[Dict[str, str]],
- destinee: jid.JID|None,
+ destinee: jid.JID | None,
item: dict,
) -> None:
"""Parse and deliver direct AP items translating to XMPP messages
@@ -2755,15 +2655,12 @@ class APGateway:
targets_urls = {t for t_set in targets.values() for t in t_set}
requestor_actor_id = self.get_requestor_actor_id_from_targets(targets_urls)
targets_jids = {
- await self.get_jid_from_id(requestor_actor_id, url)
- for url in targets_urls
+ await self.get_jid_from_id(requestor_actor_id, url) for url in targets_urls
}
if destinee is not None:
targets_jids.add(destinee)
mb_data = await self.ap_item_2_mb_data(requestor_actor_id, item)
- extra = {
- "origin_id": mb_data["id"]
- }
+ extra = {"origin_id": mb_data["id"]}
attachments = mb_data["extra"].get(C.KEY_ATTACHMENTS)
if attachments:
extra[C.KEY_ATTACHMENTS] = attachments
@@ -2773,9 +2670,9 @@ class APGateway:
defer_l.append(
client.sendMessage(
target_jid,
- {'': mb_data.get("content", "")},
+ {"": mb_data.get("content", "")},
mb_data.get("title"),
- extra=extra
+ extra=extra,
)
)
await defer.DeferredList(defer_l)
@@ -2798,18 +2695,16 @@ class APGateway:
"""
targets_urls = {t for t_set in targets.values() for t in t_set}
requestor_actor_id = self.get_requestor_actor_id_from_targets(targets_urls)
- anchor = uri.build_xmpp_uri("pubsub", path=service.full(), node=node, item=item_id)
+ anchor = uri.build_xmpp_uri(
+ "pubsub", path=service.full(), node=node, item=item_id
+ )
seen = set()
# we start with explicit mentions because mentions' content will be used in the
# future to fill "begin" and "end" reference attributes (we can't do it at the
# moment as there is no way to specify the XML element to use in the blog item).
for mention in mentions:
mentioned_jid = await self.get_jid_from_id(requestor_actor_id, mention["uri"])
- self._refs.send_reference(
- self.client,
- to_jid=mentioned_jid,
- anchor=anchor
- )
+ self._refs.send_reference(self.client, to_jid=mentioned_jid, anchor=anchor)
seen.add(mentioned_jid)
remaining = {
@@ -2818,21 +2713,17 @@ class APGateway:
for t in t_set
} - seen
for target in remaining:
- self._refs.send_reference(
- self.client,
- to_jid=target,
- anchor=anchor
- )
+ self._refs.send_reference(self.client, to_jid=target, anchor=anchor)
async def handle_pubsub_ap_item(
self,
client: SatXMPPEntity,
targets: dict[str, set[str]],
mentions: list[dict[str, str]],
- destinee: jid.JID|None,
+ destinee: jid.JID | None,
node: str,
item: dict,
- public: bool
+ public: bool,
) -> None:
"""Analyse, cache and deliver AP items translating to Pubsub
@@ -2859,14 +2750,16 @@ class APGateway:
# this item is a reply to an AP item, we use or create a corresponding node
# for comments
parent_node, __ = await self.get_comments_nodes(
- requestor_actor_id,
- item["id"],
- in_reply_to
+ requestor_actor_id, item["id"], in_reply_to
)
node = parent_node or node
cached_node = await self.host.memory.storage.get_pubsub_node(
- client, service, node, with_subscriptions=True, create=True,
- create_kwargs={"subscribed": True}
+ client,
+ service,
+ node,
+ with_subscriptions=True,
+ create=True,
+ create_kwargs={"subscribed": True},
)
else:
# it is a root item (i.e. not a reply to an other item)
@@ -2878,33 +2771,25 @@ class APGateway:
log.warning(
f"Received item in unknown node {node!r} at {service}. This may be "
f"due to a cache purge. We synchronise the node\n{item}"
-
)
return
if item.get("type") == TYPE_EVENT:
data, item_elt = await self.ap_events.ap_item_2_event_data_and_elt(
- requestor_actor_id,
- item
+ requestor_actor_id, item
)
else:
data, item_elt = await self.ap_item_2_mb_data_and_elt(
- requestor_actor_id,
- item
+ requestor_actor_id, item
)
await self.host.memory.storage.cache_pubsub_items(
- client,
- cached_node,
- [item_elt],
- [data]
+ client, cached_node, [item_elt], [data]
)
for subscription in cached_node.subscriptions:
if subscription.state != SubscriptionState.SUBSCRIBED:
continue
self.pubsub_service.notifyPublish(
- service,
- node,
- [(subscription.subscriber, None, [item_elt])]
+ service, node, [(subscription.subscriber, None, [item_elt])]
)
await self.notify_mentions(targets, mentions, service, node, item_elt["id"])
@@ -2940,7 +2825,7 @@ class APGateway:
History,
History.origin_id,
item_id,
- (History.messages, History.subjects)
+ (History.messages, History.subjects),
)
if history is not None:
@@ -2973,7 +2858,5 @@ class APGateway:
if subscription.state != SubscriptionState.SUBSCRIBED:
continue
self.pubsub_service.notifyRetract(
- client.jid,
- node,
- [(subscription.subscriber, None, [item_elt])]
+ client.jid, node, [(subscription.subscriber, None, [item_elt])]
)
diff --git a/libervia/backend/plugins/plugin_comp_ap_gateway/ad_hoc.py b/libervia/backend/plugins/plugin_comp_ap_gateway/ad_hoc.py
index 96b93668..53173573 100644
--- a/libervia/backend/plugins/plugin_comp_ap_gateway/ad_hoc.py
+++ b/libervia/backend/plugins/plugin_comp_ap_gateway/ad_hoc.py
@@ -29,6 +29,7 @@ from libervia.backend.core.log import getLogger
log = getLogger(__name__)
NS_XMPP_JID_NODE_2_AP = "https://libervia.org/ap_gateway/xmpp_jid_node_2_ap_actor"
+
class APAdHocService:
"""Ad-Hoc commands for AP Gateway"""
@@ -52,7 +53,7 @@ class APAdHocService:
command_elt: domish.Element,
session_data: dict,
action: str,
- node: str
+ node: str,
):
try:
x_elt = next(command_elt.elements(data_form.NS_X_DATA, "x"))
@@ -63,18 +64,15 @@ class APAdHocService:
# root request
status = self._c.STATUS.EXECUTING
form = data_form.Form(
- "form", title="XMPP JID/node to AP actor conversion",
- formNamespace=NS_XMPP_JID_NODE_2_AP
+ "form",
+ title="XMPP JID/node to AP actor conversion",
+ formNamespace=NS_XMPP_JID_NODE_2_AP,
)
- field = data_form.Field(
- "text-single", "jid", required=True
- )
+ field = data_form.Field("text-single", "jid", required=True)
form.addField(field)
- field = data_form.Field(
- "text-single", "node", required=False
- )
+ field = data_form.Field("text-single", "node", required=False)
form.addField(field)
payload = form.toElement()
diff --git a/libervia/backend/plugins/plugin_comp_ap_gateway/constants.py b/libervia/backend/plugins/plugin_comp_ap_gateway/constants.py
index 204a303c..f21c4132 100644
--- a/libervia/backend/plugins/plugin_comp_ap_gateway/constants.py
+++ b/libervia/backend/plugins/plugin_comp_ap_gateway/constants.py
@@ -55,32 +55,71 @@ SIGN_HEADERS = {
("digest", "(request-target)"),
],
b"GET": ["host"],
- b"POST": ["digest"]
+ b"POST": ["digest"],
}
PAGE_SIZE = 10
HS2019 = "hs2019"
# delay after which a signed request is not accepted anymore
-SIGN_EXP = 12*60*60 # 12 hours (same value as for Mastodon)
+SIGN_EXP = 12 * 60 * 60 # 12 hours (same value as for Mastodon)
LRU_MAX_SIZE = 200
ACTIVITY_TYPES = (
- "Accept", "Add", "Announce", "Arrive", "Block", "Create", "Delete", "Dislike", "Flag",
- "Follow", "Ignore", "Invite", "Join", "Leave", "Like", "Listen", "Move", "Offer",
- "Question", "Reject", "Read", "Remove", "TentativeReject", "TentativeAccept",
- "Travel", "Undo", "Update", "View",
+ "Accept",
+ "Add",
+ "Announce",
+ "Arrive",
+ "Block",
+ "Create",
+ "Delete",
+ "Dislike",
+ "Flag",
+ "Follow",
+ "Ignore",
+ "Invite",
+ "Join",
+ "Leave",
+ "Like",
+ "Listen",
+ "Move",
+ "Offer",
+ "Question",
+ "Reject",
+ "Read",
+ "Remove",
+ "TentativeReject",
+ "TentativeAccept",
+ "Travel",
+ "Undo",
+ "Update",
+ "View",
# non-standard activities
- "EmojiReact"
+ "EmojiReact",
)
ACTIVITY_TYPES_LOWER = [a.lower() for a in ACTIVITY_TYPES]
ACTIVITY_OBJECT_MANDATORY = (
- "Create", "Update", "Delete", "Follow", "Add", "Remove", "Like", "Block", "Undo"
+ "Create",
+ "Update",
+ "Delete",
+ "Follow",
+ "Add",
+ "Remove",
+ "Like",
+ "Block",
+ "Undo",
)
ACTIVITY_TARGET_MANDATORY = ("Add", "Remove")
# activities which can be used with Shared Inbox (i.e. with no account specified)
# must be lowercase
ACTIVIY_NO_ACCOUNT_ALLOWED = (
- "create", "update", "delete", "announce", "undo", "like", "emojireact", "join",
- "leave"
+ "create",
+ "update",
+ "delete",
+ "announce",
+ "undo",
+ "like",
+ "emojireact",
+ "join",
+ "leave",
)
# maximum number of parents to retrieve when comments_max_depth option is set
COMMENTS_MAX_PARENTS = 100
diff --git a/libervia/backend/plugins/plugin_comp_ap_gateway/events.py b/libervia/backend/plugins/plugin_comp_ap_gateway/events.py
index eff3f487..46ff2ecd 100644
--- a/libervia/backend/plugins/plugin_comp_ap_gateway/events.py
+++ b/libervia/backend/plugins/plugin_comp_ap_gateway/events.py
@@ -45,11 +45,12 @@ AP_EVENTS_CONTEXT = {
"addressCountry": "sc:addressCountry",
"addressLocality": "sc:addressLocality",
"addressRegion": "sc:addressRegion",
- "anonymousParticipationEnabled": {"@id": "mz:anonymousParticipationEnabled",
- "@type": "sc:Boolean"},
+ "anonymousParticipationEnabled": {
+ "@id": "mz:anonymousParticipationEnabled",
+ "@type": "sc:Boolean",
+ },
"category": "sc:category",
- "commentsEnabled": {"@id": "pt:commentsEnabled",
- "@type": "sc:Boolean"},
+ "commentsEnabled": {"@id": "pt:commentsEnabled", "@type": "sc:Boolean"},
"discoverable": "toot:discoverable",
"discussions": {"@id": "mz:discussions", "@type": "@id"},
"events": {"@id": "mz:events", "@type": "@id"},
@@ -57,8 +58,7 @@ AP_EVENTS_CONTEXT = {
"inLanguage": "sc:inLanguage",
"isOnline": {"@id": "mz:isOnline", "@type": "sc:Boolean"},
"joinMode": {"@id": "mz:joinMode", "@type": "mz:joinModeType"},
- "joinModeType": {"@id": "mz:joinModeType",
- "@type": "rdfs:Class"},
+ "joinModeType": {"@id": "mz:joinModeType", "@type": "rdfs:Class"},
"location": {"@id": "sc:location", "@type": "sc:Place"},
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"maximumAttendeeCapacity": "sc:maximumAttendeeCapacity",
@@ -66,19 +66,21 @@ AP_EVENTS_CONTEXT = {
"members": {"@id": "mz:members", "@type": "@id"},
"mz": "https://joinmobilizon.org/ns#",
"openness": {"@id": "mz:openness", "@type": "@id"},
- "participantCount": {"@id": "mz:participantCount",
- "@type": "sc:Integer"},
- "participationMessage": {"@id": "mz:participationMessage",
- "@type": "sc:Text"},
+ "participantCount": {"@id": "mz:participantCount", "@type": "sc:Integer"},
+ "participationMessage": {"@id": "mz:participationMessage", "@type": "sc:Text"},
"postalCode": "sc:postalCode",
"posts": {"@id": "mz:posts", "@type": "@id"},
"propertyID": "sc:propertyID",
"pt": "https://joinpeertube.org/ns#",
"remainingAttendeeCapacity": "sc:remainingAttendeeCapacity",
- "repliesModerationOption": {"@id": "mz:repliesModerationOption",
- "@type": "mz:repliesModerationOptionType"},
- "repliesModerationOptionType": {"@id": "mz:repliesModerationOptionType",
- "@type": "rdfs:Class"},
+ "repliesModerationOption": {
+ "@id": "mz:repliesModerationOption",
+ "@type": "mz:repliesModerationOptionType",
+ },
+ "repliesModerationOptionType": {
+ "@id": "mz:repliesModerationOptionType",
+ "@type": "rdfs:Class",
+ },
"resources": {"@id": "mz:resources", "@type": "@id"},
"sc": "http://schema.org#",
"streetAddress": "sc:streetAddress",
@@ -86,7 +88,7 @@ AP_EVENTS_CONTEXT = {
"todos": {"@id": "mz:todos", "@type": "@id"},
"toot": "http://joinmastodon.org/ns#",
"uuid": "sc:identifier",
- "value": "sc:value"
+ "value": "sc:value",
}
@@ -99,7 +101,7 @@ class APEvents:
self._events = self.host.plugins["XEP-0471"]
async def event_data_2_ap_item(
- self, event_data: dict, author_jid: jid.JID, is_new: bool=True
+ self, event_data: dict, author_jid: jid.JID, is_new: bool = True
) -> dict:
"""Convert event data to AP activity
@@ -114,8 +116,7 @@ class APEvents:
if not event_data.get("id"):
event_data["id"] = shortuuid.uuid()
ap_account = await self.apg.get_ap_account_from_jid_and_node(
- author_jid,
- self._events.namespace
+ author_jid, self._events.namespace
)
url_actor = self.apg.build_apurl(TYPE_ACTOR, ap_account)
url_item = self.apg.build_apurl(TYPE_ITEM, ap_account, event_data["id"])
@@ -141,12 +142,14 @@ class APEvents:
pass
else:
media_type = mimetypes.guess_type(head_picture_url, False)[0] or "image/jpeg"
- attachment.append({
- "name": "Banner",
- "type": "Document",
- "mediaType": media_type,
- "url": head_picture_url,
- })
+ attachment.append(
+ {
+ "name": "Banner",
+ "type": "Document",
+ "mediaType": media_type,
+ "url": head_picture_url,
+ }
+ )
descriptions = event_data.get("descriptions")
if descriptions:
@@ -162,10 +165,12 @@ class APEvents:
if categories:
tag = ap_object["tag"] = []
for category in categories:
- tag.append({
- "name": f"#{category['term']}",
- "type": "Hashtag",
- })
+ tag.append(
+ {
+ "name": f"#{category['term']}",
+ "type": "Hashtag",
+ }
+ )
locations = event_data.get("locations")
if locations:
@@ -203,12 +208,14 @@ class APEvents:
website = extra.get("website")
if website:
- attachment.append({
- "href": website,
- "mediaType": "text/html",
- "name": "Website",
- "type": "Link"
- })
+ attachment.append(
+ {
+ "href": website,
+ "mediaType": "text/html",
+ "name": "Website",
+ "type": "Link",
+ }
+ )
accessibility = extra.get("accessibility")
if accessibility:
@@ -224,11 +231,13 @@ class APEvents:
log.error(f"unexpected wheelchair value: {wheelchair}")
ap_wc_value = None
if ap_wc_value is not None:
- attachment.append({
- "propertyID": "mz:accessibility:wheelchairAccessible",
- "type": "PropertyValue",
- "value": ap_wc_value
- })
+ attachment.append(
+ {
+ "propertyID": "mz:accessibility:wheelchairAccessible",
+ "type": "PropertyValue",
+ "value": ap_wc_value,
+ }
+ )
activity = self.apg.create_activity(
"Create" if is_new else "Update", url_actor, ap_object, activity_id=url_item
@@ -236,11 +245,7 @@ class APEvents:
activity["@context"].append(AP_EVENTS_CONTEXT)
return activity
- async def ap_item_2_event_data(
- self,
- requestor_actor_id: str,
- ap_item: dict
- ) -> dict:
+ async def ap_item_2_event_data(self, requestor_actor_id: str, ap_item: dict) -> dict:
"""Convert AP activity or object to event data
@param requestor_actor_id: ID of the actor doing the request.
@@ -251,7 +256,9 @@ class APEvents:
"""
is_activity = self.apg.is_activity(ap_item)
if is_activity:
- ap_object = await self.apg.ap_get_object(requestor_actor_id, ap_item, "object")
+ ap_object = await self.apg.ap_get_object(
+ requestor_actor_id, ap_item, "object"
+ )
if not ap_object:
log.warning(f'No "object" found in AP item {ap_item!r}')
raise exceptions.DataError
@@ -311,10 +318,7 @@ class APEvents:
# description
content = ap_object.get("content")
if content:
- event_data["descriptions"] = [{
- "type": "xhtml",
- "description": content
- }]
+ event_data["descriptions"] = [{"type": "xhtml", "description": content}]
# categories
tags = ap_object.get("tag")
@@ -329,7 +333,7 @@ class APEvents:
continue
categories.append({"term": term})
- #location
+ # location
ap_location = ap_object.get("location")
if ap_location:
location = {}
@@ -361,24 +365,24 @@ class APEvents:
# a simple "yes"/"no" form.
rsvp_data = {"fields": []}
event_data["rsvp"] = [rsvp_data]
- rsvp_data["fields"].append({
- "type": "list-single",
- "name": "attending",
- "label": "Attending",
- "options": [
- {"label": "yes", "value": "yes"},
- {"label": "no", "value": "no"}
- ],
- "required": True
- })
+ rsvp_data["fields"].append(
+ {
+ "type": "list-single",
+ "name": "attending",
+ "label": "Attending",
+ "options": [
+ {"label": "yes", "value": "yes"},
+ {"label": "no", "value": "no"},
+ ],
+ "required": True,
+ }
+ )
# comments
if ap_object.get("commentsEnabled"):
__, comments_node = await self.apg.get_comments_nodes(
- requestor_actor_id,
- object_id,
- None
+ requestor_actor_id, object_id, None
)
event_data["comments"] = {
"service": author_jid,
@@ -399,15 +403,10 @@ class APEvents:
return event_data
async def ap_item_2_event_data_and_elt(
- self,
- requestor_actor_id: str,
- ap_item: dict
+ self, requestor_actor_id: str, ap_item: dict
) -> tuple[dict, domish.Element]:
"""Convert AP item to parsed event data and corresponding item element"""
- event_data = await self.ap_item_2_event_data(
- requestor_actor_id,
- ap_item
- )
+ event_data = await self.ap_item_2_event_data(requestor_actor_id, ap_item)
event_elt = self._events.event_data_2_event_elt(event_data)
item_elt = domish.Element((None, "item"))
item_elt["id"] = event_data["id"]
@@ -415,13 +414,10 @@ class APEvents:
return event_data, item_elt
async def ap_item_2_event_elt(
- self,
- requestor_actor_id: str,
- ap_item: dict
+ self, requestor_actor_id: str, ap_item: dict
) -> domish.Element:
"""Convert AP item to XMPP item element"""
__, item_elt = await self.ap_item_2_event_data_and_elt(
- requestor_actor_id,
- ap_item
+ requestor_actor_id, ap_item
)
return item_elt
diff --git a/libervia/backend/plugins/plugin_comp_ap_gateway/http_server.py b/libervia/backend/plugins/plugin_comp_ap_gateway/http_server.py
index dc627000..c4935297 100644
--- a/libervia/backend/plugins/plugin_comp_ap_gateway/http_server.py
+++ b/libervia/backend/plugins/plugin_comp_ap_gateway/http_server.py
@@ -41,11 +41,29 @@ from libervia.backend.tools.common import date_utils, uri
from libervia.backend.memory.sqla_mapping import SubscriptionState
from .constants import (
- NS_AP, MEDIA_TYPE_AP, MEDIA_TYPE_AP_ALT, CONTENT_TYPE_WEBFINGER, CONTENT_TYPE_AP,
- TYPE_ACTOR, TYPE_INBOX, TYPE_SHARED_INBOX, TYPE_OUTBOX, TYPE_EVENT, AP_REQUEST_TYPES,
- PAGE_SIZE, ACTIVITY_TYPES_LOWER, ACTIVIY_NO_ACCOUNT_ALLOWED, SIGN_HEADERS, HS2019,
- SIGN_EXP, TYPE_FOLLOWERS, TYPE_FOLLOWING, TYPE_ITEM, TYPE_LIKE, TYPE_REACTION,
- ST_AP_CACHE
+ NS_AP,
+ MEDIA_TYPE_AP,
+ MEDIA_TYPE_AP_ALT,
+ CONTENT_TYPE_WEBFINGER,
+ CONTENT_TYPE_AP,
+ TYPE_ACTOR,
+ TYPE_INBOX,
+ TYPE_SHARED_INBOX,
+ TYPE_OUTBOX,
+ TYPE_EVENT,
+ AP_REQUEST_TYPES,
+ PAGE_SIZE,
+ ACTIVITY_TYPES_LOWER,
+ ACTIVIY_NO_ACCOUNT_ALLOWED,
+ SIGN_HEADERS,
+ HS2019,
+ SIGN_EXP,
+ TYPE_FOLLOWERS,
+ TYPE_FOLLOWING,
+ TYPE_ITEM,
+ TYPE_LIKE,
+ TYPE_REACTION,
+ ST_AP_CACHE,
)
from .regex import RE_SIG_PARAM
@@ -53,13 +71,13 @@ from .regex import RE_SIG_PARAM
log = getLogger(__name__)
VERSION = unicodedata.normalize(
- 'NFKD',
- f"{C.APP_NAME} ActivityPub Gateway {C.APP_VERSION}"
+ "NFKD", f"{C.APP_NAME} ActivityPub Gateway {C.APP_VERSION}"
)
class HTTPAPGServer(web_resource.Resource):
"""HTTP Server handling ActivityPub S2S protocol"""
+
isLeaf = True
def __init__(self, ap_gateway):
@@ -68,30 +86,23 @@ class HTTPAPGServer(web_resource.Resource):
super().__init__()
def response_code(
- self,
- request: "HTTPRequest",
- http_code: int,
- msg: Optional[str] = None
+ self, request: "HTTPRequest", http_code: int, msg: Optional[str] = None
) -> None:
"""Log and set HTTP return code and associated message"""
if msg is not None:
log.warning(msg)
request.setResponseCode(http_code, None if msg is None else msg.encode())
- def _on_request_error(self, failure_: failure.Failure, request: "HTTPRequest") -> None:
+ def _on_request_error(
+ self, failure_: failure.Failure, request: "HTTPRequest"
+ ) -> None:
exc = failure_.value
if isinstance(exc, exceptions.NotFound):
- self.response_code(
- request,
- http.NOT_FOUND,
- str(exc)
- )
+ self.response_code(request, http.NOT_FOUND, str(exc))
else:
log.exception(f"Internal error: {failure_.value}")
self.response_code(
- request,
- http.INTERNAL_SERVER_ERROR,
- f"internal error: {failure_.value}"
+ request, http.INTERNAL_SERVER_ERROR, f"internal error: {failure_.value}"
)
request.finish()
raise failure_
@@ -105,7 +116,7 @@ class HTTPAPGServer(web_resource.Resource):
account = resource[5:].strip()
if not resource.startswith("acct:") or not account:
return web_resource.ErrorPage(
- http.BAD_REQUEST, "Bad Request" , "Invalid webfinger resource"
+ http.BAD_REQUEST, "Bad Request", "Invalid webfinger resource"
).render(request)
actor_url = self.apg.build_apurl(TYPE_ACTOR, account)
@@ -114,12 +125,8 @@ class HTTPAPGServer(web_resource.Resource):
"aliases": [actor_url],
"subject": resource,
"links": [
- {
- "rel": "self",
- "type": "application/activity+json",
- "href": actor_url
- }
- ]
+ {"rel": "self", "type": "application/activity+json", "href": actor_url}
+ ],
}
request.setHeader("content-type", CONTENT_TYPE_WEBFINGER)
request.write(json.dumps(resp).encode())
@@ -134,7 +141,7 @@ class HTTPAPGServer(web_resource.Resource):
node: Optional[str],
ap_account: str,
ap_url: str,
- signing_actor: str
+ signing_actor: str,
) -> None:
if node is None:
node = self.apg._m.namespace
@@ -181,9 +188,11 @@ class HTTPAPGServer(web_resource.Resource):
elif type_ == TYPE_LIKE:
await self.handle_attachment_item(client, obj, {"noticed": False})
elif type_ == TYPE_REACTION:
- await self.handle_attachment_item(client, obj, {
- "reactions": {"operation": "update", "remove": [obj["content"]]}
- })
+ await self.handle_attachment_item(
+ client,
+ obj,
+ {"reactions": {"operation": "update", "remove": [obj["content"]]}},
+ )
else:
log.warning(f"Unmanaged undo type: {type_!r}")
@@ -196,7 +205,7 @@ class HTTPAPGServer(web_resource.Resource):
node: Optional[str],
ap_account: str,
ap_url: str,
- signing_actor: str
+ signing_actor: str,
) -> None:
if node is None:
node = self.apg._m.namespace
@@ -207,7 +216,7 @@ class HTTPAPGServer(web_resource.Resource):
account_jid,
node,
# subscriptions from AP are always public
- options=self.apg._pps.set_public_opt()
+ options=self.apg._pps.set_public_opt(),
)
except pubsub.SubscriptionPending:
log.info(f"subscription to node {node!r} of {account_jid} is pending")
@@ -218,9 +227,7 @@ class HTTPAPGServer(web_resource.Resource):
raise exceptions.InternalError('"subscribed" state was expected')
inbox = await self.apg.get_ap_inbox_from_id(signing_actor, use_shared=False)
actor_id = self.apg.build_apurl(TYPE_ACTOR, ap_account)
- accept_data = self.apg.create_activity(
- "Accept", actor_id, object_=data
- )
+ accept_data = self.apg.create_activity("Accept", actor_id, object_=data)
await self.apg.sign_and_post(inbox, actor_id, accept_data)
await self.apg._c.synchronise(client, account_jid, node, resync=False)
@@ -233,7 +240,7 @@ class HTTPAPGServer(web_resource.Resource):
node: Optional[str],
ap_account: str,
ap_url: str,
- signing_actor: str
+ signing_actor: str,
) -> None:
if node is None:
node = self.apg._m.namespace
@@ -253,7 +260,9 @@ class HTTPAPGServer(web_resource.Resource):
continue
try:
sub = next(
- s for s in follow_node.subscriptions if s.subscriber==account_jid
+ s
+ for s in follow_node.subscriptions
+ if s.subscriber == account_jid
)
except StopIteration:
log.warning(
@@ -283,7 +292,7 @@ class HTTPAPGServer(web_resource.Resource):
node: Optional[str],
ap_account: Optional[str],
ap_url: str,
- signing_actor: str
+ signing_actor: str,
):
if node is None:
node = self.apg._m.namespace
@@ -325,13 +334,9 @@ class HTTPAPGServer(web_resource.Resource):
if repeated:
# we don't check sender when item is repeated, as it should be different
# from post author in this case
- sender_jid = await self.apg.get_jid_from_id(
- requestor_actor_id,
- sender
- )
+ sender_jid = await self.apg.get_jid_from_id(requestor_actor_id, sender)
repeater_jid = await self.apg.get_jid_from_id(
- requestor_actor_id,
- signing_actor
+ requestor_actor_id, signing_actor
)
repeated_item_id = obj["id"]
if self.apg.is_local_url(repeated_item_id):
@@ -347,13 +352,14 @@ class HTTPAPGServer(web_resource.Resource):
if not url_account or not url_item_id:
raise ValueError
except (RuntimeError, ValueError):
- raise exceptions.DataError(
- "local URI is invalid: {repeated_id}"
- )
+ raise exceptions.DataError("local URI is invalid: {repeated_id}")
else:
url_jid, url_node = await self.apg.get_jid_and_node(url_account)
- if ((url_jid != sender_jid
- or url_node and url_node != self.apg._m.namespace)):
+ if (
+ url_jid != sender_jid
+ or url_node
+ and url_node != self.apg._m.namespace
+ ):
raise exceptions.DataError(
"announced ID doesn't match sender ({sender}): "
f"[repeated_item_id]"
@@ -368,17 +374,15 @@ class HTTPAPGServer(web_resource.Resource):
"pubsub",
path=sender_jid.full(),
node=self.apg._m.namespace,
- item=repeated_item_id
- )
+ item=repeated_item_id,
+ ),
}
# we must use activity's id and targets, not the original item ones
for field in ("id", "to", "bto", "cc", "bcc"):
obj[field] = data.get(field)
else:
if sender != signing_actor:
- log.warning(
- "Ignoring object not attributed to signing actor: {obj}"
- )
+ log.warning("Ignoring object not attributed to signing actor: {obj}")
continue
await self.apg.new_ap_item(client, account_jid, node, obj)
@@ -392,7 +396,7 @@ class HTTPAPGServer(web_resource.Resource):
node: Optional[str],
ap_account: Optional[str],
ap_url: str,
- signing_actor: str
+ signing_actor: str,
):
await self.handle_new_ap_items(
requestor_actor_id, request, data, account_jid, node, signing_actor
@@ -407,7 +411,7 @@ class HTTPAPGServer(web_resource.Resource):
node: Optional[str],
ap_account: Optional[str],
ap_url: str,
- signing_actor: str
+ signing_actor: str,
):
# Update is the same as create: the item ID stays the same, thus the item will be
# overwritten
@@ -424,7 +428,7 @@ class HTTPAPGServer(web_resource.Resource):
node: Optional[str],
ap_account: Optional[str],
ap_url: str,
- signing_actor: str
+ signing_actor: str,
):
# we create a new item
await self.handle_new_ap_items(
@@ -434,14 +438,11 @@ class HTTPAPGServer(web_resource.Resource):
account_jid,
node,
signing_actor,
- repeated=True
+ repeated=True,
)
async def handle_attachment_item(
- self,
- client: SatXMPPEntity,
- data: dict,
- attachment_data: dict
+ self, client: SatXMPPEntity, data: dict, attachment_data: dict
) -> None:
target_ids = data.get("object")
if not target_ids:
@@ -485,11 +486,7 @@ class HTTPAPGServer(web_resource.Resource):
author_jid, item_node, item_id
)
cached_node = await self.apg.host.memory.storage.get_pubsub_node(
- client,
- author_jid,
- attachment_node,
- with_subscriptions=True,
- create=True
+ client, author_jid, attachment_node, with_subscriptions=True, create=True
)
found_items, __ = await self.apg.host.memory.storage.get_items(
cached_node, item_ids=[client.jid.userhost()]
@@ -501,19 +498,13 @@ class HTTPAPGServer(web_resource.Resource):
old_item_elt = found_item.data
item_elt = await self.apg._pa.apply_set_handler(
- client,
- {"extra": attachment_data},
- old_item_elt,
- None
+ client, {"extra": attachment_data}, old_item_elt, None
)
# we reparse the element, as there can be other attachments
attachments_data = self.apg._pa.items_2_attachment_data(client, [item_elt])
# and we update the cache
await self.apg.host.memory.storage.cache_pubsub_items(
- client,
- cached_node,
- [item_elt],
- attachments_data or [{}]
+ client, cached_node, [item_elt], attachments_data or [{}]
)
if self.apg.is_virtual_jid(author_jid):
@@ -525,7 +516,7 @@ class HTTPAPGServer(web_resource.Resource):
self.apg.pubsub_service.notifyPublish(
author_jid,
attachment_node,
- [(subscription.subscriber, None, [item_elt])]
+ [(subscription.subscriber, None, [item_elt])],
)
else:
# the attachment is on an XMPP item, we publish it to the attachment node
@@ -542,7 +533,7 @@ class HTTPAPGServer(web_resource.Resource):
node: Optional[str],
ap_account: Optional[str],
ap_url: str,
- signing_actor: str
+ signing_actor: str,
) -> None:
client = await self.apg.get_virtual_client(requestor_actor_id, signing_actor)
await self.handle_attachment_item(client, data, {"noticed": True})
@@ -556,12 +547,12 @@ class HTTPAPGServer(web_resource.Resource):
node: Optional[str],
ap_account: Optional[str],
ap_url: str,
- signing_actor: str
+ signing_actor: str,
) -> None:
client = await self.apg.get_virtual_client(requestor_actor_id, signing_actor)
- await self.handle_attachment_item(client, data, {
- "reactions": {"operation": "update", "add": [data["content"]]}
- })
+ await self.handle_attachment_item(
+ client, data, {"reactions": {"operation": "update", "add": [data["content"]]}}
+ )
async def handle_join_activity(
self,
@@ -572,7 +563,7 @@ class HTTPAPGServer(web_resource.Resource):
node: Optional[str],
ap_account: Optional[str],
ap_url: str,
- signing_actor: str
+ signing_actor: str,
) -> None:
client = await self.apg.get_virtual_client(requestor_actor_id, signing_actor)
await self.handle_attachment_item(client, data, {"rsvp": {"attending": "yes"}})
@@ -586,7 +577,7 @@ class HTTPAPGServer(web_resource.Resource):
node: Optional[str],
ap_account: Optional[str],
ap_url: str,
- signing_actor: str
+ signing_actor: str,
) -> None:
client = await self.apg.get_virtual_client(requestor_actor_id, signing_actor)
await self.handle_attachment_item(client, data, {"rsvp": {"attending": "no"}})
@@ -599,7 +590,7 @@ class HTTPAPGServer(web_resource.Resource):
node: Optional[str],
ap_account: str,
ap_url: str,
- signing_actor: Optional[str]
+ signing_actor: Optional[str],
) -> dict:
inbox = self.apg.build_apurl(TYPE_INBOX, ap_account)
shared_inbox = self.apg.build_apurl(TYPE_SHARED_INBOX)
@@ -623,9 +614,8 @@ class HTTPAPGServer(web_resource.Resource):
actor_data = {
"@context": [
"https://www.w3.org/ns/activitystreams",
- "https://w3id.org/security/v1"
+ "https://w3id.org/security/v1",
],
-
# XXX: Mastodon doesn't like percent-encode arobas, so we have to unescape it
# if it is escaped
"id": ap_url.replace("%40", "@"),
@@ -639,7 +629,7 @@ class HTTPAPGServer(web_resource.Resource):
"publicKey": {
"id": f"{ap_url}#main-key",
"owner": ap_url,
- "publicKeyPem": self.apg.public_key_pem
+ "publicKeyPem": self.apg.public_key_pem,
},
"endpoints": {
"sharedInbox": shared_inbox,
@@ -664,7 +654,7 @@ class HTTPAPGServer(web_resource.Resource):
actor_data["icon"] = {
"type": "Image",
"url": avatar_url,
- "mediaType": media_type
+ "mediaType": media_type,
}
return actor_data
@@ -672,13 +662,12 @@ class HTTPAPGServer(web_resource.Resource):
def get_canonical_url(self, request: "HTTPRequest") -> str:
return parse.urljoin(
f"https://{self.apg.public_url}",
- request.path.decode().rstrip("/")
- # we unescape "@" for the same reason as in [ap_actor_request]
+ request.path.decode().rstrip("/"),
+ # we unescape "@" for the same reason as in [ap_actor_request]
).replace("%40", "@")
def query_data_2_rsm_request(
- self,
- query_data: Dict[str, List[str]]
+ self, query_data: Dict[str, List[str]]
) -> rsm.RSMRequest:
"""Get RSM kwargs to use with RSMRequest from query data"""
page = query_data.get("page")
@@ -690,7 +679,7 @@ class HTTPAPGServer(web_resource.Resource):
else:
for query_key in ("index", "before", "after"):
try:
- kwargs={query_key: query_data[query_key][0], "max_": PAGE_SIZE}
+ kwargs = {query_key: query_data[query_key][0], "max_": PAGE_SIZE}
except (KeyError, IndexError, ValueError):
pass
else:
@@ -705,7 +694,7 @@ class HTTPAPGServer(web_resource.Resource):
node: Optional[str],
ap_account: str,
ap_url: str,
- query_data: Dict[str, List[str]]
+ query_data: Dict[str, List[str]],
) -> dict:
if node is None:
node = self.apg._m.namespace
@@ -719,7 +708,7 @@ class HTTPAPGServer(web_resource.Resource):
service=account_jid,
node=node,
rsm_request=self.query_data_2_rsm_request(query_data),
- extra = {C.KEY_USE_CACHE: False}
+ extra={C.KEY_USE_CACHE: False},
)
except error.StanzaError as e:
log.warning(f"Can't get data from pubsub node {node} at {account_jid}: {e}")
@@ -730,8 +719,7 @@ class HTTPAPGServer(web_resource.Resource):
if node and node.startswith(self.apg._events.namespace):
ordered_items = [
await self.apg.ap_events.event_data_2_ap_item(
- self.apg._events.event_elt_2_event_data(item),
- account_jid
+ self.apg._events.event_elt_2_event_data(item), account_jid
)
for item in reversed(items)
]
@@ -740,11 +728,8 @@ class HTTPAPGServer(web_resource.Resource):
await self.apg.mb_data_2_ap_item(
self.apg.client,
await self.apg._m.item_2_mb_data(
- self.apg.client,
- item,
- account_jid,
- node
- )
+ self.apg.client, item, account_jid, node
+ ),
)
for item in reversed(items)
]
@@ -753,7 +738,7 @@ class HTTPAPGServer(web_resource.Resource):
"id": url,
"type": "OrderedCollectionPage",
"partOf": base_url,
- "orderedItems": ordered_items
+ "orderedItems": ordered_items,
}
if "rsm" not in metadata:
@@ -764,13 +749,13 @@ class HTTPAPGServer(web_resource.Resource):
# of what we get with RSM (at least with Libervia Pubsub)
if not metadata["complete"]:
try:
- last= metadata["rsm"]["last"]
+ last = metadata["rsm"]["last"]
except KeyError:
last = None
ret_data["prev"] = f"{base_url}?{parse.urlencode({'after': last})}"
if metadata["rsm"]["index"] != 0:
try:
- first= metadata["rsm"]["first"]
+ first = metadata["rsm"]["first"]
except KeyError:
first = None
ret_data["next"] = f"{base_url}?{parse.urlencode({'before': first})}"
@@ -785,7 +770,7 @@ class HTTPAPGServer(web_resource.Resource):
node: Optional[str],
ap_account: str,
ap_url: str,
- signing_actor: Optional[str]
+ signing_actor: Optional[str],
) -> dict:
if node is None:
node = self.apg._m.namespace
@@ -809,7 +794,7 @@ class HTTPAPGServer(web_resource.Resource):
node=node,
max_items=0,
rsm_request=rsm.RSMRequest(max_=0),
- extra = {C.KEY_USE_CACHE: False}
+ extra={C.KEY_USE_CACHE: False},
)
except error.StanzaError as e:
log.warning(f"Can't get data from pubsub node {node} at {account_jid}: {e}")
@@ -844,7 +829,7 @@ class HTTPAPGServer(web_resource.Resource):
node: Optional[str],
ap_account: Optional[str],
ap_url: str,
- signing_actor: Optional[str]
+ signing_actor: Optional[str],
) -> None:
assert data is not None
if signing_actor is None:
@@ -855,14 +840,14 @@ class HTTPAPGServer(web_resource.Resource):
return self.response_code(
request,
http.UNSUPPORTED_MEDIA_TYPE,
- f"request is not an activity, ignoring"
+ f"request is not an activity, ignoring",
)
if account_jid is None and activity_type not in ACTIVIY_NO_ACCOUNT_ALLOWED:
return self.response_code(
request,
http.UNSUPPORTED_MEDIA_TYPE,
- f"{activity_type.title()!r} activity must target an account"
+ f"{activity_type.title()!r} activity must target an account",
)
try:
@@ -871,12 +856,18 @@ class HTTPAPGServer(web_resource.Resource):
return self.response_code(
request,
http.UNSUPPORTED_MEDIA_TYPE,
- f"{activity_type.title()} activity is not yet supported"
+ f"{activity_type.title()} activity is not yet supported",
)
else:
await method(
- requestor_actor_id, request, data, account_jid, node, ap_account, ap_url,
- signing_actor
+ requestor_actor_id,
+ request,
+ data,
+ account_jid,
+ node,
+ ap_account,
+ ap_url,
+ signing_actor,
)
async def ap_followers_request(
@@ -887,7 +878,7 @@ class HTTPAPGServer(web_resource.Resource):
node: Optional[str],
ap_account: Optional[str],
ap_url: str,
- signing_actor: Optional[str]
+ signing_actor: Optional[str],
) -> dict:
if node is None:
node = self.apg._m.namespace
@@ -902,20 +893,22 @@ class HTTPAPGServer(web_resource.Resource):
ap_account = self.apg._e.unescape(subscriber.user)
else:
# regular XMPP user
- ap_account = await self.apg.get_ap_account_from_jid_and_node(subscriber, node)
+ ap_account = await self.apg.get_ap_account_from_jid_and_node(
+ subscriber, node
+ )
followers.append(ap_account)
url = self.get_canonical_url(request)
return {
- "@context": ["https://www.w3.org/ns/activitystreams"],
- "type": "OrderedCollection",
- "id": url,
- "totalItems": len(subscribers),
- "first": {
- "type": "OrderedCollectionPage",
+ "@context": ["https://www.w3.org/ns/activitystreams"],
+ "type": "OrderedCollection",
"id": url,
- "orderedItems": followers
- }
+ "totalItems": len(subscribers),
+ "first": {
+ "type": "OrderedCollectionPage",
+ "id": url,
+ "orderedItems": followers,
+ },
}
async def ap_following_request(
@@ -926,12 +919,10 @@ class HTTPAPGServer(web_resource.Resource):
node: Optional[str],
ap_account: Optional[str],
ap_url: str,
- signing_actor: Optional[str]
+ signing_actor: Optional[str],
) -> dict[str, Any]:
client = self.apg.client
- subscriptions = await self.apg._pps.subscriptions(
- client, account_jid, node
- )
+ subscriptions = await self.apg._pps.subscriptions(client, account_jid, node)
following = []
for sub_dict in subscriptions:
service = jid.JID(sub_dict["service"])
@@ -947,15 +938,15 @@ class HTTPAPGServer(web_resource.Resource):
url = self.get_canonical_url(request)
return {
- "@context": ["https://www.w3.org/ns/activitystreams"],
- "type": "OrderedCollection",
- "id": url,
- "totalItems": len(subscriptions),
- "first": {
- "type": "OrderedCollectionPage",
+ "@context": ["https://www.w3.org/ns/activitystreams"],
+ "type": "OrderedCollection",
"id": url,
- "orderedItems": following
- }
+ "totalItems": len(subscriptions),
+ "first": {
+ "type": "OrderedCollectionPage",
+ "id": url,
+ "orderedItems": following,
+ },
}
def _get_to_log(
@@ -965,24 +956,23 @@ class HTTPAPGServer(web_resource.Resource):
) -> List[str]:
"""Get base data to logs in verbose mode"""
from pprint import pformat
+
to_log = [
"",
- f"<<< got {request.method.decode()} request - {request.uri.decode()}"
+ f"<<< got {request.method.decode()} request - {request.uri.decode()}",
]
if data is not None:
to_log.append(pformat(data))
- if self.apg.verbose>=3:
+ if self.apg.verbose >= 3:
headers = "\n".join(
f" {k.decode()}: {v.decode()}"
- for k,v in request.getAllHeaders().items()
+ for k, v in request.getAllHeaders().items()
)
to_log.append(f" headers:\n{headers}")
return to_log
def get_requestor_actor_id(
- self,
- data: dict|None = None,
- uri_extra_args: list[str]|None = None
+ self, data: dict | None = None, uri_extra_args: list[str] | None = None
) -> str:
"""Find the actor ID of the requestor.
@@ -1034,33 +1024,30 @@ class HTTPAPGServer(web_resource.Resource):
# Still nothing, we'll have to use a generic actor.
log.warning(
- "Can't find destinee in \"to\" field, using generic requestor for signature."
- )
- return self.apg.build_apurl(
- TYPE_ACTOR, f"libervia@{self.apg.public_url}"
+ 'Can\'t find destinee in "to" field, using generic requestor for signature.'
)
+ return self.apg.build_apurl(TYPE_ACTOR, f"libervia@{self.apg.public_url}")
async def ap_request(
self,
request: "HTTPRequest",
- data: dict|None = None,
- signing_actor: str|None = None,
- requestor_actor_id: str|None = None,
+ data: dict | None = None,
+ signing_actor: str | None = None,
+ requestor_actor_id: str | None = None,
) -> None:
if self.apg.verbose:
to_log = self._get_to_log(request, data)
path = request.path.decode()
- ap_url = parse.urljoin(
- f"https://{self.apg.public_url}",
- path
- )
+ ap_url = parse.urljoin(f"https://{self.apg.public_url}", path)
request_type, extra_args = self.apg.parse_apurl(ap_url)
header_accept = request.getHeader("accept") or ""
- if ((MEDIA_TYPE_AP not in header_accept
- and MEDIA_TYPE_AP_ALT not in header_accept
- and request_type in self.apg.html_redirect)):
+ if (
+ MEDIA_TYPE_AP not in header_accept
+ and MEDIA_TYPE_AP_ALT not in header_accept
+ and request_type in self.apg.html_redirect
+ ):
# this is not a AP request, and we have a redirections for it
kw = {}
if extra_args:
@@ -1081,14 +1068,14 @@ class HTTPAPGServer(web_resource.Resource):
if not filters:
break
# if we have filter, they must all match
- elif all(v in kw[k] for k,v in filters.items()):
+ elif all(v in kw[k] for k, v in filters.items()):
break
else:
# no redirection is matching
redirection = None
if redirection is not None:
- kw = {k: parse.quote(str(v), safe="") for k,v in kw.items()}
+ kw = {k: parse.quote(str(v), safe="") for k, v in kw.items()}
target_url = redirection["url"].format(**kw)
content = web_util.redirectTo(target_url.encode(), request)
request.write(content)
@@ -1096,9 +1083,7 @@ class HTTPAPGServer(web_resource.Resource):
return
if requestor_actor_id is None:
- requestor_actor_id = self.get_requestor_actor_id(
- data, extra_args
- )
+ requestor_actor_id = self.get_requestor_actor_id(data, extra_args)
if len(extra_args) == 0:
if request_type != "shared_inbox":
raise exceptions.DataError(f"Invalid request type: {request_type!r}")
@@ -1121,21 +1106,29 @@ class HTTPAPGServer(web_resource.Resource):
ap_account = extra_args[0]
account_jid, node = await self.apg.get_jid_and_node(ap_account)
if request_type not in AP_REQUEST_TYPES.get(
- request.method.decode().upper(), []
+ request.method.decode().upper(), []
):
raise exceptions.DataError(f"Invalid request type: {request_type!r}")
method = getattr(self, f"ap_{request_type}_request")
ret_data = await method(
- requestor_actor_id, request, data, account_jid, node, ap_account, ap_url, signing_actor
+ requestor_actor_id,
+ request,
+ data,
+ account_jid,
+ node,
+ ap_account,
+ ap_url,
+ signing_actor,
)
if ret_data is not None:
request.setHeader("content-type", CONTENT_TYPE_AP)
request.write(json.dumps(ret_data).encode())
if self.apg.verbose:
to_log.append(f"--- RET (code: {request.code})---")
- if self.apg.verbose>=2:
+ if self.apg.verbose >= 2:
if ret_data is not None:
from pprint import pformat
+
to_log.append(f"{pformat(ret_data)}")
to_log.append("---")
log.info("\n".join(to_log))
@@ -1149,15 +1142,13 @@ class HTTPAPGServer(web_resource.Resource):
self.response_code(
request,
http.BAD_REQUEST,
- f"invalid body, was expecting a JSON object"
+ f"invalid body, was expecting a JSON object",
)
request.finish()
return
except (json.JSONDecodeError, ValueError) as e:
self.response_code(
- request,
- http.BAD_REQUEST,
- f"invalid json in inbox request: {e}"
+ request, http.BAD_REQUEST, f"invalid json in inbox request: {e}"
)
request.finish()
return
@@ -1185,18 +1176,12 @@ class HTTPAPGServer(web_resource.Resource):
to_log.append(f" body: {request.content.read()!r}")
request.content.seek(0)
log.info("\n".join(to_log))
- self.response_code(
- request,
- http.FORBIDDEN,
- f"invalid signature: {e}"
- )
+ self.response_code(request, http.FORBIDDEN, f"invalid signature: {e}")
request.finish()
return
except Exception as e:
self.response_code(
- request,
- http.INTERNAL_SERVER_ERROR,
- f"Can't check signature: {e}"
+ request, http.INTERNAL_SERVER_ERROR, f"Can't check signature: {e}"
)
request.finish()
return
@@ -1219,10 +1204,7 @@ class HTTPAPGServer(web_resource.Resource):
self._on_request_error(failure.Failure(e), request)
async def check_signing_actor(
- self,
- requestor_actor_id: str,
- data: dict,
- signing_actor: str
+ self, requestor_actor_id: str, data: dict, signing_actor: str
) -> None:
"""That that signing actor correspond to actor declared in data
@@ -1241,9 +1223,7 @@ class HTTPAPGServer(web_resource.Resource):
)
async def check_signature(
- self,
- requestor_actor_id: str,
- request: "HTTPRequest"
+ self, requestor_actor_id: str, request: "HTTPRequest"
) -> str:
"""Check and validate HTTP signature
@@ -1264,10 +1244,11 @@ class HTTPAPGServer(web_resource.Resource):
except KeyError:
raise exceptions.EncryptionError('"keyId" is missing from signature')
algorithm = sign_data.get("algorithm", HS2019)
- signed_headers = sign_data.get(
- "headers",
- "(created)" if algorithm==HS2019 else "date"
- ).lower().split()
+ signed_headers = (
+ sign_data.get("headers", "(created)" if algorithm == HS2019 else "date")
+ .lower()
+ .split()
+ )
try:
headers_to_check = SIGN_HEADERS[None] + SIGN_HEADERS[request.method]
except KeyError:
@@ -1284,9 +1265,7 @@ class HTTPAPGServer(web_resource.Resource):
f"at least one of following header must be signed: {header}"
)
elif header not in signed_headers:
- raise exceptions.EncryptionError(
- f"the {header!r} header must be signed"
- )
+ raise exceptions.EncryptionError(f"the {header!r} header must be signed")
body = request.content.read()
request.content.seek(0)
@@ -1329,7 +1308,8 @@ class HTTPAPGServer(web_resource.Resource):
if forwarded is not None:
try:
host = [
- f[5:] for f in forwarded.split(";")
+ f[5:]
+ for f in forwarded.split(";")
if f.startswith("host=")
][0] or None
except IndexError:
@@ -1342,7 +1322,8 @@ class HTTPAPGServer(web_resource.Resource):
value = host
elif to_sign == "digest":
hashes = {
- algo.lower(): hash_ for algo, hash_ in (
+ algo.lower(): hash_
+ for algo, hash_ in (
digest.split("=", 1) for digest in value.split(",")
)
}
@@ -1367,7 +1348,6 @@ class HTTPAPGServer(web_resource.Resource):
else:
created = date_utils.date_parse(headers["date"])
-
try:
expires = float(headers["expires"])
except KeyError:
@@ -1386,23 +1366,17 @@ class HTTPAPGServer(web_resource.Resource):
try:
return await self.apg.check_signature(
- requestor_actor_id,
- sign_data["signature"],
- key_id,
- headers
+ requestor_actor_id, sign_data["signature"], key_id, headers
)
except exceptions.EncryptionError:
- method, url = headers["(request-target)"].rsplit(' ', 1)
+ method, url = headers["(request-target)"].rsplit(" ", 1)
headers["(request-target)"] = f"{method} {parse.unquote(url)}"
log.debug(
"Using workaround for (request-target) encoding bug in signature, "
"see https://github.com/mastodon/mastodon/issues/18871"
)
return await self.apg.check_signature(
- requestor_actor_id,
- sign_data["signature"],
- key_id,
- headers
+ requestor_actor_id, sign_data["signature"], key_id, headers
)
def render(self, request):
diff --git a/libervia/backend/plugins/plugin_comp_ap_gateway/pubsub_service.py b/libervia/backend/plugins/plugin_comp_ap_gateway/pubsub_service.py
index 8b21f986..9b65cb03 100644
--- a/libervia/backend/plugins/plugin_comp_ap_gateway/pubsub_service.py
+++ b/libervia/backend/plugins/plugin_comp_ap_gateway/pubsub_service.py
@@ -37,11 +37,7 @@ from libervia.backend.tools.utils import ensure_deferred
from libervia.backend.tools.web import download_file
from libervia.backend.memory.sqla_mapping import PubsubSub, SubscriptionState
-from .constants import (
- TYPE_ACTOR,
- ST_AVATAR,
- MAX_AVATAR_SIZE
-)
+from .constants import TYPE_ACTOR, ST_AVATAR, MAX_AVATAR_SIZE
log = getLogger(__name__)
@@ -52,13 +48,14 @@ NODE_CONFIG = [
{"var": "pubsub#max_items", "value": "max"},
{"var": "pubsub#access_model", "type": "list-single", "value": "open"},
{"var": "pubsub#publish_model", "type": "list-single", "value": "open"},
-
]
NODE_CONFIG_VALUES = {c["var"]: c["value"] for c in NODE_CONFIG}
NODE_OPTIONS = {c["var"]: {} for c in NODE_CONFIG}
for c in NODE_CONFIG:
- NODE_OPTIONS[c["var"]].update({k:v for k,v in c.items() if k not in ("var", "value")})
+ NODE_OPTIONS[c["var"]].update(
+ {k: v for k, v in c.items() if k not in ("var", "value")}
+ )
class APPubsubService(rsm.PubSubService):
@@ -88,34 +85,30 @@ class APPubsubService(rsm.PubSubService):
in requestor
"""
if not recipient.user:
- raise error.StanzaError(
- "item-not-found",
- text="No user part specified"
- )
+ raise error.StanzaError("item-not-found", text="No user part specified")
requestor_actor_id = self.apg.build_apurl(TYPE_ACTOR, requestor.userhost())
recipient_account = self.apg._e.unescape(recipient.user)
- recipient_actor_id = await self.apg.get_ap_actor_id_from_account(recipient_account)
+ recipient_actor_id = await self.apg.get_ap_actor_id_from_account(
+ recipient_account
+ )
inbox = await self.apg.get_ap_inbox_from_id(recipient_actor_id, use_shared=False)
return requestor_actor_id, recipient_actor_id, inbox
-
@ensure_deferred
async def publish(self, requestor, service, nodeIdentifier, items):
if self.apg.local_only and not self.apg.is_local(requestor):
raise error.StanzaError(
- "forbidden",
- "Only local users can publish on this gateway."
+ "forbidden", "Only local users can publish on this gateway."
)
if not service.user:
raise error.StanzaError(
"bad-request",
- "You must specify an ActivityPub actor account in JID user part."
+ "You must specify an ActivityPub actor account in JID user part.",
)
ap_account = self.apg._e.unescape(service.user)
if ap_account.count("@") != 1:
raise error.StanzaError(
- "bad-request",
- f"{ap_account!r} is not a valid ActivityPub actor account."
+ "bad-request", f"{ap_account!r} is not a valid ActivityPub actor account."
)
client = self.apg.client.get_virtual_client(requestor)
@@ -130,21 +123,17 @@ class APPubsubService(rsm.PubSubService):
cached_node = await self.host.memory.storage.get_pubsub_node(
client, service, nodeIdentifier, with_subscriptions=True, create=True
)
- await self.host.memory.storage.cache_pubsub_items(
- client,
- cached_node,
- items
- )
+ await self.host.memory.storage.cache_pubsub_items(client, cached_node, items)
for subscription in cached_node.subscriptions:
if subscription.state != SubscriptionState.SUBSCRIBED:
continue
self.notifyPublish(
- service,
- nodeIdentifier,
- [(subscription.subscriber, None, items)]
+ service, nodeIdentifier, [(subscription.subscriber, None, items)]
)
- async def ap_following_2_elt(self, requestor_actor_id: str, ap_item: dict) -> domish.Element:
+ async def ap_following_2_elt(
+ self, requestor_actor_id: str, ap_item: dict
+ ) -> domish.Element:
"""Convert actor ID from following collection to XMPP item
@param requestor_actor_id: ID of the actor doing the request.
@@ -159,9 +148,7 @@ class APPubsubService(rsm.PubSubService):
return item_elt
async def ap_follower_2_elt(
- self,
- requestor_actor_id: str,
- ap_item: dict
+ self, requestor_actor_id: str, ap_item: dict
) -> domish.Element:
"""Convert actor ID from followers collection to XMPP item
@@ -175,9 +162,7 @@ class APPubsubService(rsm.PubSubService):
return item_elt
async def generate_v_card(
- self,
- requestor_actor_id: str,
- ap_account: str
+ self, requestor_actor_id: str, ap_account: str
) -> domish.Element:
"""Generate vCard4 (XEP-0292) item element from ap_account's metadata
@@ -186,8 +171,7 @@ class APPubsubService(rsm.PubSubService):
@return: - with the element
"""
actor_data = await self.apg.get_ap_actor_data_from_account(
- requestor_actor_id,
- ap_account
+ requestor_actor_id, ap_account
)
identity_data = {}
@@ -212,10 +196,7 @@ class APPubsubService(rsm.PubSubService):
return item_elt
async def get_avatar_data(
- self,
- client: SatXMPPEntity,
- requestor_actor_id: str,
- ap_account: str
+ self, client: SatXMPPEntity, requestor_actor_id: str, ap_account: str
) -> dict[str, Any]:
"""Retrieve actor's avatar if any, cache it and file actor_data
@@ -259,27 +240,21 @@ class APPubsubService(rsm.PubSubService):
avatar_data = {
"path": dest_path,
"filename": filename,
- 'media_type': image.guess_type(dest_path),
+ "media_type": image.guess_type(dest_path),
}
- await self.apg._i.cache_avatar(
- self.apg.IMPORT_NAME,
- avatar_data
- )
+ await self.apg._i.cache_avatar(self.apg.IMPORT_NAME, avatar_data)
else:
avatar_data = {
- "cache_uid": cache["uid"],
- "path": cache["path"],
- "media_type": cache["mime_type"]
- }
+ "cache_uid": cache["uid"],
+ "path": cache["path"],
+ "media_type": cache["mime_type"],
+ }
return avatar_data
async def generate_avatar_metadata(
- self,
- client: SatXMPPEntity,
- requestor_actor_id: str,
- ap_account: str
+ self, client: SatXMPPEntity, requestor_actor_id: str, ap_account: str
) -> domish.Element:
"""Generate the metadata element for user avatar
@@ -308,14 +283,11 @@ class APPubsubService(rsm.PubSubService):
"""
if not itemIdentifiers:
avatar_data = await self.get_avatar_data(
- client,
- requestor_actor_id,
- ap_account
+ client, requestor_actor_id, ap_account
)
if "base64" not in avatar_data:
await threads.deferToThread(
- self._blocking_b_6_4_encode_avatar,
- avatar_data
+ self._blocking_b_6_4_encode_avatar, avatar_data
)
else:
if len(itemIdentifiers) > 1:
@@ -327,10 +299,7 @@ class APPubsubService(rsm.PubSubService):
cache_data = self.apg.host.common_cache.get_metadata(item_id)
if cache_data is None:
raise error.StanzaError("item-not-found")
- avatar_data = {
- "cache_uid": item_id,
- "path": cache_data["path"]
- }
+ avatar_data = {"cache_uid": item_id, "path": cache_data["path"]}
await threads.deferToThread(self._blocking_b_6_4_encode_avatar, avatar_data)
return self.apg._a.build_item_data_elt(avatar_data)
@@ -343,7 +312,7 @@ class APPubsubService(rsm.PubSubService):
node: str,
maxItems: Optional[int],
itemIdentifiers: Optional[List[str]],
- rsm_req: Optional[rsm.RSMRequest]
+ rsm_req: Optional[rsm.RSMRequest],
) -> Tuple[List[domish.Element], Optional[rsm.RSMResponse]]:
if not service.user:
return [], None
@@ -353,8 +322,7 @@ class APPubsubService(rsm.PubSubService):
return [], None
requestor_actor_id = self.apg.build_apurl(
- TYPE_ACTOR,
- await self.apg.get_ap_account_from_jid_and_node(service, node)
+ TYPE_ACTOR, await self.apg.get_ap_account_from_jid_and_node(service, node)
)
# cached_node may be pre-filled with some nodes (e.g. attachments nodes),
@@ -379,17 +347,12 @@ class APPubsubService(rsm.PubSubService):
return [item_elt], None
elif node == self.apg._a.namespace_metadata:
item_elt = await self.generate_avatar_metadata(
- self.apg.client,
- requestor_actor_id,
- ap_account
+ self.apg.client, requestor_actor_id, ap_account
)
return [item_elt], None
elif node == self.apg._a.namespace_data:
item_elt = await self.generate_avatar_data(
- self.apg.client,
- requestor_actor_id,
- ap_account,
- itemIdentifiers
+ self.apg.client, requestor_actor_id, ap_account, itemIdentifiers
)
return [item_elt], None
elif self.apg._pa.is_attachment_node(node):
@@ -410,7 +373,7 @@ class APPubsubService(rsm.PubSubService):
raise error.StanzaError(
"feature-not-implemented",
text=f"AP Gateway {C.APP_VERSION} only supports "
- f"{self.apg._m.namespace} node for now"
+ f"{self.apg._m.namespace} node for now",
)
collection_name = "outbox"
use_cache = True
@@ -444,18 +407,26 @@ class APPubsubService(rsm.PubSubService):
if rsm_req is None:
if maxItems is None:
maxItems = 20
- kwargs.update({
- "max_items": maxItems,
- "chronological_pagination": False,
- })
+ kwargs.update(
+ {
+ "max_items": maxItems,
+ "chronological_pagination": False,
+ }
+ )
else:
- if len(
- [v for v in (rsm_req.after, rsm_req.before, rsm_req.index)
- if v is not None]
- ) > 1:
+ if (
+ len(
+ [
+ v
+ for v in (rsm_req.after, rsm_req.before, rsm_req.index)
+ if v is not None
+ ]
+ )
+ > 1
+ ):
raise error.StanzaError(
"bad-request",
- text="You can't use after, before and index at the same time"
+ text="You can't use after, before and index at the same time",
)
kwargs.update({"max_items": rsm_req.max})
if rsm_req.after is not None:
@@ -476,25 +447,21 @@ class APPubsubService(rsm.PubSubService):
try:
parent_data = await self.apg.ap_get(parent_item, requestor_actor_id)
collection = await self.apg.ap_get_object(
- requestor_actor_id,
- parent_data.get("object", {}),
- "replies"
+ requestor_actor_id, parent_data.get("object", {}), "replies"
)
except Exception as e:
- raise error.StanzaError(
- "item-not-found",
- text=str(e)
- )
+ raise error.StanzaError("item-not-found", text=str(e))
else:
actor_data = await self.apg.get_ap_actor_data_from_account(
- requestor_actor_id,
- ap_account
+ requestor_actor_id, ap_account
+ )
+ collection = await self.apg.ap_get_object(
+ requestor_actor_id, actor_data, collection_name
)
- collection = await self.apg.ap_get_object(requestor_actor_id, actor_data, collection_name)
if not collection:
raise error.StanzaError(
"item-not-found",
- text=f"No collection found for node {node!r} (account: {ap_account})"
+ text=f"No collection found for node {node!r} (account: {ap_account})",
)
kwargs["parser"] = parser
@@ -528,17 +495,15 @@ class APPubsubService(rsm.PubSubService):
else:
try:
subscription = next(
- s for s in node.subscriptions
+ s
+ for s in node.subscriptions
if s.subscriber == requestor.userhostJID()
)
except StopIteration:
subscription = None
if subscription is None:
- subscription = PubsubSub(
- subscriber=requestor.userhostJID(),
- state=sub_state
- )
+ subscription = PubsubSub(subscriber=requestor.userhostJID(), state=sub_state)
node.subscriptions.append(subscription)
await self.host.memory.storage.add(node)
else:
@@ -586,11 +551,7 @@ class APPubsubService(rsm.PubSubService):
data = self.apg.create_activity(
"Undo",
req_actor_id,
- self.apg.create_activity(
- "Follow",
- req_actor_id,
- recip_actor_id
- )
+ self.apg.create_activity("Follow", req_actor_id, recip_actor_id),
)
resp = await self.apg.sign_and_post(inbox, req_actor_id, data)
@@ -602,10 +563,7 @@ class APPubsubService(rsm.PubSubService):
return NODE_OPTIONS
def getConfiguration(
- self,
- requestor: jid.JID,
- service: jid.JID,
- nodeIdentifier: str
+ self, requestor: jid.JID, service: jid.JID, nodeIdentifier: str
) -> defer.Deferred:
return defer.succeed(NODE_CONFIG_VALUES)
@@ -615,12 +573,9 @@ class APPubsubService(rsm.PubSubService):
service: jid.JID,
nodeIdentifier: str,
pep: bool = False,
- recipient: Optional[jid.JID] = None
+ recipient: Optional[jid.JID] = None,
) -> Optional[dict]:
if not nodeIdentifier:
return None
- info = {
- "type": "leaf",
- "meta-data": NODE_CONFIG
- }
+ info = {"type": "leaf", "meta-data": NODE_CONFIG}
return info
diff --git a/libervia/backend/plugins/plugin_comp_ap_gateway/regex.py b/libervia/backend/plugins/plugin_comp_ap_gateway/regex.py
index 5e10c38e..d90bc114 100644
--- a/libervia/backend/plugins/plugin_comp_ap_gateway/regex.py
+++ b/libervia/backend/plugins/plugin_comp_ap_gateway/regex.py
@@ -42,17 +42,17 @@ import re
# obs-text = %x80-FF
# ---
-ows = '[ \t]*'
-bws = f'{ows}'
-obs_text = '[\\x80-\\xff]'
-qdtext = f'([\t !#-\\[\\]-~]|{obs_text})'
-quoted_pair = f'\\\\([\t !-~]|{obs_text})'
+ows = "[ \t]*"
+bws = f"{ows}"
+obs_text = "[\\x80-\\xff]"
+qdtext = f"([\t !#-\\[\\]-~]|{obs_text})"
+quoted_pair = f"\\\\([\t !-~]|{obs_text})"
quoted_string = f'"({qdtext}|{quoted_pair})*"'
tchar = "([!#$%&`*+\\-.^_]|\\\\'|[|~0-9a-zA-Z])"
-token = f'({tchar})+'
+token = f"({tchar})+"
RE_SIG_PARAM = re.compile(
- f'(?P{token}{bws})={bws}'
- f'((?P{token})|(?P{quoted_string}))'
+ f"(?P{token}{bws})={bws}"
+ f"((?P{token})|(?P{quoted_string}))"
)
diff --git a/libervia/backend/plugins/plugin_comp_file_sharing.py b/libervia/backend/plugins/plugin_comp_file_sharing.py
index dd0403b2..f29ea0c5 100644
--- a/libervia/backend/plugins/plugin_comp_file_sharing.py
+++ b/libervia/backend/plugins/plugin_comp_file_sharing.py
@@ -82,10 +82,7 @@ OVER_QUOTA_TXT = D_(
"{used_space}, you can't upload {file_size} more."
)
-HTTP_VERSION = unicodedata.normalize(
- 'NFKD',
- f"{C.APP_NAME} file sharing {C.APP_VERSION}"
-)
+HTTP_VERSION = unicodedata.normalize("NFKD", f"{C.APP_NAME} file sharing {C.APP_VERSION}")
class HTTPFileServer(resource.Resource):
@@ -94,32 +91,32 @@ class HTTPFileServer(resource.Resource):
def errorPage(self, request, code):
request.setResponseCode(code)
if code == http.BAD_REQUEST:
- brief = 'Bad Request'
+ brief = "Bad Request"
details = "Your request is invalid"
elif code == http.FORBIDDEN:
- brief = 'Forbidden'
+ brief = "Forbidden"
details = "You're not allowed to use this resource"
elif code == http.NOT_FOUND:
- brief = 'Not Found'
+ brief = "Not Found"
details = "No resource found at this URL"
else:
- brief = 'Error'
+ brief = "Error"
details = "This resource can't be used"
log.error(f"Unexpected return code used: {code}")
log.warning(
- f'Error returned while trying to access url {request.uri.decode()}: '
+ f"Error returned while trying to access url {request.uri.decode()}: "
f'"{brief}" ({code}): {details}'
)
return resource.ErrorPage(code, brief, details).render(request)
def get_disposition_type(self, media_type, media_subtype):
- if media_type in ('image', 'video'):
- return 'inline'
- elif media_type == 'application' and media_subtype == 'pdf':
- return 'inline'
+ if media_type in ("image", "video"):
+ return "inline"
+ elif media_type == "application" and media_subtype == "pdf":
+ return "inline"
else:
- return 'attachment'
+ return "attachment"
def render(self, request):
request.setHeader("server", HTTP_VERSION)
@@ -127,13 +124,14 @@ class HTTPFileServer(resource.Resource):
request.setHeader("Access-Control-Allow-Methods", "OPTIONS, HEAD, GET, PUT")
request.setHeader(
"Access-Control-Allow-Headers",
- "Content-Type, Range, Xmpp-File-Path, Xmpp-File-No-Http")
+ "Content-Type, Range, Xmpp-File-Path, Xmpp-File-No-Http",
+ )
request.setHeader("Access-Control-Allow-Credentials", "true")
request.setHeader("Accept-Ranges", "bytes")
request.setHeader(
- "Access-Control-Expose-Headers",
- "Date, Content-Length, Content-Range")
+ "Access-Control-Expose-Headers", "Date, Content-Length, Content-Range"
+ )
return super().render(request)
def render_OPTIONS(self, request):
@@ -157,7 +155,8 @@ class HTTPFileServer(resource.Resource):
request.finish()
return
found_files = await request.file_sharing.host.memory.get_files(
- client=None, peer_jid=None, perms_to_check=None, public_id=upload_id)
+ client=None, peer_jid=None, perms_to_check=None, public_id=upload_id
+ )
if not found_files:
request.write(self.errorPage(request, http.NOT_FOUND))
request.finish()
@@ -166,22 +165,22 @@ class HTTPFileServer(resource.Resource):
log.error(f"more that one files found for public id {upload_id!r}")
found_file = found_files[0]
- file_path = request.file_sharing.files_path/found_file['file_hash']
+ file_path = request.file_sharing.files_path / found_file["file_hash"]
file_res = static.File(file_path)
file_res.type = f'{found_file["media_type"]}/{found_file["media_subtype"]}'
- file_res.encoding = file_res.contentEncodings.get(Path(found_file['name']).suffix)
+ file_res.encoding = file_res.contentEncodings.get(Path(found_file["name"]).suffix)
disp_type = self.get_disposition_type(
- found_file['media_type'], found_file['media_subtype'])
+ found_file["media_type"], found_file["media_subtype"]
+ )
# the URL is percent encoded, and not all browsers/tools unquote the file name,
# thus we add a content disposition header
request.setHeader(
- 'Content-Disposition',
- f"{disp_type}; filename*=UTF-8''{quote(found_file['name'])}"
+ "Content-Disposition",
+ f"{disp_type}; filename*=UTF-8''{quote(found_file['name'])}",
)
# cf. https://xmpp.org/extensions/xep-0363.html#server
request.setHeader(
- 'Content-Security-Policy',
- "default-src 'none'; frame-ancestors 'none';"
+ "Content-Security-Policy", "default-src 'none'; frame-ancestors 'none';"
)
ret = file_res.render(request)
if ret != server.NOT_DONE_YET:
@@ -215,7 +214,7 @@ class HTTPFileServer(resource.Resource):
if path:
path = unquote(path)
else:
- path = "/uploads"
+ path = "/uploads"
if request.getHeader("Xmpp-File-No-Http") is not None:
public_id = None
else:
@@ -225,11 +224,14 @@ class HTTPFileServer(resource.Resource):
"name": unquote(upload_request.filename),
"mime_type": upload_request.content_type,
"size": upload_request.size,
- "path": path
+ "path": path,
}
await request.file_sharing.register_received_file(
- client, upload_request.from_, file_data, tmp_file_path,
+ client,
+ upload_request.from_,
+ file_data,
+ tmp_file_path,
public_id=public_id,
)
@@ -256,11 +258,13 @@ class FileSharingRequest(server.Request):
# we normalise the path
path = urlparse(path.decode()).path
try:
- __, upload_id, filename = path.split('/')
+ __, upload_id, filename = path.split("/")
except ValueError:
raise exceptions.DataError("no enought path elements")
if len(upload_id) < 10:
- raise exceptions.DataError(f"invalid upload ID received for a PUT: {upload_id!r}")
+ raise exceptions.DataError(
+ f"invalid upload ID received for a PUT: {upload_id!r}"
+ )
self._upload_data = (upload_id, filename)
return self._upload_data
@@ -276,11 +280,11 @@ class FileSharingRequest(server.Request):
def refuse_request(self):
if self.content is not None:
self.content.close()
- self.content = open(os.devnull, 'w+b')
+ self.content = open(os.devnull, "w+b")
self.channel._respondToBadRequestAndDisconnect()
def gotLength(self, length):
- if self.channel._command.decode().upper() == 'PUT':
+ if self.channel._command.decode().upper() == "PUT":
# for PUT we check early if upload_id is fine, to avoid buffering a file we'll refuse
# we buffer the file in component's TMP_BUFFER_DIR, so we just have to rename it at the end
try:
@@ -289,9 +293,13 @@ class FileSharingRequest(server.Request):
log.warning(f"Invalid PUT request, we stop here: {e}")
return self.refuse_request()
try:
- client, upload_request, timer = self.file_sharing.expected_uploads.pop(upload_id)
+ client, upload_request, timer = self.file_sharing.expected_uploads.pop(
+ upload_id
+ )
except KeyError:
- log.warning(f"unknown (expired?) upload ID received for a PUT: {upload_id!r}")
+ log.warning(
+ f"unknown (expired?) upload ID received for a PUT: {upload_id!r}"
+ )
return self.refuse_request()
if not timer.active:
@@ -309,10 +317,9 @@ class FileSharingRequest(server.Request):
self.upload_request_data = (client, upload_request)
- file_tmp_path = files_utils.get_unique_name(
- self.file_tmp_dir/upload_id)
+ file_tmp_path = files_utils.get_unique_name(self.file_tmp_dir / upload_id)
- self.content = open(file_tmp_path, 'w+b')
+ self.content = open(file_tmp_path, "w+b")
else:
return super().gotLength(length)
@@ -331,8 +338,8 @@ class FileSharingSite(server.Site):
super().__init__(HTTPFileServer())
def getContentFile(self, length):
- file_tmp_path = self.file_tmp_dir/shortuuid.uuid()
- return open(file_tmp_path, 'w+b')
+ file_tmp_path = self.file_tmp_dir / shortuuid.uuid()
+ return open(file_tmp_path, "w+b")
class FileSharing:
@@ -356,37 +363,53 @@ class FileSharing:
self._t = self.host.plugins["XEP-0264"]
self._hu = self.host.plugins["XEP-0363"]
self._hu.register_handler(self._on_http_upload)
- self.host.trigger.add_with_check("FILE_getDestDir", self, self._get_dest_dir_trigger)
self.host.trigger.add_with_check(
- "XEP-0234_fileSendingRequest", self, self._file_sending_request_trigger, priority=1000
+ "FILE_getDestDir", self, self._get_dest_dir_trigger
+ )
+ self.host.trigger.add_with_check(
+ "XEP-0234_fileSendingRequest",
+ self,
+ self._file_sending_request_trigger,
+ priority=1000,
+ )
+ self.host.trigger.add_with_check(
+ "XEP-0234_buildFileElement", self, self._add_file_metadata_elts
+ )
+ self.host.trigger.add_with_check(
+ "XEP-0234_parseFileElement", self, self._get_file_metadata_elts
+ )
+ self.host.trigger.add_with_check(
+ "XEP-0329_compGetFilesFromNode", self, self._add_file_metadata
)
- self.host.trigger.add_with_check("XEP-0234_buildFileElement", self, self._add_file_metadata_elts)
- self.host.trigger.add_with_check("XEP-0234_parseFileElement", self, self._get_file_metadata_elts)
- self.host.trigger.add_with_check("XEP-0329_compGetFilesFromNode", self, self._add_file_metadata)
self.host.trigger.add_with_check(
"XEP-0329_compGetFilesFromNode_build_directory",
self,
- self._add_directory_metadata_elts)
+ self._add_directory_metadata_elts,
+ )
self.host.trigger.add_with_check(
- "XEP-0329_parseResult_directory",
- self,
- self._get_directory_metadata_elts)
+ "XEP-0329_parseResult_directory", self, self._get_directory_metadata_elts
+ )
self.files_path = self.host.get_local_path(None, C.FILES_DIR)
- self.http_port = int(self.host.memory.config_get(
- 'component file-sharing', 'http_upload_port', 8888))
+ self.http_port = int(
+ self.host.memory.config_get(
+ "component file-sharing", "http_upload_port", 8888
+ )
+ )
connection_type = self.host.memory.config_get(
- 'component file-sharing', 'http_upload_connection_type', 'https')
- if connection_type not in ('http', 'https'):
+ "component file-sharing", "http_upload_connection_type", "https"
+ )
+ if connection_type not in ("http", "https"):
raise exceptions.ConfigError(
'bad http_upload_connection_type, you must use one of "http" or "https"'
)
self.server = FileSharingSite(self)
self.expected_uploads = {}
- if connection_type == 'http':
+ if connection_type == "http":
reactor.listenTCP(self.http_port, self.server)
else:
options = tls.get_options_from_config(
- self.host.memory.config, "component file-sharing")
+ self.host.memory.config, "component file-sharing"
+ )
tls.tls_options_check(options)
context_factory = tls.get_tls_context_factory(options)
reactor.listenSSL(self.http_port, self.server, context_factory)
@@ -400,7 +423,8 @@ class FileSharing:
self.init()
public_base_url = self.host.memory.config_get(
- 'component file-sharing', 'http_upload_public_facing_url')
+ "component file-sharing", "http_upload_public_facing_url"
+ )
if public_base_url is None:
client._file_sharing_base_url = f"https://{client.host}:{self.http_port}"
else:
@@ -441,7 +465,8 @@ class FileSharing:
thumbnails.append({"id": thumb_id, "size": thumb_size})
async def register_received_file(
- self, client, peer_jid, file_data, file_path, public_id=None, extra=None):
+ self, client, peer_jid, file_data, file_path, public_id=None, extra=None
+ ):
"""Post file reception tasks
once file is received, this method create hash/thumbnails if necessary
@@ -463,9 +488,9 @@ class FileSharing:
file_hash = file_data["hash_hasher"].hexdigest()
else:
hasher = self._h.get_hasher(HASH_ALGO)
- with file_path.open('rb') as f:
+ with file_path.open("rb") as f:
file_hash = await self._h.calculate_hash(f, hasher)
- final_path = self.files_path/file_hash
+ final_path = self.files_path / file_hash
if final_path.is_file():
log.debug(
@@ -490,8 +515,11 @@ class FileSharing:
try:
await video.get_thumbnail(final_path, thumb_path)
except Exception as e:
- log.warning(_("Can't get thumbnail for {final_path}: {e}").format(
- final_path=final_path, e=e))
+ log.warning(
+ _("Can't get thumbnail for {final_path}: {e}").format(
+ final_path=final_path, e=e
+ )
+ )
else:
await self.generate_thumbnails(extra, thumb_path)
@@ -534,14 +562,15 @@ class FileSharing:
text=OVER_QUOTA_TXT.format(
quota=utils.get_human_size(quota),
used_space=utils.get_human_size(used_space),
- file_size=utils.get_human_size(file_data['size'])
- )
+ file_size=utils.get_human_size(file_data["size"]),
+ ),
)
file_tmp_dir = self.host.get_local_path(
None, C.FILES_TMP_DIR, peer_jid.userhost(), component=True
)
- file_tmp_path = file_data['file_path'] = files_utils.get_unique_name(
- file_tmp_dir/filename)
+ file_tmp_path = file_data["file_path"] = files_utils.get_unique_name(
+ file_tmp_dir / filename
+ )
transfer_data["finished_d"].addCallback(
lambda __: defer.ensureDeferred(
@@ -559,8 +588,8 @@ class FileSharing:
):
"""This method retrieve a file on request, and send if after checking permissions"""
peer_jid = session["peer_jid"]
- if session['local_jid'].user:
- owner = client.get_owner_from_jid(session['local_jid'])
+ if session["local_jid"].user:
+ owner = client.get_owner_from_jid(session["local_jid"])
else:
owner = peer_jid
try:
@@ -592,9 +621,10 @@ class FileSharing:
# we only use the first found file
found_file = found_files[0]
- if found_file['type'] != C.FILE_TYPE_FILE:
- raise TypeError("a file was expected, type is {type_}".format(
- type_=found_file['type']))
+ if found_file["type"] != C.FILE_TYPE_FILE:
+ raise TypeError(
+ "a file was expected, type is {type_}".format(type_=found_file["type"])
+ )
file_hash = found_file["file_hash"]
file_path = self.files_path / file_hash
file_data["hash_hasher"] = hasher = self._h.get_hasher(found_file["hash_algo"])
@@ -624,9 +654,11 @@ class FileSharing:
else:
return (
False,
- defer.ensureDeferred(self._retrieve_files(
- client, session, content_data, content_name, file_data, file_elt
- )),
+ defer.ensureDeferred(
+ self._retrieve_files(
+ client, session, content_data, content_name, file_data, file_elt
+ )
+ ),
)
## HTTP Upload ##
@@ -639,7 +671,7 @@ class FileSharing:
async def _on_http_upload(self, client, request):
# filename should be already cleaned, but it's better to double check
- assert '/' not in request.filename
+ assert "/" not in request.filename
# client._file_sharing_allowed_hosts is set in plugin XEP-0329
if request.from_.host not in client._file_sharing_allowed_hosts:
raise error.StanzaError("forbidden")
@@ -654,13 +686,15 @@ class FileSharing:
text=OVER_QUOTA_TXT.format(
quota=utils.get_human_size(quota),
used_space=utils.get_human_size(used_space),
- file_size=utils.get_human_size(request.size)
+ file_size=utils.get_human_size(request.size),
+ ),
+ appCondition=self._hu.get_file_too_large_elt(
+ max(quota - used_space, 0)
),
- appCondition = self._hu.get_file_too_large_elt(max(quota - used_space, 0))
)
upload_id = shortuuid.ShortUUID().random(length=30)
- assert '/' not in upload_id
+ assert "/" not in upload_id
timer = reactor.callLater(30, self._purge_slot, upload_id)
self.expected_uploads[upload_id] = (client, request, timer)
url = urljoin(client._file_sharing_base_url, f"{upload_id}/{request.filename}")
@@ -675,7 +709,7 @@ class FileSharing:
def _add_file_metadata_elts(self, client, file_elt, extra_args):
# affiliation
- affiliation = extra_args.get('affiliation')
+ affiliation = extra_args.get("affiliation")
if affiliation is not None:
file_elt.addElement((NS_FS_AFFILIATION, "affiliation"), content=affiliation)
@@ -715,7 +749,8 @@ class FileSharing:
return True
def _add_file_metadata(
- self, client, iq_elt, iq_result_elt, owner, node_path, files_data):
+ self, client, iq_elt, iq_result_elt, owner, node_path, files_data
+ ):
for file_data in files_data:
file_data["comments_url"] = uri.build_xmpp_uri(
"pubsub",
@@ -725,22 +760,21 @@ class FileSharing:
return True
def _add_directory_metadata_elts(
- self, client, file_data, directory_elt, owner, node_path):
- affiliation = file_data.get('affiliation')
+ self, client, file_data, directory_elt, owner, node_path
+ ):
+ affiliation = file_data.get("affiliation")
if affiliation is not None:
directory_elt.addElement(
- (NS_FS_AFFILIATION, "affiliation"),
- content=affiliation
+ (NS_FS_AFFILIATION, "affiliation"), content=affiliation
)
- def _get_directory_metadata_elts(
- self, client, elt, file_data):
+ def _get_directory_metadata_elts(self, client, elt, file_data):
try:
affiliation_elt = next(elt.elements(NS_FS_AFFILIATION, "affiliation"))
except StopIteration:
pass
else:
- file_data['affiliation'] = str(affiliation_elt)
+ file_data["affiliation"] = str(affiliation_elt)
class Comments_handler(pubsub.PubSubService):
@@ -841,7 +875,9 @@ class Comments_handler(pubsub.PubSubService):
peer_jid = None
else:
peer_jid = requestor.userhost()
- update_cb = partial(self.comments_update, new_comments=comments, peer_jid=peer_jid)
+ update_cb = partial(
+ self.comments_update, new_comments=comments, peer_jid=peer_jid
+ )
try:
await self.host.memory.file_update(file_id, "extra", update_cb)
except exceptions.PermissionError:
diff --git a/libervia/backend/plugins/plugin_comp_file_sharing_management.py b/libervia/backend/plugins/plugin_comp_file_sharing_management.py
index 10306cd3..f16b1825 100644
--- a/libervia/backend/plugins/plugin_comp_file_sharing_management.py
+++ b/libervia/backend/plugins/plugin_comp_file_sharing_management.py
@@ -85,22 +85,30 @@ class FileSharingManagement(object):
def profile_connected(self, client):
self._c.add_ad_hoc_command(
- client, self._on_change_file, "Change Permissions of File(s)",
+ client,
+ self._on_change_file,
+ "Change Permissions of File(s)",
node=NS_FILE_MANAGEMENT_PERM,
allowed_magics=C.ENTITY_ALL,
)
self._c.add_ad_hoc_command(
- client, self._on_delete_file, "Delete File(s)",
+ client,
+ self._on_delete_file,
+ "Delete File(s)",
node=NS_FILE_MANAGEMENT_DELETE,
allowed_magics=C.ENTITY_ALL,
)
self._c.add_ad_hoc_command(
- client, self._on_gen_thumbnails, "Generate Thumbnails",
+ client,
+ self._on_gen_thumbnails,
+ "Generate Thumbnails",
node=NS_FILE_MANAGEMENT_THUMB,
allowed_magics=C.ENTITY_ALL,
)
self._c.add_ad_hoc_command(
- client, self._on_quota, "Get Quota",
+ client,
+ self._on_quota,
+ "Get Quota",
node=NS_FILE_MANAGEMENT_QUOTA,
allowed_magics=C.ENTITY_ALL,
)
@@ -108,12 +116,14 @@ class FileSharingManagement(object):
def _delete(self, service_jid_s, path, namespace, profile):
client = self.host.get_client(profile)
service_jid = jid.JID(service_jid_s) if service_jid_s else None
- return defer.ensureDeferred(self._c.sequence(
- client,
- [{"path": path, "namespace": namespace}, {"confirm": True}],
- NS_FILE_MANAGEMENT_DELETE,
- service_jid,
- ))
+ return defer.ensureDeferred(
+ self._c.sequence(
+ client,
+ [{"path": path, "namespace": namespace}, {"confirm": True}],
+ NS_FILE_MANAGEMENT_DELETE,
+ service_jid,
+ )
+ )
def _err(self, reason):
"""Helper method to get argument to return for error
@@ -133,17 +143,14 @@ class FileSharingManagement(object):
@return (tuple): arguments to use in defer.returnValue
"""
status = self._c.STATUS.EXECUTING
- form = data_form.Form("form", title="File Management",
- formNamespace=NS_FILE_MANAGEMENT)
-
- field = data_form.Field(
- "text-single", "path", required=True
+ form = data_form.Form(
+ "form", title="File Management", formNamespace=NS_FILE_MANAGEMENT
)
+
+ field = data_form.Field("text-single", "path", required=True)
form.addField(field)
- field = data_form.Field(
- "text-single", "namespace", required=False
- )
+ field = data_form.Field("text-single", "namespace", required=False)
form.addField(field)
payload = form.toElement()
@@ -159,17 +166,17 @@ class FileSharingManagement(object):
"""
fields = command_form.fields
try:
- path = fields['path'].value.strip()
- namespace = fields['namespace'].value or None
+ path = fields["path"].value.strip()
+ namespace = fields["namespace"].value or None
except KeyError:
self._c.ad_hoc_error(self._c.ERROR.BAD_PAYLOAD)
if not path:
self._c.ad_hoc_error(self._c.ERROR.BAD_PAYLOAD)
- requestor = session_data['requestor']
+ requestor = session_data["requestor"]
requestor_bare = requestor.userhostJID()
- path = path.rstrip('/')
+ path = path.rstrip("/")
parent_path, basename = os.path.split(path)
# TODO: if parent_path and basename are empty, we ask for root directory
@@ -177,35 +184,37 @@ class FileSharingManagement(object):
try:
found_files = await self.host.memory.get_files(
- client, requestor_bare, path=parent_path, name=basename,
- namespace=namespace)
+ client,
+ requestor_bare,
+ path=parent_path,
+ name=basename,
+ namespace=namespace,
+ )
found_file = found_files[0]
except (exceptions.NotFound, IndexError):
raise WorkflowError(self._err(_("file not found")))
except exceptions.PermissionError:
raise WorkflowError(self._err(_("forbidden")))
- if found_file['owner'] != requestor_bare:
+ if found_file["owner"] != requestor_bare:
# only owner can manage files
log.warning(_("Only owner can manage files"))
raise WorkflowError(self._err(_("forbidden")))
- session_data['found_file'] = found_file
- session_data['namespace'] = namespace
+ session_data["found_file"] = found_file
+ session_data["namespace"] = namespace
return found_file
def _update_read_permission(self, access, allowed_jids):
if not allowed_jids:
if C.ACCESS_PERM_READ in access:
del access[C.ACCESS_PERM_READ]
- elif allowed_jids == 'PUBLIC':
- access[C.ACCESS_PERM_READ] = {
- "type": C.ACCESS_TYPE_PUBLIC
- }
+ elif allowed_jids == "PUBLIC":
+ access[C.ACCESS_PERM_READ] = {"type": C.ACCESS_TYPE_PUBLIC}
else:
access[C.ACCESS_PERM_READ] = {
"type": C.ACCESS_TYPE_WHITELIST,
- "jids": [j.full() for j in allowed_jids]
+ "jids": [j.full() for j in allowed_jids],
}
async def _update_dir(self, client, requestor, namespace, file_data, allowed_jids):
@@ -214,19 +223,25 @@ class FileSharingManagement(object):
@param file_data(dict): metadata of the file
@param allowed_jids(list[jid.JID]): list of entities allowed to read the file
"""
- assert file_data['type'] == C.FILE_TYPE_DIRECTORY
+ assert file_data["type"] == C.FILE_TYPE_DIRECTORY
files_data = await self.host.memory.get_files(
- client, requestor, parent=file_data['id'], namespace=namespace)
+ client, requestor, parent=file_data["id"], namespace=namespace
+ )
for file_data in files_data:
- if not file_data['access'].get(C.ACCESS_PERM_READ, {}):
- log.debug("setting {perm} read permission for {name}".format(
- perm=allowed_jids, name=file_data['name']))
+ if not file_data["access"].get(C.ACCESS_PERM_READ, {}):
+ log.debug(
+ "setting {perm} read permission for {name}".format(
+ perm=allowed_jids, name=file_data["name"]
+ )
+ )
await self.host.memory.file_update(
- file_data['id'], 'access',
- partial(self._update_read_permission, allowed_jids=allowed_jids))
- if file_data['type'] == C.FILE_TYPE_DIRECTORY:
- await self._update_dir(client, requestor, namespace, file_data, 'PUBLIC')
+ file_data["id"],
+ "access",
+ partial(self._update_read_permission, allowed_jids=allowed_jids),
+ )
+ if file_data["type"] == C.FILE_TYPE_DIRECTORY:
+ await self._update_dir(client, requestor, namespace, file_data, "PUBLIC")
async def _on_change_file(self, client, command_elt, session_data, action, node):
try:
@@ -235,8 +250,8 @@ class FileSharingManagement(object):
except StopIteration:
command_form = None
- found_file = session_data.get('found_file')
- requestor = session_data['requestor']
+ found_file = session_data.get("found_file")
+ requestor = session_data["requestor"]
requestor_bare = requestor.userhostJID()
if command_form is None or len(command_form.fields) == 0:
@@ -251,31 +266,39 @@ class FileSharingManagement(object):
return e.err_args
# management request
- if found_file['type'] == C.FILE_TYPE_DIRECTORY:
+ if found_file["type"] == C.FILE_TYPE_DIRECTORY:
instructions = D_("Please select permissions for this directory")
else:
instructions = D_("Please select permissions for this file")
- form = data_form.Form("form", title="File Management",
- instructions=[instructions],
- formNamespace=NS_FILE_MANAGEMENT)
+ form = data_form.Form(
+ "form",
+ title="File Management",
+ instructions=[instructions],
+ formNamespace=NS_FILE_MANAGEMENT,
+ )
field = data_form.Field(
- "text-multi", "read_allowed", required=False,
- desc='list of jids allowed to read this file (beside yourself), or '
- '"PUBLIC" to let a public access'
+ "text-multi",
+ "read_allowed",
+ required=False,
+ desc="list of jids allowed to read this file (beside yourself), or "
+ '"PUBLIC" to let a public access',
)
read_access = found_file["access"].get(C.ACCESS_PERM_READ, {})
- access_type = read_access.get('type', C.ACCESS_TYPE_WHITELIST)
+ access_type = read_access.get("type", C.ACCESS_TYPE_WHITELIST)
if access_type == C.ACCESS_TYPE_PUBLIC:
- field.values = ['PUBLIC']
+ field.values = ["PUBLIC"]
else:
- field.values = read_access.get('jids', [])
+ field.values = read_access.get("jids", [])
form.addField(field)
- if found_file['type'] == C.FILE_TYPE_DIRECTORY:
+ if found_file["type"] == C.FILE_TYPE_DIRECTORY:
field = data_form.Field(
- "boolean", "recursive", value=False, required=False,
+ "boolean",
+ "recursive",
+ value=False,
+ required=False,
desc="Files under it will be made public to follow this dir "
- "permission (only if they don't have already a permission set)."
+ "permission (only if they don't have already a permission set).",
)
form.addField(field)
@@ -286,42 +309,49 @@ class FileSharingManagement(object):
else:
# final phase, we'll do permission change here
try:
- read_allowed = command_form.fields['read_allowed']
+ read_allowed = command_form.fields["read_allowed"]
except KeyError:
self._c.ad_hoc_error(self._c.ERROR.BAD_PAYLOAD)
- if read_allowed.value == 'PUBLIC':
- allowed_jids = 'PUBLIC'
- elif read_allowed.value.strip() == '':
+ if read_allowed.value == "PUBLIC":
+ allowed_jids = "PUBLIC"
+ elif read_allowed.value.strip() == "":
allowed_jids = None
else:
try:
- allowed_jids = [jid.JID(v.strip()) for v in read_allowed.values
- if v.strip()]
+ allowed_jids = [
+ jid.JID(v.strip()) for v in read_allowed.values if v.strip()
+ ]
except RuntimeError as e:
- log.warning(_("Can't use read_allowed values: {reason}").format(
- reason=e))
+ log.warning(
+ _("Can't use read_allowed values: {reason}").format(reason=e)
+ )
self._c.ad_hoc_error(self._c.ERROR.BAD_PAYLOAD)
- if found_file['type'] == C.FILE_TYPE_FILE:
+ if found_file["type"] == C.FILE_TYPE_FILE:
await self.host.memory.file_update(
- found_file['id'], 'access',
- partial(self._update_read_permission, allowed_jids=allowed_jids))
+ found_file["id"],
+ "access",
+ partial(self._update_read_permission, allowed_jids=allowed_jids),
+ )
else:
try:
- recursive = command_form.fields['recursive']
+ recursive = command_form.fields["recursive"]
except KeyError:
self._c.ad_hoc_error(self._c.ERROR.BAD_PAYLOAD)
await self.host.memory.file_update(
- found_file['id'], 'access',
- partial(self._update_read_permission, allowed_jids=allowed_jids))
+ found_file["id"],
+ "access",
+ partial(self._update_read_permission, allowed_jids=allowed_jids),
+ )
if recursive:
# we set all file under the directory as public (if they haven't
# already a permission set), so allowed entities of root directory
# can read them.
- namespace = session_data['namespace']
+ namespace = session_data["namespace"]
await self._update_dir(
- client, requestor_bare, namespace, found_file, 'PUBLIC')
+ client, requestor_bare, namespace, found_file, "PUBLIC"
+ )
# job done, we can end the session
status = self._c.STATUS.COMPLETED
@@ -336,8 +366,8 @@ class FileSharingManagement(object):
except StopIteration:
command_form = None
- found_file = session_data.get('found_file')
- requestor = session_data['requestor']
+ found_file = session_data.get("found_file")
+ requestor = session_data["requestor"]
requestor_bare = requestor.userhostJID()
if command_form is None or len(command_form.fields) == 0:
@@ -350,18 +380,27 @@ class FileSharingManagement(object):
found_file = await self._get_file_data(client, session_data, command_form)
except WorkflowError as e:
return e.err_args
- if found_file['type'] == C.FILE_TYPE_DIRECTORY:
- msg = D_("Are you sure to delete directory {name} and all files and "
- "directories under it?").format(name=found_file['name'])
+ if found_file["type"] == C.FILE_TYPE_DIRECTORY:
+ msg = D_(
+ "Are you sure to delete directory {name} and all files and "
+ "directories under it?"
+ ).format(name=found_file["name"])
else:
- msg = D_("Are you sure to delete file {name}?"
- .format(name=found_file['name']))
- form = data_form.Form("form", title="File Management",
- instructions = [msg],
- formNamespace=NS_FILE_MANAGEMENT)
+ msg = D_(
+ "Are you sure to delete file {name}?".format(name=found_file["name"])
+ )
+ form = data_form.Form(
+ "form",
+ title="File Management",
+ instructions=[msg],
+ formNamespace=NS_FILE_MANAGEMENT,
+ )
field = data_form.Field(
- "boolean", "confirm", value=False, required=True,
- desc="check this box to confirm"
+ "boolean",
+ "confirm",
+ value=False,
+ required=True,
+ desc="check this box to confirm",
)
form.addField(field)
status = self._c.STATUS.EXECUTING
@@ -371,15 +410,16 @@ class FileSharingManagement(object):
else:
# final phase, we'll do deletion here
try:
- confirmed = C.bool(command_form.fields['confirm'].value)
+ confirmed = C.bool(command_form.fields["confirm"].value)
except KeyError:
self._c.ad_hoc_error(self._c.ERROR.BAD_PAYLOAD)
if not confirmed:
note = None
else:
- recursive = found_file['type'] == C.FILE_TYPE_DIRECTORY
+ recursive = found_file["type"] == C.FILE_TYPE_DIRECTORY
await self.host.memory.file_delete(
- client, requestor_bare, found_file['id'], recursive)
+ client, requestor_bare, found_file["id"], recursive
+ )
note = (self._c.NOTE.INFO, _("file deleted"))
status = self._c.STATUS.COMPLETED
payload = None
@@ -393,16 +433,17 @@ class FileSharingManagement(object):
@param file_data(dict): metadata of the file
"""
- if file_data['type'] == C.FILE_TYPE_DIRECTORY:
+ if file_data["type"] == C.FILE_TYPE_DIRECTORY:
sub_files_data = await self.host.memory.get_files(
- client, requestor, parent=file_data['id'], namespace=namespace)
+ client, requestor, parent=file_data["id"], namespace=namespace
+ )
for sub_file_data in sub_files_data:
await self._gen_thumbs(client, requestor, namespace, sub_file_data)
- elif file_data['type'] == C.FILE_TYPE_FILE:
- media_type = file_data['media_type']
- file_path = os.path.join(self.files_path, file_data['file_hash'])
- if media_type == 'image':
+ elif file_data["type"] == C.FILE_TYPE_FILE:
+ media_type = file_data["media_type"]
+ file_path = os.path.join(self.files_path, file_data["file_hash"])
+ if media_type == "image":
thumbnails = []
for max_thumb_size in self._t.SIZES:
@@ -414,20 +455,26 @@ class FileSharingManagement(object):
60 * 60 * 24 * 31 * 6,
)
except Exception as e:
- log.warning(_("Can't create thumbnail: {reason}")
- .format(reason=e))
+ log.warning(
+ _("Can't create thumbnail: {reason}").format(reason=e)
+ )
break
thumbnails.append({"id": thumb_id, "size": thumb_size})
await self.host.memory.file_update(
- file_data['id'], 'extra',
- partial(self._update_thumbs, thumbnails=thumbnails))
+ file_data["id"],
+ "extra",
+ partial(self._update_thumbs, thumbnails=thumbnails),
+ )
- log.info("thumbnails for [{file_name}] generated"
- .format(file_name=file_data['name']))
+ log.info(
+ "thumbnails for [{file_name}] generated".format(
+ file_name=file_data["name"]
+ )
+ )
else:
- log.warning("unmanaged file type: {type_}".format(type_=file_data['type']))
+ log.warning("unmanaged file type: {type_}".format(type_=file_data["type"]))
async def _on_gen_thumbnails(self, client, command_elt, session_data, action, node):
try:
@@ -436,8 +483,8 @@ class FileSharingManagement(object):
except StopIteration:
command_form = None
- found_file = session_data.get('found_file')
- requestor = session_data['requestor']
+ found_file = session_data.get("found_file")
+ requestor = session_data["requestor"]
if command_form is None or len(command_form.fields) == 0:
# root request
@@ -451,7 +498,7 @@ class FileSharingManagement(object):
return e.err_args
log.info("Generating thumbnails as requested")
- await self._gen_thumbs(client, requestor, found_file['namespace'], found_file)
+ await self._gen_thumbs(client, requestor, found_file["namespace"], found_file)
# job done, we can end the session
status = self._c.STATUS.COMPLETED
@@ -460,7 +507,7 @@ class FileSharingManagement(object):
return (payload, status, None, note)
async def _on_quota(self, client, command_elt, session_data, action, node):
- requestor = session_data['requestor']
+ requestor = session_data["requestor"]
quota = self.host.plugins["file_sharing"].get_quota(client, requestor)
try:
size_used = await self.host.memory.file_get_used_space(client, requestor)
@@ -473,11 +520,10 @@ class FileSharingManagement(object):
note = (
self._c.NOTE.INFO,
_("You are currently using {size_used} on {size_quota}").format(
- size_used = utils.get_human_size(size_used),
- size_quota = (
- _("unlimited quota") if quota is None
- else utils.get_human_size(quota)
- )
- )
+ size_used=utils.get_human_size(size_used),
+ size_quota=(
+ _("unlimited quota") if quota is None else utils.get_human_size(quota)
+ ),
+ ),
)
return (payload, status, None, note)
diff --git a/libervia/backend/plugins/plugin_dbg_manhole.py b/libervia/backend/plugins/plugin_dbg_manhole.py
index 6b8d6857..c34e86f5 100644
--- a/libervia/backend/plugins/plugin_dbg_manhole.py
+++ b/libervia/backend/plugins/plugin_dbg_manhole.py
@@ -40,7 +40,6 @@ PLUGIN_INFO = {
}
-
class Manhole(object):
def __init__(self, host):
@@ -50,10 +49,17 @@ class Manhole(object):
self.start_manhole(port)
def start_manhole(self, port):
- log.warning(_("/!\\ Manhole debug server activated, be sure to not use it in "
- "production, this is dangerous /!\\"))
- log.info(_("You can connect to manhole server using telnet on port {port}")
- .format(port=port))
+ log.warning(
+ _(
+ "/!\\ Manhole debug server activated, be sure to not use it in "
+ "production, this is dangerous /!\\"
+ )
+ )
+ log.info(
+ _("You can connect to manhole server using telnet on port {port}").format(
+ port=port
+ )
+ )
f = protocol.ServerFactory()
namespace = {
"host": self.host,
@@ -61,9 +67,10 @@ class Manhole(object):
"jid": jid,
"d": defer.ensureDeferred,
}
- f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
- insults.ServerProtocol,
- ColoredManhole,
- namespace=namespace,
- )
+ f.protocol = lambda: TelnetTransport(
+ TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ ColoredManhole,
+ namespace=namespace,
+ )
reactor.listenTCP(port, f)
diff --git a/libervia/backend/plugins/plugin_exp_command_export.py b/libervia/backend/plugins/plugin_exp_command_export.py
index 7aaae5a4..0ec17eaa 100644
--- a/libervia/backend/plugins/plugin_exp_command_export.py
+++ b/libervia/backend/plugins/plugin_exp_command_export.py
@@ -41,7 +41,7 @@ PLUGIN_INFO = {
class ExportCommandProtocol(protocol.ProcessProtocol):
- """ Try to register an account with prosody """
+ """Try to register an account with prosody"""
def __init__(self, parent, client, target, options):
self.parent = parent
@@ -73,10 +73,10 @@ class ExportCommandProtocol(protocol.ProcessProtocol):
self.transport.write(message.encode("utf-8"))
def bool_option(self, key):
- """ Get boolean value from options
+ """Get boolean value from options
@param key: name of the option
@return: True if key exists and set to "true" (case insensitive),
- False in all other cases """
+ False in all other cases"""
value = self.options.get(key, "")
return value.lower() == "true"
@@ -92,7 +92,9 @@ class CommandExport(object):
log.info(_("Plugin command export initialization"))
self.host = host
self.spawned = {} # key = entity
- host.trigger.add("message_received", self.message_received_trigger, priority=10000)
+ host.trigger.add(
+ "message_received", self.message_received_trigger, priority=10000
+ )
host.bridge.add_method(
"command_export",
".plugin",
@@ -102,19 +104,19 @@ class CommandExport(object):
)
def removeProcess(self, entity, process):
- """ Called when the process is finished
+ """Called when the process is finished
@param entity: jid.JID attached to the process
@param process: process to remove"""
try:
processes_set = self.spawned[(entity, process.client.profile)]
processes_set.discard(process)
if not processes_set:
- del (self.spawned[(entity, process.client.profile)])
+ del self.spawned[(entity, process.client.profile)]
except ValueError:
pass
def message_received_trigger(self, client, message_elt, post_treat):
- """ Check if source is linked and repeat message, else do nothing """
+ """Check if source is linked and repeat message, else do nothing"""
from_jid = jid.JID(message_elt["from"])
spawned_key = (from_jid.userhostJID(), client.profile)
@@ -140,7 +142,7 @@ class CommandExport(object):
return True
def _export_command(self, command, args, targets, options, profile_key):
- """ Export a commands to authorised targets
+ """Export a commands to authorised targets
@param command: full path of the command to execute
@param args: list of arguments, with command name as first one
@param targets: list of allowed entities
diff --git a/libervia/backend/plugins/plugin_exp_invitation.py b/libervia/backend/plugins/plugin_exp_invitation.py
index 5417f6fa..c357cfc6 100644
--- a/libervia/backend/plugins/plugin_exp_invitation.py
+++ b/libervia/backend/plugins/plugin_exp_invitation.py
@@ -45,9 +45,7 @@ PLUGIN_INFO = {
}
NS_INVITATION = "https://salut-a-toi/protocol/invitation:0"
-INVITATION = '/message/invitation[@xmlns="{ns_invit}"]'.format(
- ns_invit=NS_INVITATION
-)
+INVITATION = '/message/invitation[@xmlns="{ns_invit}"]'.format(ns_invit=NS_INVITATION)
NS_INVITATION_LIST = NS_INVITATION + "#list"
@@ -91,8 +89,10 @@ class Invitation(object):
"""
if namespace in self._ns_cb:
raise exceptions.ConflictError(
- "invitation namespace {namespace} is already register with {callback}"
- .format(namespace=namespace, callback=self._ns_cb[namespace]))
+ "invitation namespace {namespace} is already register with {callback}".format(
+ namespace=namespace, callback=self._ns_cb[namespace]
+ )
+ )
self._ns_cb[namespace] = callback
def _generate_base_invitation(self, client, invitee_jid, name, extra):
@@ -118,14 +118,16 @@ class Invitation(object):
invitation_elt = mess_data["xml"].addElement("invitation", NS_INVITATION)
if name is not None:
invitation_elt["name"] = name
- thumb_url = extra.get('thumb_url')
+ thumb_url = extra.get("thumb_url")
if thumb_url:
- if not thumb_url.startswith('http'):
+ if not thumb_url.startswith("http"):
log.warning(
- "only http URLs are allowed for thumbnails, got {url}, ignoring"
- .format(url=thumb_url))
+ "only http URLs are allowed for thumbnails, got {url}, ignoring".format(
+ url=thumb_url
+ )
+ )
else:
- invitation_elt['thumb_url'] = thumb_url
+ invitation_elt["thumb_url"] = thumb_url
return mess_data, invitation_elt
def send_pubsub_invitation(
@@ -136,7 +138,7 @@ class Invitation(object):
node: str,
item_id: Optional[str],
name: Optional[str],
- extra: Optional[dict]
+ extra: Optional[dict],
) -> None:
"""Send an pubsub invitation in a stanza
@@ -151,7 +153,8 @@ class Invitation(object):
if extra is None:
extra = {}
mess_data, invitation_elt = self._generate_base_invitation(
- client, invitee_jid, name, extra)
+ client, invitee_jid, name, extra
+ )
pubsub_elt = invitation_elt.addElement("pubsub")
pubsub_elt["service"] = service.full()
pubsub_elt["node"] = node
@@ -173,8 +176,15 @@ class Invitation(object):
client.send(mess_data["xml"])
async def send_file_sharing_invitation(
- self, client, invitee_jid, service, repos_type=None, namespace=None, path=None,
- name=None, extra=None
+ self,
+ client,
+ invitee_jid,
+ service,
+ repos_type=None,
+ namespace=None,
+ path=None,
+ name=None,
+ extra=None,
):
"""Send a file sharing invitation in a stanza
@@ -196,7 +206,7 @@ class Invitation(object):
# FIXME: not the best place to adapt permission, but it's necessary to check them
# for UX
try:
- await self.host.plugins['XEP-0329'].affiliationsSet(
+ await self.host.plugins["XEP-0329"].affiliationsSet(
client, service, namespace, path, {invitee_jid: "member"}
)
except Exception as e:
@@ -214,18 +224,20 @@ class Invitation(object):
)
else:
try:
- extra['thumb_url'] = own_interest['thumb_url']
+ extra["thumb_url"] = own_interest["thumb_url"]
except KeyError:
pass
mess_data, invitation_elt = self._generate_base_invitation(
- client, invitee_jid, name, extra)
+ client, invitee_jid, name, extra
+ )
file_sharing_elt = invitation_elt.addElement("file_sharing")
file_sharing_elt["service"] = service.full()
if repos_type is not None:
if repos_type not in ("files", "photos"):
msg = "unknown repository type: {repos_type}".format(
- repos_type=repos_type)
+ repos_type=repos_type
+ )
log.warning(msg)
raise exceptions.DateError(msg)
file_sharing_elt["type"] = repos_type
@@ -250,8 +262,9 @@ class Invitation(object):
client, service, node, item_ids=[item_id]
)
except Exception as e:
- log.warning(_("Can't get item linked with invitation: {reason}").format(
- reason=e))
+ log.warning(
+ _("Can't get item linked with invitation: {reason}").format(reason=e)
+ )
try:
item_elt = items[0]
except IndexError:
@@ -261,8 +274,9 @@ class Invitation(object):
try:
namespace = item_elt.firstChildElement().uri
except Exception as e:
- log.warning(_("Can't retrieve namespace of invitation: {reason}").format(
- reason = e))
+ log.warning(
+ _("Can't retrieve namespace of invitation: {reason}").format(reason=e)
+ )
raise exceptions.DataError
args = [service, node, item_id, item_elt]
@@ -271,7 +285,7 @@ class Invitation(object):
node_data_elt = next(pubsub_elt.elements(NS_INVITATION, "node_data"))
except StopIteration:
raise exceptions.DataError("Bad invitation, ignoring")
- namespace = node_data_elt['namespace']
+ namespace = node_data_elt["namespace"]
args = [service, node, None, node_data_elt]
return namespace, args
@@ -296,7 +310,7 @@ class Invitation(object):
name = invitation_elt.getAttribute("name")
extra = {}
if invitation_elt.hasAttribute("thumb_url"):
- extra['thumb_url'] = invitation_elt['thumb_url']
+ extra["thumb_url"] = invitation_elt["thumb_url"]
for elt in invitation_elt.elements():
if elt.uri != NS_INVITATION:
@@ -307,22 +321,26 @@ class Invitation(object):
elif elt.name == "file_sharing":
method = self._parse_file_sharing_elt
else:
- log.warning("not implemented invitation element: {xml}".format(
- xml = elt.toXml()))
+ log.warning(
+ "not implemented invitation element: {xml}".format(xml=elt.toXml())
+ )
continue
try:
namespace, args = await method(client, elt)
except exceptions.DataError:
- log.warning("Can't parse invitation element: {xml}".format(
- xml = elt.toXml()))
+ log.warning(
+ "Can't parse invitation element: {xml}".format(xml=elt.toXml())
+ )
continue
try:
cb = self._ns_cb[namespace]
except KeyError:
- log.warning(_(
- 'No handler for namespace "{namespace}", invitation ignored')
- .format(namespace=namespace))
+ log.warning(
+ _(
+ 'No handler for namespace "{namespace}", invitation ignored'
+ ).format(namespace=namespace)
+ )
else:
await utils.as_deferred(cb, client, namespace, name, extra, *args)
diff --git a/libervia/backend/plugins/plugin_exp_invitation_file.py b/libervia/backend/plugins/plugin_exp_invitation_file.py
index 80f0f711..768447c1 100644
--- a/libervia/backend/plugins/plugin_exp_invitation_file.py
+++ b/libervia/backend/plugins/plugin_exp_invitation_file.py
@@ -53,21 +53,35 @@ class FileSharingInvitation:
in_sign="ssssssss",
out_sign="",
method=self._send_file_sharing_invitation,
- async_=True
+ async_=True,
)
def _send_file_sharing_invitation(
- self, invitee_jid_s, service_s, repos_type=None, namespace=None, path=None,
- name=None, extra_s='', profile_key=C.PROF_KEY_NONE):
+ self,
+ invitee_jid_s,
+ service_s,
+ repos_type=None,
+ namespace=None,
+ path=None,
+ name=None,
+ extra_s="",
+ profile_key=C.PROF_KEY_NONE,
+ ):
client = self.host.get_client(profile_key)
invitee_jid = jid.JID(invitee_jid_s)
service = jid.JID(service_s)
extra = data_format.deserialise(extra_s)
return defer.ensureDeferred(
self.host.plugins["INVITATION"].send_file_sharing_invitation(
- client, invitee_jid, service, repos_type=repos_type or None,
- namespace=namespace or None, path=path or None, name=name or None,
- extra=extra)
+ client,
+ invitee_jid,
+ service,
+ repos_type=repos_type or None,
+ namespace=namespace or None,
+ path=path or None,
+ name=name or None,
+ extra=extra,
+ )
)
def on_invitation(
@@ -79,25 +93,31 @@ class FileSharingInvitation:
service: jid.JID,
repos_type: str,
sharing_ns: str,
- path: str
+ path: str,
):
if repos_type == "files":
type_human = _("file sharing")
elif repos_type == "photos":
type_human = _("photo album")
else:
- log.warning("Unknown repository type: {repos_type}".format(
- repos_type=repos_type))
+ log.warning(
+ "Unknown repository type: {repos_type}".format(repos_type=repos_type)
+ )
repos_type = "file"
type_human = _("file sharing")
- log.info(_(
- '{profile} has received an invitation for a files repository ({type_human}) '
- 'with namespace {sharing_ns!r} at path [{path}]').format(
- profile=client.profile, type_human=type_human, sharing_ns=sharing_ns,
- path=path)
+ log.info(
+ _(
+ "{profile} has received an invitation for a files repository ({type_human}) "
+ "with namespace {sharing_ns!r} at path [{path}]"
+ ).format(
+ profile=client.profile,
+ type_human=type_human,
+ sharing_ns=sharing_ns,
+ path=path,
)
+ )
return defer.ensureDeferred(
- self.host.plugins['LIST_INTEREST'].register_file_sharing(
+ self.host.plugins["LIST_INTEREST"].register_file_sharing(
client, service, repos_type, sharing_ns, path, name, extra
)
)
diff --git a/libervia/backend/plugins/plugin_exp_invitation_pubsub.py b/libervia/backend/plugins/plugin_exp_invitation_pubsub.py
index 0a9718f8..8b34d1ba 100644
--- a/libervia/backend/plugins/plugin_exp_invitation_pubsub.py
+++ b/libervia/backend/plugins/plugin_exp_invitation_pubsub.py
@@ -57,20 +57,23 @@ class PubsubInvitation:
in_sign="sssssss",
out_sign="",
method=self._send_pubsub_invitation,
- async_=True
+ async_=True,
)
- def register(
- self,
- namespace: str,
- handler
- ) -> None:
+ def register(self, namespace: str, handler) -> None:
self._ns_handler[namespace] = handler
self.host.plugins["INVITATION"].register_namespace(namespace, self.on_invitation)
def _send_pubsub_invitation(
- self, invitee_jid_s, service_s, node, item_id=None,
- name=None, extra_s='', profile_key=C.PROF_KEY_NONE):
+ self,
+ invitee_jid_s,
+ service_s,
+ node,
+ item_id=None,
+ name=None,
+ extra_s="",
+ profile_key=C.PROF_KEY_NONE,
+ ):
client = self.host.get_client(profile_key)
invitee_jid = jid.JID(invitee_jid_s)
service = jid.JID(service_s)
@@ -83,7 +86,7 @@ class PubsubInvitation:
node,
item_id or None,
name=name or None,
- extra=extra
+ extra=extra,
)
)
@@ -94,7 +97,7 @@ class PubsubInvitation:
service: jid.JID,
node: str,
item_id: Optional[str] = None,
- name: str = '',
+ name: str = "",
extra: Optional[dict] = None,
) -> None:
if extra is None:
@@ -112,24 +115,26 @@ class PubsubInvitation:
else:
await utils.as_deferred(
preflight,
- client, invitee_jid, service, node, item_id, name, extra
+ client,
+ invitee_jid,
+ service,
+ node,
+ item_id,
+ name,
+ extra,
)
if item_id is None:
item_id = extra.pop("default_item_id", None)
# we authorize our invitee to see the nodes of interest
- await self._p.set_node_affiliations(client, service, node, {invitee_jid: "member"})
+ await self._p.set_node_affiliations(
+ client, service, node, {invitee_jid: "member"}
+ )
log.debug(f"affiliation set on {service}'s {node!r} node")
# now we send the invitation
self.host.plugins["INVITATION"].send_pubsub_invitation(
- client,
- invitee_jid,
- service,
- node,
- item_id,
- name=name or None,
- extra=extra
+ client, invitee_jid, service, node, item_id, name=name or None, extra=extra
)
async def on_invitation(
@@ -141,7 +146,7 @@ class PubsubInvitation:
service: jid.JID,
node: str,
item_id: Optional[str],
- item_elt: domish.Element
+ item_elt: domish.Element,
) -> None:
if extra is None:
extra = {}
@@ -155,7 +160,14 @@ class PubsubInvitation:
else:
await utils.as_deferred(
preflight,
- client, namespace, name, extra, service, node, item_id, item_elt
+ client,
+ namespace,
+ name,
+ extra,
+ service,
+ node,
+ item_id,
+ item_elt,
)
if item_id is None:
item_id = extra.pop("default_item_id", None)
@@ -164,6 +176,6 @@ class PubsubInvitation:
if not name:
name = extra.pop("name", "")
- return await self.host.plugins['LIST_INTEREST'].register_pubsub(
- client, namespace, service, node, item_id, creator,
- name, element, extra)
+ return await self.host.plugins["LIST_INTEREST"].register_pubsub(
+ client, namespace, service, node, item_id, creator, name, element, extra
+ )
diff --git a/libervia/backend/plugins/plugin_exp_jingle_stream.py b/libervia/backend/plugins/plugin_exp_jingle_stream.py
index fce3ccc9..b3d11e47 100644
--- a/libervia/backend/plugins/plugin_exp_jingle_stream.py
+++ b/libervia/backend/plugins/plugin_exp_jingle_stream.py
@@ -217,17 +217,19 @@ class JingleStream(object):
factory.port_listening = port_listening
break
# we don't want to wait for IQ result of initiate
- defer.ensureDeferred(self._j.initiate(
- client,
- to_jid,
- [
- {
- "app_ns": NS_STREAM,
- "senders": self._j.ROLE_INITIATOR,
- "app_kwargs": {"stream_object": factory},
- }
- ],
- ))
+ defer.ensureDeferred(
+ self._j.initiate(
+ client,
+ to_jid,
+ [
+ {
+ "app_ns": NS_STREAM,
+ "senders": self._j.ROLE_INITIATOR,
+ "app_kwargs": {"stream_object": factory},
+ }
+ ],
+ )
+ )
return str(port)
def jingle_session_init(self, client, session, content_name, stream_object):
@@ -239,7 +241,9 @@ class JingleStream(object):
return desc_elt
@defer.inlineCallbacks
- def jingle_request_confirmation(self, client, action, session, content_name, desc_elt):
+ def jingle_request_confirmation(
+ self, client, action, session, content_name, desc_elt
+ ):
"""This method request confirmation for a jingle session"""
content_data = session["contents"][content_name]
if content_data["senders"] not in (
@@ -287,7 +291,9 @@ class JingleStream(object):
content_data["stream_object"] = application_data["stream_object"]
finished_d = content_data["finished_d"] = defer.Deferred()
args = [client, session, content_name, content_data]
- finished_d.addCallbacks(self._finished_cb, self._finished_eb, args, None, args)
+ finished_d.addCallbacks(
+ self._finished_cb, self._finished_eb, args, None, args
+ )
else:
log.warning("FIXME: unmanaged action {}".format(action))
return desc_elt
diff --git a/libervia/backend/plugins/plugin_exp_lang_detect.py b/libervia/backend/plugins/plugin_exp_lang_detect.py
index 8011cd19..30001ae0 100644
--- a/libervia/backend/plugins/plugin_exp_lang_detect.py
+++ b/libervia/backend/plugins/plugin_exp_lang_detect.py
@@ -79,7 +79,7 @@ class LangDetect(object):
return mess_data
def message_received_trigger(self, client, message_elt, post_treat):
- """ Check if source is linked and repeat message, else do nothing """
+ """Check if source is linked and repeat message, else do nothing"""
lang_detect = self.host.memory.param_get_a(
NAME, CATEGORY, profile_key=client.profile
diff --git a/libervia/backend/plugins/plugin_exp_list_of_interest.py b/libervia/backend/plugins/plugin_exp_list_of_interest.py
index 3b8a8e4d..b3db2daf 100644
--- a/libervia/backend/plugins/plugin_exp_list_of_interest.py
+++ b/libervia/backend/plugins/plugin_exp_list_of_interest.py
@@ -99,8 +99,18 @@ class ListInterest(object):
if e.condition == "conflict":
log.debug(_("requested node already exists"))
- async def register_pubsub(self, client, namespace, service, node, item_id=None,
- creator=False, name=None, element=None, extra=None):
+ async def register_pubsub(
+ self,
+ client,
+ namespace,
+ service,
+ node,
+ item_id=None,
+ creator=False,
+ name=None,
+ element=None,
+ extra=None,
+ ):
"""Register an interesting element in personal list
@param namespace(unicode): namespace of the interest
@@ -123,10 +133,10 @@ class ListInterest(object):
interest_elt = domish.Element((NS_LIST_INTEREST, "interest"))
interest_elt["namespace"] = namespace
if name is not None:
- interest_elt['name'] = name
- thumb_url = extra.get('thumb_url')
+ interest_elt["name"] = name
+ thumb_url = extra.get("thumb_url")
if thumb_url:
- interest_elt['thumb_url'] = thumb_url
+ interest_elt["thumb_url"] = thumb_url
pubsub_elt = interest_elt.addElement("pubsub")
pubsub_elt["service"] = service.full()
pubsub_elt["node"] = node
@@ -136,12 +146,9 @@ class ListInterest(object):
pubsub_elt["creator"] = C.BOOL_TRUE
if element is not None:
pubsub_elt.addChild(element)
- uri_kwargs = {
- "path": service.full(),
- "node": node
- }
+ uri_kwargs = {"path": service.full(), "node": node}
if item_id:
- uri_kwargs['id'] = item_id
+ uri_kwargs["id"] = item_id
interest_uri = uri.build_xmpp_uri("pubsub", **uri_kwargs)
# we use URI of the interest as item id to avoid duplicates
item_elt = pubsub.Item(interest_uri, payload=interest_elt)
@@ -150,16 +157,22 @@ class ListInterest(object):
)
def _register_file_sharing(
- self, service, repos_type, namespace, path, name, extra_raw,
- profile
+ self, service, repos_type, namespace, path, name, extra_raw, profile
):
client = self.host.get_client(profile)
extra = data_format.deserialise(extra_raw)
- return defer.ensureDeferred(self.register_file_sharing(
- client, jid.JID(service), repos_type or None, namespace or None, path or None,
- name or None, extra
- ))
+ return defer.ensureDeferred(
+ self.register_file_sharing(
+ client,
+ jid.JID(service),
+ repos_type or None,
+ namespace or None,
+ path or None,
+ name or None,
+ extra,
+ )
+ )
def normalise_file_sharing_service(self, client, service):
# FIXME: Q&D fix as the bare file sharing service JID will lead to user own
@@ -167,14 +180,21 @@ class ListInterest(object):
# By specifying the user part, we for the use of the host repository.
# A cleaner way should be implemented
if service.user is None:
- service.user = self.host.plugins['XEP-0106'].escape(client.jid.user)
+ service.user = self.host.plugins["XEP-0106"].escape(client.jid.user)
def get_file_sharing_id(self, service, namespace, path):
return f"{service}_{namespace or ''}_{path or ''}"
async def register_file_sharing(
- self, client, service, repos_type=None, namespace=None, path=None, name=None,
- extra=None):
+ self,
+ client,
+ service,
+ repos_type=None,
+ namespace=None,
+ path=None,
+ name=None,
+ extra=None,
+ ):
"""Register an interesting file repository in personal list
@param service(jid.JID): service of the file repository
@@ -192,10 +212,10 @@ class ListInterest(object):
interest_elt = domish.Element((NS_LIST_INTEREST, "interest"))
interest_elt["namespace"] = self.host.get_namespace("fis")
if name is not None:
- interest_elt['name'] = name
- thumb_url = extra.get('thumb_url')
+ interest_elt["name"] = name
+ thumb_url = extra.get("thumb_url")
if thumb_url:
- interest_elt['thumb_url'] = thumb_url
+ interest_elt["thumb_url"] = thumb_url
file_sharing_elt = interest_elt.addElement("file_sharing")
file_sharing_elt["service"] = service.full()
@@ -213,36 +233,41 @@ class ListInterest(object):
def _list_interests_serialise(self, interests_data):
interests = []
for item_elt in interests_data[0]:
- interest_data = {"id": item_elt['id']}
+ interest_data = {"id": item_elt["id"]}
interest_elt = item_elt.interest
- if interest_elt.hasAttribute('namespace'):
- interest_data['namespace'] = interest_elt.getAttribute('namespace')
- if interest_elt.hasAttribute('name'):
- interest_data['name'] = interest_elt.getAttribute('name')
- if interest_elt.hasAttribute('thumb_url'):
- interest_data['thumb_url'] = interest_elt.getAttribute('thumb_url')
+ if interest_elt.hasAttribute("namespace"):
+ interest_data["namespace"] = interest_elt.getAttribute("namespace")
+ if interest_elt.hasAttribute("name"):
+ interest_data["name"] = interest_elt.getAttribute("name")
+ if interest_elt.hasAttribute("thumb_url"):
+ interest_data["thumb_url"] = interest_elt.getAttribute("thumb_url")
elt = interest_elt.firstChildElement()
if elt.uri != NS_LIST_INTEREST:
- log.warning("unexpected child element, ignoring: {xml}".format(
- xml = elt.toXml()))
+ log.warning(
+ "unexpected child element, ignoring: {xml}".format(xml=elt.toXml())
+ )
continue
- if elt.name == 'pubsub':
- interest_data.update({
- "type": "pubsub",
- "service": elt['service'],
- "node": elt['node'],
- })
- for attr in ('item', 'creator'):
+ if elt.name == "pubsub":
+ interest_data.update(
+ {
+ "type": "pubsub",
+ "service": elt["service"],
+ "node": elt["node"],
+ }
+ )
+ for attr in ("item", "creator"):
if elt.hasAttribute(attr):
interest_data[attr] = elt[attr]
- elif elt.name == 'file_sharing':
- interest_data.update({
- "type": "file_sharing",
- "service": elt['service'],
- })
- if elt.hasAttribute('type'):
- interest_data['subtype'] = elt['type']
- for attr in ('files_namespace', 'path'):
+ elif elt.name == "file_sharing":
+ interest_data.update(
+ {
+ "type": "file_sharing",
+ "service": elt["service"],
+ }
+ )
+ if elt.hasAttribute("type"):
+ interest_data["subtype"] = elt["type"]
+ for attr in ("files_namespace", "path"):
if elt.hasAttribute(attr):
interest_data[attr] = elt[attr]
else:
@@ -282,8 +307,9 @@ class ListInterest(object):
try:
interest_elt = next(item.elements(NS_LIST_INTEREST, "interest"))
except StopIteration:
- log.warning(_("Missing interest element: {xml}").format(
- xml=item.toXml()))
+ log.warning(
+ _("Missing interest element: {xml}").format(xml=item.toXml())
+ )
continue
if interest_elt.getAttribute("namespace") == namespace:
filtered_items.append(item)
@@ -292,14 +318,15 @@ class ListInterest(object):
return (items, metadata)
def _interest_retract(self, service_s, item_id, profile_key):
- d = self._p._retract_item(
- service_s, NS_LIST_INTEREST, item_id, True, profile_key)
+ d = self._p._retract_item(service_s, NS_LIST_INTEREST, item_id, True, profile_key)
d.addCallback(lambda __: None)
return d
async def get(self, client: SatXMPPEntity, item_id: str) -> dict:
"""Retrieve a specific interest in profile's list"""
- items_data = await self._p.get_items(client, None, NS_LIST_INTEREST, item_ids=[item_id])
+ items_data = await self._p.get_items(
+ client, None, NS_LIST_INTEREST, item_ids=[item_id]
+ )
try:
return self._list_interests_serialise(items_data)[0]
except IndexError:
diff --git a/libervia/backend/plugins/plugin_exp_parrot.py b/libervia/backend/plugins/plugin_exp_parrot.py
index 43b5af32..fcf70295 100644
--- a/libervia/backend/plugins/plugin_exp_parrot.py
+++ b/libervia/backend/plugins/plugin_exp_parrot.py
@@ -74,7 +74,7 @@ class Exp_Parrot(object):
# raise trigger.SkipOtherTriggers
def message_received_trigger(self, client, message_elt, post_treat):
- """ Check if source is linked and repeat message, else do nothing """
+ """Check if source is linked and repeat message, else do nothing"""
# TODO: many things are not repeated (subject, thread, etc)
from_jid = message_elt["from"]
@@ -93,7 +93,8 @@ class Exp_Parrot(object):
try:
entity_type = self.host.memory.entity_data_get(
- client, from_jid, [C.ENTITY_TYPE])[C.ENTITY_TYPE]
+ client, from_jid, [C.ENTITY_TYPE]
+ )[C.ENTITY_TYPE]
except (UnknownEntityError, KeyError):
entity_type = "contact"
if entity_type == C.ENTITY_TYPE_MUC:
diff --git a/libervia/backend/plugins/plugin_exp_pubsub_admin.py b/libervia/backend/plugins/plugin_exp_pubsub_admin.py
index 43428400..08c2e8bb 100644
--- a/libervia/backend/plugins/plugin_exp_pubsub_admin.py
+++ b/libervia/backend/plugins/plugin_exp_pubsub_admin.py
@@ -37,9 +37,11 @@ PLUGIN_INFO = {
C.PI_RECOMMENDATIONS: [],
C.PI_MAIN: "PubsubAdmin",
C.PI_HANDLER: "no",
- C.PI_DESCRIPTION: _("""\Implementation of Pubsub Administrator
+ C.PI_DESCRIPTION: _(
+ """\Implementation of Pubsub Administrator
This allows a pubsub administrator to overwrite completly items, including publisher.
-Specially useful when importing a node."""),
+Specially useful when importing a node."""
+ ),
}
NS_PUBSUB_ADMIN = "https://salut-a-toi.org/spec/pubsub_admin:0"
@@ -58,35 +60,36 @@ class PubsubAdmin(object):
async_=True,
)
- def _publish(self, service, nodeIdentifier, items, extra=None,
- profile_key=C.PROF_KEY_NONE):
+ def _publish(
+ self, service, nodeIdentifier, items, extra=None, profile_key=C.PROF_KEY_NONE
+ ):
client = self.host.get_client(profile_key)
service = None if not service else jid.JID(service)
extra = data_format.deserialise(extra)
- items = [generic.parseXml(i.encode('utf-8')) for i in items]
- return self.publish(
- client, service, nodeIdentifier, items, extra
- )
+ items = [generic.parseXml(i.encode("utf-8")) for i in items]
+ return self.publish(client, service, nodeIdentifier, items, extra)
def _send_cb(self, iq_result):
publish_elt = iq_result.admin.pubsub.publish
ids = []
- for item_elt in publish_elt.elements(pubsub.NS_PUBSUB, 'item'):
- ids.append(item_elt['id'])
+ for item_elt in publish_elt.elements(pubsub.NS_PUBSUB, "item"):
+ ids.append(item_elt["id"])
return ids
def publish(self, client, service, nodeIdentifier, items, extra=None):
for item in items:
- if item.name != 'item' or item.uri != pubsub.NS_PUBSUB:
+ if item.name != "item" or item.uri != pubsub.NS_PUBSUB:
raise exceptions.DataError(
- 'Invalid element, a pubsub item is expected: {xml}'.format(
- xml=item.toXml()))
+ "Invalid element, a pubsub item is expected: {xml}".format(
+ xml=item.toXml()
+ )
+ )
iq_elt = client.IQ()
- iq_elt['to'] = service.full() if service else client.jid.userhost()
- admin_elt = iq_elt.addElement((NS_PUBSUB_ADMIN, 'admin'))
- pubsub_elt = admin_elt.addElement((pubsub.NS_PUBSUB, 'pubsub'))
- publish_elt = pubsub_elt.addElement('publish')
- publish_elt['node'] = nodeIdentifier
+ iq_elt["to"] = service.full() if service else client.jid.userhost()
+ admin_elt = iq_elt.addElement((NS_PUBSUB_ADMIN, "admin"))
+ pubsub_elt = admin_elt.addElement((pubsub.NS_PUBSUB, "pubsub"))
+ publish_elt = pubsub_elt.addElement("publish")
+ publish_elt["node"] = nodeIdentifier
for item in items:
publish_elt.addChild(item)
d = iq_elt.send()
diff --git a/libervia/backend/plugins/plugin_exp_pubsub_hook.py b/libervia/backend/plugins/plugin_exp_pubsub_hook.py
index 8e6da70d..3cd01a11 100644
--- a/libervia/backend/plugins/plugin_exp_pubsub_hook.py
+++ b/libervia/backend/plugins/plugin_exp_pubsub_hook.py
@@ -55,7 +55,9 @@ class PubsubHook(object):
def __init__(self, host):
log.info(_("PubSub Hook initialization"))
self.host = host
- self.node_hooks = {} # keep track of the number of hooks per node (for all profiles)
+ self.node_hooks = (
+ {}
+ ) # keep track of the number of hooks per node (for all profiles)
host.bridge.add_method(
"ps_hook_add", ".plugin", in_sign="ssssbs", out_sign="", method=self._addHook
)
@@ -108,7 +110,9 @@ class PubsubHook(object):
else:
if self.node_hooks[node] == 0:
del self.node_hooks[node]
- self.host.plugins["XEP-0060"].remove_managed_node(node, self._items_received)
+ self.host.plugins["XEP-0060"].remove_managed_node(
+ node, self._items_received
+ )
log.debug(_("hook removed"))
else:
log.debug(_("node still needed for an other hook"))
@@ -120,9 +124,7 @@ class PubsubHook(object):
)
if hook_type != HOOK_TYPE_PYTHON_FILE:
raise NotImplementedError(
- _("{hook_type} hook type not implemented yet").format(
- hook_type=hook_type
- )
+ _("{hook_type} hook type not implemented yet").format(hook_type=hook_type)
)
self._install_node_manager(client, node)
hook_data = {"service": service, "type": hook_type, "arg": hook_arg}
@@ -160,7 +162,14 @@ class PubsubHook(object):
try:
if hook_type == HOOK_TYPE_PYTHON_FILE:
hook_globals = {}
- exec(compile(open(hook_data["arg"], "rb").read(), hook_data["arg"], 'exec'), hook_globals)
+ exec(
+ compile(
+ open(hook_data["arg"], "rb").read(),
+ hook_data["arg"],
+ "exec",
+ ),
+ hook_globals,
+ )
callback = hook_globals["hook"]
else:
raise NotImplementedError(
@@ -224,7 +233,9 @@ class PubsubHook(object):
def _removeHook(self, service, node, hook_type, hook_arg, profile):
client = self.host.get_client(profile)
service = jid.JID(service) if service else client.jid.userhostJID()
- return self.remove_hook(client, service, node, hook_type or None, hook_arg or None)
+ return self.remove_hook(
+ client, service, node, hook_type or None, hook_arg or None
+ )
def remove_hook(self, client, service, node, hook_type=None, hook_arg=None):
"""Remove a persistent or temporaty root
diff --git a/libervia/backend/plugins/plugin_import.py b/libervia/backend/plugins/plugin_import.py
index 10952e19..b9235f8f 100644
--- a/libervia/backend/plugins/plugin_import.py
+++ b/libervia/backend/plugins/plugin_import.py
@@ -139,8 +139,16 @@ class ImportPlugin(object):
else:
return importer.short_desc, importer.long_desc
- def _do_import(self, import_handler, name, location, options, pubsub_service="",
- pubsub_node="", profile=C.PROF_KEY_NONE):
+ def _do_import(
+ self,
+ import_handler,
+ name,
+ location,
+ options,
+ pubsub_service="",
+ pubsub_node="",
+ profile=C.PROF_KEY_NONE,
+ ):
client = self.host.get_client(profile)
options = {key: str(value) for key, value in options.items()}
for option in import_handler.BOOL_OPTIONS:
@@ -169,8 +177,16 @@ class ImportPlugin(object):
)
@defer.inlineCallbacks
- def do_import(self, client, import_handler, name, location, options=None,
- pubsub_service=None, pubsub_node=None,):
+ def do_import(
+ self,
+ client,
+ import_handler,
+ name,
+ location,
+ options=None,
+ pubsub_service=None,
+ pubsub_node=None,
+ ):
"""import data
@param import_handler(object): instance of the import handler
@@ -324,9 +340,9 @@ class ImportPlugin(object):
name = name.lower()
if name in import_handler.importers:
raise exceptions.ConflictError(
- _(
- "An {handler_name} importer with the name {name} already exist"
- ).format(handler_name=import_handler.name, name=name)
+ _("An {handler_name} importer with the name {name} already exist").format(
+ handler_name=import_handler.name, name=name
+ )
)
import_handler.importers[name] = Importer(callback, short_desc, long_desc)
diff --git a/libervia/backend/plugins/plugin_merge_req_mercurial.py b/libervia/backend/plugins/plugin_merge_req_mercurial.py
index e58f9d41..c03e8397 100644
--- a/libervia/backend/plugins/plugin_merge_req_mercurial.py
+++ b/libervia/backend/plugins/plugin_merge_req_mercurial.py
@@ -24,6 +24,7 @@ from libervia.backend.core.i18n import _, D_
from libervia.backend.core.constants import Const as C
from libervia.backend.core import exceptions
from libervia.backend.core.log import getLogger
+
log = getLogger(__name__)
@@ -34,15 +35,16 @@ PLUGIN_INFO = {
C.PI_DEPENDENCIES: ["MERGE_REQUESTS"],
C.PI_MAIN: "MercurialHandler",
C.PI_HANDLER: "no",
- C.PI_DESCRIPTION: _("""Merge request handler for Mercurial""")
+ C.PI_DESCRIPTION: _("""Merge request handler for Mercurial"""),
}
SHORT_DESC = D_("handle Mercurial repository")
-CLEAN_RE = re.compile(r'[^\w -._]', flags=re.UNICODE)
+CLEAN_RE = re.compile(r"[^\w -._]", flags=re.UNICODE)
class MercurialProtocol(async_process.CommandProtocol):
"""handle hg commands"""
+
name = "Mercurial"
command = None
@@ -65,45 +67,58 @@ class MercurialProtocol(async_process.CommandProtocol):
class MercurialHandler(object):
- data_types = ('mercurial_changeset',)
+ data_types = ("mercurial_changeset",)
def __init__(self, host):
log.info(_("Mercurial merge request handler initialization"))
try:
- MercurialProtocol.command = which('hg')[0]
+ MercurialProtocol.command = which("hg")[0]
except IndexError:
- raise exceptions.NotFound(_("Mercurial executable (hg) not found, "
- "can't use Mercurial handler"))
+ raise exceptions.NotFound(
+ _("Mercurial executable (hg) not found, " "can't use Mercurial handler")
+ )
self.host = host
- self._m = host.plugins['MERGE_REQUESTS']
- self._m.register('mercurial', self, self.data_types, SHORT_DESC)
-
+ self._m = host.plugins["MERGE_REQUESTS"]
+ self._m.register("mercurial", self, self.data_types, SHORT_DESC)
def check(self, repository):
- d = MercurialProtocol.run(repository, 'identify')
+ d = MercurialProtocol.run(repository, "identify")
d.addCallback(lambda __: True)
d.addErrback(lambda __: False)
return d
def export(self, repository):
d = MercurialProtocol.run(
- repository, 'export', '-g', '-r', 'outgoing() and ancestors(.)',
- '--encoding=utf-8'
+ repository,
+ "export",
+ "-g",
+ "-r",
+ "outgoing() and ancestors(.)",
+ "--encoding=utf-8",
)
- d.addCallback(lambda data: data.decode('utf-8'))
+ d.addCallback(lambda data: data.decode("utf-8"))
return d
def import_(self, repository, data, data_type, item_id, service, node, extra):
parsed_data = self.parse(data)
try:
- parsed_name = parsed_data[0]['commit_msg'].split('\n')[0]
- parsed_name = CLEAN_RE.sub('', parsed_name)[:40]
+ parsed_name = parsed_data[0]["commit_msg"].split("\n")[0]
+ parsed_name = CLEAN_RE.sub("", parsed_name)[:40]
except Exception:
- parsed_name = ''
- name = 'mr_{item_id}_{parsed_name}'.format(item_id=CLEAN_RE.sub('', item_id),
- parsed_name=parsed_name)
- return MercurialProtocol.run(repository, 'qimport', '-g', '--name', name,
- '--encoding=utf-8', '-', stdin=data)
+ parsed_name = ""
+ name = "mr_{item_id}_{parsed_name}".format(
+ item_id=CLEAN_RE.sub("", item_id), parsed_name=parsed_name
+ )
+ return MercurialProtocol.run(
+ repository,
+ "qimport",
+ "-g",
+ "--name",
+ name,
+ "--encoding=utf-8",
+ "-",
+ stdin=data,
+ )
def parse(self, data, data_type=None):
lines = data.splitlines()
@@ -113,55 +128,62 @@ class MercurialHandler(object):
patch = {}
commit_msg = []
diff = []
- state = 'init'
- if lines[0] != '# HG changeset patch':
- raise exceptions.DataError(_('invalid changeset signature'))
+ state = "init"
+ if lines[0] != "# HG changeset patch":
+ raise exceptions.DataError(_("invalid changeset signature"))
# line index of this patch in the whole data
patch_idx = total_lines - len(lines)
del lines[0]
for idx, line in enumerate(lines):
- if state == 'init':
- if line.startswith('# '):
- if line.startswith('# User '):
+ if state == "init":
+ if line.startswith("# "):
+ if line.startswith("# User "):
elems = line[7:].split()
if not elems:
continue
last = elems[-1]
- if (last.startswith('<') and last.endswith('>')
- and '@' in last):
+ if (
+ last.startswith("<")
+ and last.endswith(">")
+ and "@" in last
+ ):
patch[self._m.META_EMAIL] = elems.pop()[1:-1]
- patch[self._m.META_AUTHOR] = ' '.join(elems)
- elif line.startswith('# Date '):
+ patch[self._m.META_AUTHOR] = " ".join(elems)
+ elif line.startswith("# Date "):
time_data = line[7:].split()
if len(time_data) != 2:
- log.warning(_('unexpected time data: {data}')
- .format(data=line[7:]))
+ log.warning(
+ _("unexpected time data: {data}").format(
+ data=line[7:]
+ )
+ )
continue
- patch[self._m.META_TIMESTAMP] = (int(time_data[0])
- + int(time_data[1]))
- elif line.startswith('# Node ID '):
+ patch[self._m.META_TIMESTAMP] = int(time_data[0]) + int(
+ time_data[1]
+ )
+ elif line.startswith("# Node ID "):
patch[self._m.META_HASH] = line[10:]
- elif line.startswith('# Parent '):
+ elif line.startswith("# Parent "):
patch[self._m.META_PARENT_HASH] = line[10:]
else:
- state = 'commit_msg'
- if state == 'commit_msg':
- if line.startswith('diff --git a/'):
- state = 'diff'
+ state = "commit_msg"
+ if state == "commit_msg":
+ if line.startswith("diff --git a/"):
+ state = "diff"
patch[self._m.META_DIFF_IDX] = patch_idx + idx + 1
else:
commit_msg.append(line)
- if state == 'diff':
- if line.startswith('# ') or idx == len(lines)-1:
+ if state == "diff":
+ if line.startswith("# ") or idx == len(lines) - 1:
# a new patch is starting or we have reached end of patches
- if idx == len(lines)-1:
+ if idx == len(lines) - 1:
# end of patches, we need to keep the line
diff.append(line)
- patch[self._m.META_COMMIT_MSG] = '\n'.join(commit_msg)
- patch[self._m.META_DIFF] = '\n'.join(diff)
+ patch[self._m.META_COMMIT_MSG] = "\n".join(commit_msg)
+ patch[self._m.META_DIFF] = "\n".join(diff)
patches.append(patch)
- if idx == len(lines)-1:
+ if idx == len(lines) - 1:
del lines[:]
else:
del lines[:idx]
diff --git a/libervia/backend/plugins/plugin_misc_account.py b/libervia/backend/plugins/plugin_misc_account.py
index c44cb4a4..8c39f65b 100644
--- a/libervia/backend/plugins/plugin_misc_account.py
+++ b/libervia/backend/plugins/plugin_misc_account.py
@@ -314,7 +314,8 @@ class MiscAccount(object):
# there is no email when an existing XMPP account is used
body = f"New account created on {domain}: {profile} [{email or ''}]"
d_admin = sat_email.send_email(
- self.host.memory.config, admins_emails, subject, body)
+ self.host.memory.config, admins_emails, subject, body
+ )
admins_emails_txt = ", ".join(["<" + addr + ">" for addr in admins_emails])
d_admin.addCallbacks(
@@ -348,7 +349,7 @@ class MiscAccount(object):
"Account creation confirmation sent to <{}>".format(email)
),
self._send_email_eb,
- errbackArgs=[email]
+ errbackArgs=[email],
)
return defer.DeferredList([d_user, d_admin])
@@ -743,15 +744,18 @@ class MiscAccount(object):
d.addCallback(
lambda __: self.host.memory.get_profile_name(jid_s)
) # checks if the profile has been successfuly created
- d.addCallback(lambda profile: defer.ensureDeferred(
- self.host.connect(profile, password, {}, 0)))
+ d.addCallback(
+ lambda profile: defer.ensureDeferred(
+ self.host.connect(profile, password, {}, 0)
+ )
+ )
def connected(result):
self.send_emails(None, profile=jid_s)
return result
def remove_profile(
- failure
+ failure,
): # profile has been successfully created but the XMPP credentials are wrong!
log.debug(
"Removing previously auto-created profile: %s" % failure.getErrorMessage()
diff --git a/libervia/backend/plugins/plugin_misc_android.py b/libervia/backend/plugins/plugin_misc_android.py
index e3e1f932..6324a053 100644
--- a/libervia/backend/plugins/plugin_misc_android.py
+++ b/libervia/backend/plugins/plugin_misc_android.py
@@ -67,20 +67,20 @@ from android.broadcast import BroadcastReceiver
from android import python_act
-Context = autoclass('android.content.Context')
-ConnectivityManager = autoclass('android.net.ConnectivityManager')
-MediaPlayer = autoclass('android.media.MediaPlayer')
-AudioManager = autoclass('android.media.AudioManager')
+Context = autoclass("android.content.Context")
+ConnectivityManager = autoclass("android.net.ConnectivityManager")
+MediaPlayer = autoclass("android.media.MediaPlayer")
+AudioManager = autoclass("android.media.AudioManager")
# notifications
-AndroidString = autoclass('java.lang.String')
-PendingIntent = autoclass('android.app.PendingIntent')
-Intent = autoclass('android.content.Intent')
+AndroidString = autoclass("java.lang.String")
+PendingIntent = autoclass("android.app.PendingIntent")
+Intent = autoclass("android.content.Intent")
# DNS
# regex to find dns server prop with "getprop"
RE_DNS = re.compile(r"^\[net\.[a-z0-9]+\.dns[0-4]\]: \[(.*)\]$", re.MULTILINE)
-SystemProperties = autoclass('android.os.SystemProperties')
+SystemProperties = autoclass("android.os.SystemProperties")
#: delay between a pause event and sending the inactive indication to server, in seconds
#: we don't send the indication immediately because user can be just checking something
@@ -148,7 +148,8 @@ class Notification(AndroidNotification):
action_data = AndroidString(json.dumps(sat_action).encode())
log.debug(f"adding extra {INTENT_EXTRA_ACTION} ==> {action_data}")
notification_intent = notification_intent.putExtra(
- INTENT_EXTRA_ACTION, action_data)
+ INTENT_EXTRA_ACTION, action_data
+ )
# we use PendingIntent.FLAG_UPDATE_CURRENT here, otherwise extra won't be set
# in the new intent (the old ACTION_MAIN intent will be reused). This differs
@@ -168,14 +169,12 @@ class Notification(AndroidNotification):
# in the generic "notify" method).
sat_action = kwargs.pop("sat_action", None)
noti = None
- message = kwargs.get('message', '').encode('utf-8')
- ticker = kwargs.get('ticker', '').encode('utf-8')
- title = AndroidString(
- kwargs.get('title', '').encode('utf-8')
- )
- icon = kwargs.get('app_icon', '')
+ message = kwargs.get("message", "").encode("utf-8")
+ ticker = kwargs.get("ticker", "").encode("utf-8")
+ title = AndroidString(kwargs.get("title", "").encode("utf-8"))
+ icon = kwargs.get("app_icon", "")
- if kwargs.get('toast', False):
+ if kwargs.get("toast", False):
self._toast(message)
return
else:
@@ -212,7 +211,6 @@ class FrontendStateFactory(protocol.Factory):
return FrontendStateProtocol(self.android_plugin)
-
class AndroidPlugin(object):
params = """
@@ -243,7 +241,7 @@ class AndroidPlugin(object):
log.info(_("plugin Android initialization"))
log.info(f"using Android API {api_version}")
self.host = host
- self._csi = host.plugins.get('XEP-0352')
+ self._csi = host.plugins.get("XEP-0352")
self._csi_timer = None
host.memory.update_params(self.params)
try:
@@ -268,7 +266,9 @@ class AndroidPlugin(object):
raise e
# we set a low priority because we want the notification to be sent after all
# plugins have done their job
- host.trigger.add("message_received", self.message_received_trigger, priority=-1000)
+ host.trigger.add(
+ "message_received", self.message_received_trigger, priority=-1000
+ )
# profiles autoconnection
host.bridge.add_method(
@@ -312,7 +312,7 @@ class AndroidPlugin(object):
callback=lambda *args, **kwargs: reactor.callFromThread(
self.on_connectivity_change
),
- actions=["android.net.conn.CONNECTIVITY_CHANGE"]
+ actions=["android.net.conn.CONNECTIVITY_CHANGE"],
)
self.br.start()
@@ -355,14 +355,18 @@ class AndroidPlugin(object):
- there is a message and it is not a groupchat
- message is not coming from ourself
"""
- if (mess_data["message"] and mess_data["type"] != C.MESS_TYPE_GROUPCHAT
- and not mess_data["from"].userhostJID() == client.jid.userhostJID()):
+ if (
+ mess_data["message"]
+ and mess_data["type"] != C.MESS_TYPE_GROUPCHAT
+ and not mess_data["from"].userhostJID() == client.jid.userhostJID()
+ ):
message = next(iter(mess_data["message"].values()))
try:
subject = next(iter(mess_data["subject"].values()))
except StopIteration:
subject = D_("new message from {contact}").format(
- contact = mess_data['from'])
+ contact=mess_data["from"]
+ )
notification = Notification()
notification._notify(
@@ -379,25 +383,24 @@ class AndroidPlugin(object):
vibrate_mode = ringer_mode == AudioManager.RINGER_MODE_VIBRATE
ring_setting = self.host.memory.param_get_a(
- PARAM_RING_NAME,
- PARAM_RING_CATEGORY,
- profile_key=client.profile
+ PARAM_RING_NAME, PARAM_RING_CATEGORY, profile_key=client.profile
)
- if ring_setting != 'never' and ringer_mode == AudioManager.RINGER_MODE_NORMAL:
+ if ring_setting != "never" and ringer_mode == AudioManager.RINGER_MODE_NORMAL:
self.notif_player.start()
vibration_setting = self.host.memory.param_get_a(
- PARAM_VIBRATE_NAME,
- PARAM_VIBRATE_CATEGORY,
- profile_key=client.profile
+ PARAM_VIBRATE_NAME, PARAM_VIBRATE_CATEGORY, profile_key=client.profile
)
- if (vibration_setting == 'always'
- or vibration_setting == 'vibrate' and vibrate_mode):
- try:
- vibrator.vibrate()
- except Exception as e:
- log.warning("Can't use vibrator: {e}".format(e=e))
+ if (
+ vibration_setting == "always"
+ or vibration_setting == "vibrate"
+ and vibrate_mode
+ ):
+ try:
+ vibrator.vibrate()
+ except Exception as e:
+ log.warning("Can't use vibrator: {e}".format(e=e))
return mess_data
def message_received_trigger(self, client, message_elt, post_treat):
@@ -414,7 +417,8 @@ class AndroidPlugin(object):
async def _get_profiles_autoconnect(self):
autoconnect_dict = await self.host.memory.storage.get_ind_param_values(
- category='Connection', name='autoconnect_backend',
+ category="Connection",
+ name="autoconnect_backend",
)
return [p for p, v in autoconnect_dict.items() if C.bool(v)]
@@ -426,7 +430,8 @@ class AndroidPlugin(object):
if len(profiles_autoconnect) > 1:
log.warning(
f"More that one profiles with backend autoconnection set found, picking "
- f"up first one (full list: {profiles_autoconnect!r})")
+ f"up first one (full list: {profiles_autoconnect!r})"
+ )
return profiles_autoconnect[0]
# CSI
@@ -503,17 +508,22 @@ class AndroidPlugin(object):
elif net_type == NET_TYPE_MOBILE:
log.info("mobile data activated")
else:
- log.info("network activated (type={net_type_android})"
- .format(net_type_android=net_type_android))
+ log.info(
+ "network activated (type={net_type_android})".format(
+ net_type_android=net_type_android
+ )
+ )
else:
- log.debug("_check_connectivity called without network change ({net_type})"
- .format(net_type = net_type))
+ log.debug(
+ "_check_connectivity called without network change ({net_type})".format(
+ net_type=net_type
+ )
+ )
# we always call _handle_network_change even if there is not connectivity change
# to be sure to reconnect when necessary
await self._handle_network_change(net_type)
-
def on_connectivity_change(self):
log.debug("on_connectivity_change called")
d = defer.ensureDeferred(self._check_connectivity())
@@ -527,7 +537,8 @@ class AndroidPlugin(object):
log.info(
"Patching Twisted to use Android DNS resolver ({dns_servers})".format(
- dns_servers=', '.join([s[0] for s in dns_servers]))
+ dns_servers=", ".join([s[0] for s in dns_servers])
+ )
)
dns_client.theResolver = dns_client.createResolver(servers=dns_servers)
@@ -538,7 +549,7 @@ class AndroidPlugin(object):
# thanks to A-IV at https://stackoverflow.com/a/11362271 for the way to go
log.debug("Old API, using SystemProperties to find DNS")
for idx in range(1, 5):
- addr = SystemProperties.get(f'net.dns{idx}')
+ addr = SystemProperties.get(f"net.dns{idx}")
if abstract.isIPAddress(addr):
servers.append((addr, 53))
else:
@@ -546,15 +557,18 @@ class AndroidPlugin(object):
# use of getprop inspired by various solutions at
# https://stackoverflow.com/q/3070144
# it's the most simple option, and it fit wells with async_process
- getprop_paths = which('getprop')
+ getprop_paths = which("getprop")
if getprop_paths:
try:
getprop_path = getprop_paths[0]
props = await async_process.run(getprop_path)
- servers = [(ip, 53) for ip in RE_DNS.findall(props.decode())
- if abstract.isIPAddress(ip)]
+ servers = [
+ (ip, 53)
+ for ip in RE_DNS.findall(props.decode())
+ if abstract.isIPAddress(ip)
+ ]
except Exception as e:
- log.warning(f"Can't use \"getprop\" to find DNS server: {e}")
+ log.warning(f'Can\'t use "getprop" to find DNS server: {e}')
if not servers:
# FIXME: Cloudflare's 1.1.1.1 seems to have a better privacy policy, to be
# checked.
@@ -562,5 +576,5 @@ class AndroidPlugin(object):
"no server found, we have to use factory Google DNS, this is not ideal "
"for privacy"
)
- servers.append(('8.8.8.8', 53), ('8.8.4.4', 53))
+ servers.append(("8.8.8.8", 53), ("8.8.4.4", 53))
return servers
diff --git a/libervia/backend/plugins/plugin_misc_app_manager/__init__.py b/libervia/backend/plugins/plugin_misc_app_manager/__init__.py
index 54740a44..9b0cc25a 100644
--- a/libervia/backend/plugins/plugin_misc_app_manager/__init__.py
+++ b/libervia/backend/plugins/plugin_misc_app_manager/__init__.py
@@ -45,7 +45,7 @@ try:
import yaml
except ImportError:
raise exceptions.MissingModule(
- 'Missing module PyYAML, please download/install it. You can use '
+ "Missing module PyYAML, please download/install it. You can use "
'"pip install pyyaml"'
)
@@ -59,8 +59,6 @@ except ImportError:
from yaml import Loader, Dumper
-
-
PLUGIN_INFO = {
C.PI_NAME: "Applications Manager",
C.PI_IMPORT_NAME: "APP_MANAGER",
@@ -73,7 +71,8 @@ PLUGIN_INFO = {
Manage external applications using packagers, OS virtualization/containers or other
software management tools.
-"""),
+"""
+ ),
}
APP_FILE_PREFIX = "libervia_app_"
@@ -92,7 +91,6 @@ class AppManager:
# instance id to app data map
self._instances = {}
-
self.persistent_data = persistent.LazyPersistentBinaryDict("app_manager")
host.bridge.add_method(
@@ -128,30 +126,28 @@ class AppManager:
)
# application has been started succeesfully,
# args: name, instance_id, extra
- host.bridge.add_signal(
- "application_started", ".plugin", signature="sss"
- )
+ host.bridge.add_signal("application_started", ".plugin", signature="sss")
# application went wrong with the application
# args: name, instance_id, extra
- host.bridge.add_signal(
- "application_error", ".plugin", signature="sss"
+ host.bridge.add_signal("application_error", ".plugin", signature="sss")
+ yaml.add_constructor("!libervia_conf", self._libervia_conf_constr, Loader=Loader)
+ yaml.add_constructor(
+ "!libervia_generate_pwd", self._libervia_generate_pwd_constr, Loader=Loader
)
yaml.add_constructor(
- "!libervia_conf", self._libervia_conf_constr, Loader=Loader)
- yaml.add_constructor(
- "!libervia_generate_pwd", self._libervia_generate_pwd_constr, Loader=Loader)
- yaml.add_constructor(
- "!libervia_param", self._libervia_param_constr, Loader=Loader)
+ "!libervia_param", self._libervia_param_constr, Loader=Loader
+ )
def unload(self):
log.debug("unloading applications manager")
for instances in self._started.values():
for instance in instances:
- data = instance['data']
- if not data['single_instance']:
+ data = instance["data"]
+ if not data["single_instance"]:
log.debug(
- f"cleaning temporary directory at {data['_instance_dir_path']}")
- data['_instance_dir_obj'].cleanup()
+ f"cleaning temporary directory at {data['_instance_dir_path']}"
+ )
+ data["_instance_dir_obj"].cleanup()
def _libervia_conf_constr(self, loader, node) -> str:
"""Get a value from Libervia configuration
@@ -183,22 +179,27 @@ class AppManager:
value = self.host.memory.config_get(section, name, default)
# FIXME: "public_url" is used only here and doesn't take multi-sites into account
- if name == "public_url" and (not value or value.startswith('http')):
+ if name == "public_url" and (not value or value.startswith("http")):
if not value:
- log.warning(_(
- 'No value found for "public_url", using "example.org" for '
- 'now, please set the proper value in libervia.conf'))
+ log.warning(
+ _(
+ 'No value found for "public_url", using "example.org" for '
+ "now, please set the proper value in libervia.conf"
+ )
+ )
else:
- log.warning(_(
- 'invalid value for "public_url" ({value}), it musts not start with '
- 'schema ("http"), ignoring it and using "example.org" '
- 'instead')
- .format(value=value))
+ log.warning(
+ _(
+ 'invalid value for "public_url" ({value}), it musts not start with '
+ 'schema ("http"), ignoring it and using "example.org" '
+ "instead"
+ ).format(value=value)
+ )
value = "example.org"
if filter_ is None:
pass
- elif filter_ == 'first':
+ elif filter_ == "first":
value = value[0]
elif filter_ == "not":
value = C.bool(value)
@@ -232,7 +233,7 @@ class AppManager:
except KeyError:
alphabet = string.ascii_letters + string.digits
key_size = int(kwargs.get("size", 30))
- key = ''.join(secrets.choice(alphabet) for __ in range(key_size))
+ key = "".join(secrets.choice(alphabet) for __ in range(key_size))
self._app_persistent_data[pwd_data_key] = key
else:
log.debug(f"Re-using existing key for {name!r} password.")
@@ -255,7 +256,8 @@ class AppManager:
name = manager.name
if name in self._managers:
raise exceptions.ConflictError(
- f"There is already a manager with the name {name}")
+ f"There is already a manager with the name {name}"
+ )
self._managers[manager.name] = manager
if manager.discover_path is not None:
self.discover(manager.discover_path, manager)
@@ -269,25 +271,18 @@ class AppManager:
try:
app_type = app_data["type"]
except KeyError:
- raise exceptions.DataError(
- "app file doesn't have the mandatory \"type\" key"
- )
+ raise exceptions.DataError('app file doesn\'t have the mandatory "type" key')
if not isinstance(app_type, str):
- raise exceptions.DataError(
- f"invalid app data type: {app_type!r}"
- )
+ raise exceptions.DataError(f"invalid app data type: {app_type!r}")
app_type = app_type.strip()
try:
return self._managers[app_type]
except KeyError:
raise exceptions.NotFound(
- f"No manager found to manage app of type {app_type!r}")
+ f"No manager found to manage app of type {app_type!r}"
+ )
- def get_app_data(
- self,
- id_type: Optional[str],
- identifier: str
- ) -> dict:
+ def get_app_data(self, id_type: Optional[str], identifier: str) -> dict:
"""Retrieve instance's app_data from identifier
@param id_type: type of the identifier, can be:
@@ -300,8 +295,8 @@ class AppManager:
@raise ValueError: id_type is invalid
"""
if not id_type:
- id_type = 'name'
- if id_type == 'name':
+ id_type = "name"
+ if id_type == "name":
identifier = identifier.lower().strip()
try:
return next(iter(self._started[identifier]))
@@ -309,7 +304,7 @@ class AppManager:
raise exceptions.NotFound(
f"No instance of {identifier!r} is currently running"
)
- elif id_type == 'instance':
+ elif id_type == "instance":
instance_id = identifier
try:
return self._instances[instance_id]
@@ -320,11 +315,7 @@ class AppManager:
else:
raise ValueError(f"invalid id_type: {id_type!r}")
- def discover(
- self,
- dir_path: Path,
- manager: AppManagerBackend|None = None
- ) -> None:
+ def discover(self, dir_path: Path, manager: AppManagerBackend | None = None) -> None:
"""Search for app configuration file.
App configuration files must start with [APP_FILE_PREFIX] and have a ``.yaml``
@@ -336,19 +327,16 @@ class AppManager:
app_data = self.parse(file_path)
manager = self.get_manager(app_data)
except (exceptions.DataError, exceptions.NotFound) as e:
- log.warning(
- f"Can't parse {file_path}, skipping: {e}")
+ log.warning(f"Can't parse {file_path}, skipping: {e}")
continue
- app_name = file_path.stem[len(APP_FILE_PREFIX):].strip().lower()
+ app_name = file_path.stem[len(APP_FILE_PREFIX) :].strip().lower()
if not app_name:
log.warning(f"invalid app file name at {file_path}")
continue
app_dict = self._apps.setdefault(app_name, {})
manager_set = app_dict.setdefault(manager, set())
manager_set.add(file_path)
- log.debug(
- f"{app_name!r} {manager.name} application found"
- )
+ log.debug(f"{app_name!r} {manager.name} application found")
def parse(self, file_path: Path, params: Optional[dict] = None) -> dict:
"""Parse Libervia application file
@@ -367,7 +355,7 @@ class AppManager:
if "name" not in app_data:
# note that we don't use lower() here as we want human readable name and
# uppercase may be set on purpose
- app_data['name'] = file_path.stem[len(APP_FILE_PREFIX):].strip()
+ app_data["name"] = file_path.stem[len(APP_FILE_PREFIX) :].strip()
single_instance = app_data.setdefault("single_instance", True)
if not isinstance(single_instance, bool):
raise ValueError(
@@ -429,9 +417,7 @@ class AppManager:
try:
app_file_path = next(iter(next(iter(self._apps[app_name].values()))))
except KeyError:
- raise exceptions.NotFound(
- f"No application found with the name {app_name!r}"
- )
+ raise exceptions.NotFound(f"No application found with the name {app_name!r}")
log.info(f"starting {app_name!r}")
self._app_persistent_data = await self.persistent_data.get(app_name) or {}
self._app_persistent_data["last_started"] = time.time()
@@ -439,13 +425,10 @@ class AppManager:
app_data = self.parse(app_file_path, extra)
await self.persistent_data.aset(app_name, self._app_persistent_data)
app_data["_started"] = False
- app_data['_file_path'] = app_file_path
- app_data['_name_canonical'] = app_name
- single_instance = app_data['single_instance']
- ret_data = {
- "name": app_name,
- "started": False
- }
+ app_data["_file_path"] = app_file_path
+ app_data["_name_canonical"] = app_name
+ single_instance = app_data["single_instance"]
+ ret_data = {"name": app_name, "started": False}
if single_instance:
if started_data:
instance_data = started_data[0]
@@ -462,14 +445,14 @@ class AppManager:
PLUGIN_INFO[C.PI_IMPORT_NAME], app_name
)
cache_path.mkdir(0o700, parents=True, exist_ok=True)
- app_data['_instance_dir_path'] = cache_path
+ app_data["_instance_dir_path"] = cache_path
else:
dest_dir_obj = tempfile.TemporaryDirectory(prefix="libervia_app_")
- app_data['_instance_dir_obj'] = dest_dir_obj
- app_data['_instance_dir_path'] = Path(dest_dir_obj.name)
- instance_id = ret_data["instance"] = app_data['_instance_id'] = shortuuid.uuid()
+ app_data["_instance_dir_obj"] = dest_dir_obj
+ app_data["_instance_dir_path"] = Path(dest_dir_obj.name)
+ instance_id = ret_data["instance"] = app_data["_instance_id"] = shortuuid.uuid()
manager = self.get_manager(app_data)
- app_data['_manager'] = manager
+ app_data["_manager"] = manager
started_data.append(app_data)
self._instances[instance_id] = app_data
# we retrieve exposed data such as url_prefix which can be useful computed exposed
@@ -482,7 +465,7 @@ class AppManager:
start = manager.start
except AttributeError:
raise exceptions.InternalError(
- f"{manager.name} doesn't have the mandatory \"start\" method"
+ f'{manager.name} doesn\'t have the mandatory "start" method'
)
else:
defer.ensureDeferred(self.start_app(start, app_data))
@@ -498,12 +481,8 @@ class AppManager:
self.host.bridge.application_error(
app_name,
instance_id,
- data_format.serialise(
- {
- "class": str(type(e)),
- "msg": str(e)
- }
- ))
+ data_format.serialise({"class": str(type(e)), "msg": str(e)}),
+ )
else:
app_data["_started"] = True
self.host.bridge.application_started(app_name, instance_id, "")
@@ -512,7 +491,8 @@ class AppManager:
def _stop(self, identifier, id_type, extra):
extra = data_format.deserialise(extra)
return defer.ensureDeferred(
- self.stop(str(identifier), str(id_type) or None, extra))
+ self.stop(str(identifier), str(id_type) or None, extra)
+ )
async def stop(
self,
@@ -527,15 +507,15 @@ class AppManager:
log.info(f"stopping {app_data['name']!r}")
- app_name = app_data['_name_canonical']
- instance_id = app_data['_instance_id']
- manager = app_data['_manager']
+ app_name = app_data["_name_canonical"]
+ instance_id = app_data["_instance_id"]
+ manager = app_data["_manager"]
try:
stop = manager.stop
except AttributeError:
raise exceptions.InternalError(
- f"{manager.name} doesn't have the mandatory \"stop\" method"
+ f'{manager.name} doesn\'t have the mandatory "stop" method'
)
else:
try:
@@ -551,7 +531,8 @@ class AppManager:
del self._instances[instance_id]
except KeyError:
log.error(
- f"INTERNAL ERROR: {instance_id!r} is not present in self._instances")
+ f"INTERNAL ERROR: {instance_id!r} is not present in self._instances"
+ )
try:
self._started[app_name].remove(app_data)
@@ -590,13 +571,13 @@ class AppManager:
to handle manager specific conventions.
"""
app_data = self.get_app_data(id_type, identifier)
- if app_data.get('_exposed_computed', False):
- return app_data['expose']
+ if app_data.get("_exposed_computed", False):
+ return app_data["expose"]
if extra is None:
extra = {}
expose = app_data.setdefault("expose", {})
if "passwords" in expose:
- passwords = expose['passwords']
+ passwords = expose["passwords"]
for name, value in list(passwords.items()):
if isinstance(value, list):
# if we have a list, is the sequence of keys leading to the value
@@ -605,7 +586,8 @@ class AppManager:
passwords[name] = self.get_app_data_value(value, app_data)
except KeyError:
log.warning(
- f"Can't retrieve exposed value for password {name!r}: {e}")
+ f"Can't retrieve exposed value for password {name!r}: {e}"
+ )
del passwords[name]
for key in ("url_prefix", "front_url"):
@@ -614,9 +596,7 @@ class AppManager:
try:
expose[key] = self.get_app_data_value(value, app_data)
except KeyError:
- log.warning(
- f"Can't retrieve exposed value for {key!r} at {value}"
- )
+ log.warning(f"Can't retrieve exposed value for {key!r} at {value}")
del expose[key]
front_url = expose.get("front_url")
@@ -628,35 +608,35 @@ class AppManager:
path_elt = parsed_url.path.split("/", 1)
parsed_url = parsed_url._replace(
netloc=path_elt[0],
- path=f"/{path_elt[1]}" if len(path_elt) > 1 else ""
+ path=f"/{path_elt[1]}" if len(path_elt) > 1 else "",
)
- parsed_url = parsed_url._replace(scheme='https')
+ parsed_url = parsed_url._replace(scheme="https")
expose["front_url"] = urlunparse(parsed_url)
if extra.get("skip_compute", False):
return expose
try:
- compute_expose = app_data['_manager'].compute_expose
+ compute_expose = app_data["_manager"].compute_expose
except AttributeError:
pass
else:
await compute_expose(app_data)
- app_data['_exposed_computed'] = True
+ app_data["_exposed_computed"] = True
return expose
async def _do_prepare(
self,
app_data: dict,
) -> None:
- name = app_data['name']
- dest_path = app_data['_instance_dir_path']
+ name = app_data["name"]
+ dest_path = app_data["_instance_dir_path"]
if next(dest_path.iterdir(), None) != None:
log.debug(f"There is already a prepared dir at {dest_path}, nothing to do")
return
try:
- prepare = app_data['prepare'].copy()
+ prepare = app_data["prepare"].copy()
except KeyError:
prepare = {}
@@ -668,7 +648,7 @@ class AppManager:
log.debug(f"[{name}] [prepare] running {action!r} action")
if action == "git":
try:
- git_path = which('git')[0]
+ git_path = which("git")[0]
except IndexError:
raise exceptions.NotFound(
"Can't find \"git\" executable, {name} can't be started without it"
@@ -688,8 +668,8 @@ class AppManager:
self,
app_data: dict,
) -> None:
- dest_path = app_data['_instance_dir_path']
- files = app_data.get('files')
+ dest_path = app_data["_instance_dir_path"]
+ files = app_data.get("files")
if not files:
return
if not isinstance(files, dict):
diff --git a/libervia/backend/plugins/plugin_misc_app_manager/models.py b/libervia/backend/plugins/plugin_misc_app_manager/models.py
index 69da84d9..40794635 100644
--- a/libervia/backend/plugins/plugin_misc_app_manager/models.py
+++ b/libervia/backend/plugins/plugin_misc_app_manager/models.py
@@ -27,7 +27,7 @@ class AppManagerBackend(abc.ABC):
"""Abstract class for App Manager."""
name: str
- discover_path: Path|None = None
+ discover_path: Path | None = None
def __init__(self, host) -> None:
"""Initialize the App Manager.
diff --git a/libervia/backend/plugins/plugin_misc_attach.py b/libervia/backend/plugins/plugin_misc_attach.py
index dfcd9c52..e3cb9e24 100644
--- a/libervia/backend/plugins/plugin_misc_attach.py
+++ b/libervia/backend/plugins/plugin_misc_attach.py
@@ -49,7 +49,7 @@ PLUGIN_INFO = {
}
-AttachmentHandler = namedtuple('AttachmentHandler', ['can_handle', 'attach', 'priority'])
+AttachmentHandler = namedtuple("AttachmentHandler", ["can_handle", "attach", "priority"])
class AttachPlugin:
@@ -60,7 +60,7 @@ class AttachPlugin:
self._u = host.plugins["UPLOAD"]
host.trigger.add("sendMessage", self._send_message_trigger)
host.trigger.add("sendMessageComponent", self._send_message_trigger)
- self._attachments_handlers = {'clear': [], 'encrypted': []}
+ self._attachments_handlers = {"clear": [], "encrypted": []}
self.register(self.default_can_handle, self.default_attach, False, -1000)
def register(self, can_handle, attach, encrypted=False, priority=0):
@@ -82,12 +82,13 @@ class AttachPlugin:
"""
handler = AttachmentHandler(can_handle, attach, priority)
handlers = (
- self._attachments_handlers['encrypted']
- if encrypted else self._attachments_handlers['clear']
+ self._attachments_handlers["encrypted"]
+ if encrypted
+ else self._attachments_handlers["clear"]
)
if handler in handlers:
raise exceptions.InternalError(
- 'Attachment handler has been registered twice, this should never happen'
+ "Attachment handler has been registered twice, this should never happen"
)
handlers.append(handler)
@@ -112,33 +113,40 @@ class AttachPlugin:
media_type = mimetypes.guess_type(path, strict=False)[0]
if media_type is None:
log.warning(
- _("Can't resize attachment of unknown type: {attachment}")
- .format(attachment=attachment))
+ _(
+ "Can't resize attachment of unknown type: {attachment}"
+ ).format(attachment=attachment)
+ )
continue
attachment[C.KEY_ATTACHMENTS_MEDIA_TYPE] = media_type
- main_type = media_type.split('/')[0]
+ main_type = media_type.split("/")[0]
if main_type == "image":
report = image.check(self.host, path)
- if report['too_large']:
+ if report["too_large"]:
tmp_dir = Path(tempfile.mkdtemp())
tmp_dirs_to_clean.append(tmp_dir)
new_path = tmp_dir / path.name
await image.resize(
- path, report["recommended_size"], dest=new_path)
+ path, report["recommended_size"], dest=new_path
+ )
attachment["path"] = new_path
log.info(
- _("Attachment {path!r} has been resized at {new_path!r}")
- .format(path=str(path), new_path=str(new_path)))
+ _(
+ "Attachment {path!r} has been resized at {new_path!r}"
+ ).format(path=str(path), new_path=str(new_path))
+ )
else:
log.warning(
- _("Can't resize attachment of type {main_type!r}: {attachment}")
- .format(main_type=main_type, attachment=attachment))
+ _(
+ "Can't resize attachment of type {main_type!r}: {attachment}"
+ ).format(main_type=main_type, attachment=attachment)
+ )
if client.encryption.is_encryption_requested(data):
- handlers = self._attachments_handlers['encrypted']
+ handlers = self._attachments_handlers["encrypted"]
else:
- handlers = self._attachments_handlers['clear']
+ handlers = self._attachments_handlers["clear"]
for handler in handlers:
can_handle = await utils.as_deferred(handler.can_handle, client, data)
@@ -147,8 +155,9 @@ class AttachPlugin:
else:
raise exceptions.NotFound(
_("No plugin can handle attachment with {destinee}").format(
- destinee = data['to']
- ))
+ destinee=data["to"]
+ )
+ )
await utils.as_deferred(handler.attach, client, data)
@@ -159,10 +168,7 @@ class AttachPlugin:
return data
async def upload_files(
- self,
- client: SatXMPPEntity,
- data: dict,
- upload_cb: Optional[Callable] = None
+ self, client: SatXMPPEntity, data: dict, upload_cb: Optional[Callable] = None
):
"""Upload file, and update attachments
@@ -202,7 +208,7 @@ class AttachPlugin:
continue
if "url" in attachment:
- url = attachment.pop('url')
+ url = attachment.pop("url")
log.warning(
f"unexpected URL in attachment: {url!r}\nattachment: {attachment}"
)
@@ -214,18 +220,18 @@ class AttachPlugin:
attachment["size"] = path.stat().st_size
- extra = {
- "attachment": attachment
- }
+ extra = {"attachment": attachment}
progress_id = attachment.pop("progress_id", None)
if progress_id:
extra["progress_id"] = progress_id
check_certificate = self.host.memory.param_get_a(
- "check_certificate", "Connection", profile_key=client.profile)
+ "check_certificate", "Connection", profile_key=client.profile
+ )
if not check_certificate:
- extra['ignore_tls_errors'] = True
+ extra["ignore_tls_errors"] = True
log.warning(
- _("certificate check disabled for upload, this is dangerous!"))
+ _("certificate check disabled for upload, this is dangerous!")
+ )
__, upload_d = await upload_cb(
client=client,
@@ -255,8 +261,9 @@ class AttachPlugin:
return defer.ensureDeferred(self.attach_files(client, data))
def _send_message_trigger(
- self, client, mess_data, pre_xml_treatments, post_xml_treatments):
- if mess_data['extra'].get(C.KEY_ATTACHMENTS):
+ self, client, mess_data, pre_xml_treatments, post_xml_treatments
+ ):
+ if mess_data["extra"].get(C.KEY_ATTACHMENTS):
post_xml_treatments.addCallback(self._attach_files, client=client)
return True
@@ -271,8 +278,8 @@ class AttachPlugin:
body_elt = data["xml"].addElement("body")
attachments = data["extra"][C.KEY_ATTACHMENTS]
if attachments:
- body_links = '\n'.join(a['url'] for a in attachments)
+ body_links = "\n".join(a["url"] for a in attachments)
if str(body_elt).strip():
# if there is already a body, we add a line feed before the first link
- body_elt.addContent('\n')
+ body_elt.addContent("\n")
body_elt.addContent(body_links)
diff --git a/libervia/backend/plugins/plugin_misc_download.py b/libervia/backend/plugins/plugin_misc_download.py
index a80f7a2e..f26d4f61 100644
--- a/libervia/backend/plugins/plugin_misc_download.py
+++ b/libervia/backend/plugins/plugin_misc_download.py
@@ -72,18 +72,20 @@ class DownloadPlugin(object):
)
self._download_callbacks = {}
self._scheme_callbacks = {}
- self.register_scheme('http', self.download_http)
- self.register_scheme('https', self.download_http)
+ self.register_scheme("http", self.download_http)
+ self.register_scheme("https", self.download_http)
def _file_download(
- self, attachment_s: str, dest_path: str, extra_s: str, profile: str
+ self, attachment_s: str, dest_path: str, extra_s: str, profile: str
) -> defer.Deferred:
- d = defer.ensureDeferred(self.file_download(
- self.host.get_client(profile),
- data_format.deserialise(attachment_s),
- Path(dest_path),
- data_format.deserialise(extra_s)
- ))
+ d = defer.ensureDeferred(
+ self.file_download(
+ self.host.get_client(profile),
+ data_format.deserialise(attachment_s),
+ Path(dest_path),
+ data_format.deserialise(extra_s),
+ )
+ )
d.addCallback(lambda ret: data_format.serialise(ret))
return d
@@ -92,7 +94,7 @@ class DownloadPlugin(object):
client: SatXMPPEntity,
attachment: Dict[str, Any],
dest_path: Path,
- extra: Optional[Dict[str, Any]] = None
+ extra: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""Download a file using best available method
@@ -103,8 +105,10 @@ class DownloadPlugin(object):
try:
progress_id, __ = await self.download(client, attachment, dest_path, extra)
except Exception as e:
- if (isinstance(e, jabber_error.StanzaError)
- and e.condition == 'not-acceptable'):
+ if (
+ isinstance(e, jabber_error.StanzaError)
+ and e.condition == "not-acceptable"
+ ):
reason = e.text
else:
reason = str(e)
@@ -119,14 +123,16 @@ class DownloadPlugin(object):
return {"progress": progress_id}
def _file_download_complete(
- self, attachment_s: str, dest_path: str, extra_s: str, profile: str
+ self, attachment_s: str, dest_path: str, extra_s: str, profile: str
) -> defer.Deferred:
- d = defer.ensureDeferred(self.file_download_complete(
- self.host.get_client(profile),
- data_format.deserialise(attachment_s),
- Path(dest_path),
- data_format.deserialise(extra_s)
- ))
+ d = defer.ensureDeferred(
+ self.file_download_complete(
+ self.host.get_client(profile),
+ data_format.deserialise(attachment_s),
+ Path(dest_path),
+ data_format.deserialise(extra_s),
+ )
+ )
d.addCallback(lambda path: str(path))
return d
@@ -135,7 +141,7 @@ class DownloadPlugin(object):
client: SatXMPPEntity,
attachment: Dict[str, Any],
dest_path: Path,
- extra: Optional[Dict[str, Any]] = None
+ extra: Optional[Dict[str, Any]] = None,
) -> str:
"""Helper method to fully download a file and return its path
@@ -152,11 +158,11 @@ class DownloadPlugin(object):
client: SatXMPPEntity,
uri: str,
dest_path: Union[Path, str],
- extra: Optional[Dict[str, Any]] = None
+ extra: Optional[Dict[str, Any]] = None,
) -> Tuple[str, defer.Deferred]:
if extra is None:
extra = {}
- uri_parsed = urlparse(uri, 'http')
+ uri_parsed = urlparse(uri, "http")
if dest_path:
dest_path = Path(dest_path)
cache_uid = None
@@ -164,31 +170,32 @@ class DownloadPlugin(object):
filename = Path(unquote(uri_parsed.path)).name.strip() or C.FILE_DEFAULT_NAME
# we don't use Path.suffixes because we don't want to have more than 2
# suffixes, but we still want to handle suffixes like "tar.gz".
- stem, *suffixes = filename.rsplit('.', 2)
+ stem, *suffixes = filename.rsplit(".", 2)
# we hash the URL to have an unique identifier, and avoid double download
url_hash = hashlib.sha256(uri_parsed.geturl().encode()).hexdigest()
cache_uid = f"{stem}_{url_hash}"
cache_data = client.cache.get_metadata(cache_uid)
if cache_data is not None:
# file is already in cache, we return it
- download_d = defer.succeed(cache_data['path'])
- return '', download_d
+ download_d = defer.succeed(cache_data["path"])
+ return "", download_d
else:
# the file is not in cache
- unique_name = '.'.join([cache_uid] + suffixes)
+ unique_name = ".".join([cache_uid] + suffixes)
with client.cache.cache_data(
- "DOWNLOAD", cache_uid, filename=unique_name) as f:
+ "DOWNLOAD", cache_uid, filename=unique_name
+ ) as f:
# we close the file and only use its name, the file will be opened
# by the registered callback
dest_path = Path(f.name)
# should we check certificates?
check_certificate = self.host.memory.param_get_a(
- "check_certificate", "Connection", profile_key=client.profile)
+ "check_certificate", "Connection", profile_key=client.profile
+ )
if not check_certificate:
- extra['ignore_tls_errors'] = True
- log.warning(
- _("certificate check disabled for download, this is dangerous!"))
+ extra["ignore_tls_errors"] = True
+ log.warning(_("certificate check disabled for download, this is dangerous!"))
try:
callback = self._scheme_callbacks[uri_parsed.scheme]
@@ -197,11 +204,12 @@ class DownloadPlugin(object):
else:
try:
progress_id, download_d = await callback(
- client, uri_parsed, dest_path, extra)
+ client, uri_parsed, dest_path, extra
+ )
except Exception as e:
- log.warning(_(
- "Can't download URI {uri}: {reason}").format(
- uri=uri, reason=e))
+ log.warning(
+ _("Can't download URI {uri}: {reason}").format(uri=uri, reason=e)
+ )
if cache_uid is not None:
client.cache.remove_from_cache(cache_uid)
elif dest_path.exists():
@@ -210,13 +218,12 @@ class DownloadPlugin(object):
download_d.addCallback(lambda __: dest_path)
return progress_id, download_d
-
async def download(
self,
client: SatXMPPEntity,
attachment: Dict[str, Any],
dest_path: Union[Path, str],
- extra: Optional[Dict[str, Any]] = None
+ extra: Optional[Dict[str, Any]] = None,
) -> Tuple[str, defer.Deferred]:
"""Download a file from URI using suitable method
@@ -247,9 +254,7 @@ class DownloadPlugin(object):
try:
cb = self._download_callbacks[source_type]
except KeyError:
- log.warning(
- f"no source handler registered for {source_type!r}"
- )
+ log.warning(f"no source handler registered for {source_type!r}")
else:
try:
return await cb(client, attachment, source, dest_path, extra)
@@ -271,11 +276,14 @@ class DownloadPlugin(object):
source_type: str,
callback: Callable[
[
- SatXMPPEntity, Dict[str, Any], Dict[str, Any], Union[str, Path],
- Dict[str, Any]
+ SatXMPPEntity,
+ Dict[str, Any],
+ Dict[str, Any],
+ Union[str, Path],
+ Dict[str, Any],
],
- Tuple[str, defer.Deferred]
- ]
+ Tuple[str, defer.Deferred],
+ ],
) -> None:
"""Register a handler to manage a type of attachment source
@@ -334,17 +342,15 @@ class DownloadPlugin(object):
async def download_http(self, client, uri_parsed, dest_path, options):
url = uri_parsed.geturl()
- if options.get('ignore_tls_errors', False):
- log.warning(
- "TLS certificate check disabled, this is highly insecure"
- )
+ if options.get("ignore_tls_errors", False):
+ log.warning("TLS certificate check disabled, this is highly insecure")
treq_client = treq_client_no_ssl
else:
treq_client = treq
head_data = await treq_client.head(url)
try:
- content_length = int(head_data.headers.getRawHeaders('content-length')[0])
+ content_length = int(head_data.headers.getRawHeaders("content-length")[0])
except (KeyError, TypeError, IndexError):
content_length = None
log.debug(f"No content lenght found at {url}")
@@ -353,7 +359,7 @@ class DownloadPlugin(object):
client,
dest_path,
mode="wb",
- size = content_length,
+ size=content_length,
)
progress_id = file_obj.uid
diff --git a/libervia/backend/plugins/plugin_misc_email_invitation.py b/libervia/backend/plugins/plugin_misc_email_invitation.py
index 1f08b55f..8c688177 100644
--- a/libervia/backend/plugins/plugin_misc_email_invitation.py
+++ b/libervia/backend/plugins/plugin_misc_email_invitation.py
@@ -38,27 +38,37 @@ PLUGIN_INFO = {
C.PI_NAME: "Email Invitations",
C.PI_IMPORT_NAME: "EMAIL_INVITATION",
C.PI_TYPE: C.PLUG_TYPE_MISC,
- C.PI_DEPENDENCIES: ['XEP-0077'],
+ C.PI_DEPENDENCIES: ["XEP-0077"],
C.PI_RECOMMENDATIONS: ["IDENTITY"],
C.PI_MAIN: "InvitationsPlugin",
C.PI_HANDLER: "no",
- C.PI_DESCRIPTION: _("""invitation of people without XMPP account""")
+ C.PI_DESCRIPTION: _("""invitation of people without XMPP account"""),
}
SUFFIX_MAX = 5
INVITEE_PROFILE_TPL = "guest@@{uuid}"
-KEY_ID = 'id'
-KEY_JID = 'jid'
-KEY_CREATED = 'created'
-KEY_LAST_CONNECTION = 'last_connection'
-KEY_GUEST_PROFILE = 'guest_profile'
-KEY_PASSWORD = 'password'
-KEY_EMAILS_EXTRA = 'emails_extra'
-EXTRA_RESERVED = {KEY_ID, KEY_JID, KEY_CREATED, 'jid_', 'jid', KEY_LAST_CONNECTION,
- KEY_GUEST_PROFILE, KEY_PASSWORD, KEY_EMAILS_EXTRA}
+KEY_ID = "id"
+KEY_JID = "jid"
+KEY_CREATED = "created"
+KEY_LAST_CONNECTION = "last_connection"
+KEY_GUEST_PROFILE = "guest_profile"
+KEY_PASSWORD = "password"
+KEY_EMAILS_EXTRA = "emails_extra"
+EXTRA_RESERVED = {
+ KEY_ID,
+ KEY_JID,
+ KEY_CREATED,
+ "jid_",
+ "jid",
+ KEY_LAST_CONNECTION,
+ KEY_GUEST_PROFILE,
+ KEY_PASSWORD,
+ KEY_EMAILS_EXTRA,
+}
DEFAULT_SUBJECT = D_("You have been invited by {host_name} to {app_name}")
-DEFAULT_BODY = D_("""Hello {name}!
+DEFAULT_BODY = D_(
+ """Hello {name}!
You have received an invitation from {host_name} to participate to "{app_name}".
To join, you just have to click on the following URL:
@@ -68,7 +78,8 @@ Please note that this URL should not be shared with anybody!
If you want more details on {app_name}, you can check {app_url}.
Welcome!
-""")
+"""
+)
class InvitationsPlugin(object):
@@ -76,39 +87,79 @@ class InvitationsPlugin(object):
def __init__(self, host):
log.info(_("plugin Invitations initialization"))
self.host = host
- self.invitations = persistent.LazyPersistentBinaryDict('invitations')
- host.bridge.add_method("invitation_create", ".plugin", in_sign='sasssssssssa{ss}s',
- out_sign='a{ss}',
- method=self._create,
- async_=True)
- host.bridge.add_method("invitation_get", ".plugin", in_sign='s', out_sign='a{ss}',
- method=self.get,
- async_=True)
- host.bridge.add_method("invitation_delete", ".plugin", in_sign='s', out_sign='',
- method=self._delete,
- async_=True)
- host.bridge.add_method("invitation_modify", ".plugin", in_sign='sa{ss}b',
- out_sign='',
- method=self._modify,
- async_=True)
- host.bridge.add_method("invitation_list", ".plugin", in_sign='s',
- out_sign='a{sa{ss}}',
- method=self._list,
- async_=True)
- host.bridge.add_method("invitation_simple_create", ".plugin", in_sign='sssss',
- out_sign='a{ss}',
- method=self._simple_create,
- async_=True)
+ self.invitations = persistent.LazyPersistentBinaryDict("invitations")
+ host.bridge.add_method(
+ "invitation_create",
+ ".plugin",
+ in_sign="sasssssssssa{ss}s",
+ out_sign="a{ss}",
+ method=self._create,
+ async_=True,
+ )
+ host.bridge.add_method(
+ "invitation_get",
+ ".plugin",
+ in_sign="s",
+ out_sign="a{ss}",
+ method=self.get,
+ async_=True,
+ )
+ host.bridge.add_method(
+ "invitation_delete",
+ ".plugin",
+ in_sign="s",
+ out_sign="",
+ method=self._delete,
+ async_=True,
+ )
+ host.bridge.add_method(
+ "invitation_modify",
+ ".plugin",
+ in_sign="sa{ss}b",
+ out_sign="",
+ method=self._modify,
+ async_=True,
+ )
+ host.bridge.add_method(
+ "invitation_list",
+ ".plugin",
+ in_sign="s",
+ out_sign="a{sa{ss}}",
+ method=self._list,
+ async_=True,
+ )
+ host.bridge.add_method(
+ "invitation_simple_create",
+ ".plugin",
+ in_sign="sssss",
+ out_sign="a{ss}",
+ method=self._simple_create,
+ async_=True,
+ )
def check_extra(self, extra):
if EXTRA_RESERVED.intersection(extra):
raise ValueError(
- _("You can't use following key(s) in extra, they are reserved: {}")
- .format(', '.join(EXTRA_RESERVED.intersection(extra))))
+ _(
+ "You can't use following key(s) in extra, they are reserved: {}"
+ ).format(", ".join(EXTRA_RESERVED.intersection(extra)))
+ )
- def _create(self, email='', emails_extra=None, jid_='', password='', name='',
- host_name='', language='', url_template='', message_subject='',
- message_body='', extra=None, profile=''):
+ def _create(
+ self,
+ email="",
+ emails_extra=None,
+ jid_="",
+ password="",
+ name="",
+ host_name="",
+ language="",
+ url_template="",
+ message_subject="",
+ message_body="",
+ extra=None,
+ profile="",
+ ):
# XXX: we don't use **kwargs here to keep arguments name for introspection with
# D-Bus bridge
if emails_extra is None:
@@ -117,16 +168,24 @@ class InvitationsPlugin(object):
if extra is None:
extra = {}
else:
- extra = {str(k): str(v) for k,v in extra.items()}
+ extra = {str(k): str(v) for k, v in extra.items()}
- kwargs = {"extra": extra,
- KEY_EMAILS_EXTRA: [str(e) for e in emails_extra]
- }
+ kwargs = {"extra": extra, KEY_EMAILS_EXTRA: [str(e) for e in emails_extra]}
# we need to be sure that values are unicode, else they won't be pickled correctly
# with D-Bus
- for key in ("jid_", "password", "name", "host_name", "email", "language",
- "url_template", "message_subject", "message_body", "profile"):
+ for key in (
+ "jid_",
+ "password",
+ "name",
+ "host_name",
+ "email",
+ "language",
+ "url_template",
+ "message_subject",
+ "message_body",
+ "profile",
+ ):
value = locals()[key]
if value:
kwargs[key] = str(value)
@@ -152,16 +211,13 @@ class InvitationsPlugin(object):
return invitation
async def _create_account_and_profile(
- self,
- id_: str,
- kwargs: dict,
- extra: dict
+ self, id_: str, kwargs: dict, extra: dict
) -> None:
"""Create XMPP account and Libervia profile for guest"""
## XMPP account creation
- password = kwargs.pop('password', None)
+ password = kwargs.pop("password", None)
if password is None:
- password = utils.generate_password()
+ password = utils.generate_password()
assert password
# XXX: password is here saved in clear in database
# it is needed for invitation as the same password is used for profile
@@ -171,14 +227,15 @@ class InvitationsPlugin(object):
# not be saved and could be used to encrypt profile password.
extra[KEY_PASSWORD] = password
- jid_ = kwargs.pop('jid_', None)
+ jid_ = kwargs.pop("jid_", None)
if not jid_:
- domain = self.host.memory.config_get(None, 'xmpp_domain')
+ domain = self.host.memory.config_get(None, "xmpp_domain")
if not domain:
# TODO: fallback to profile's domain
raise ValueError(_("You need to specify xmpp_domain in sat.conf"))
- jid_ = "invitation-{uuid}@{domain}".format(uuid=shortuuid.uuid(),
- domain=domain)
+ jid_ = "invitation-{uuid}@{domain}".format(
+ uuid=shortuuid.uuid(), domain=domain
+ )
jid_ = jid.JID(jid_)
extra[KEY_JID] = jid_.full()
@@ -186,42 +243,46 @@ class InvitationsPlugin(object):
# we don't register account if there is no user as anonymous login is then
# used
try:
- await self.host.plugins['XEP-0077'].register_new_account(jid_, password)
+ await self.host.plugins["XEP-0077"].register_new_account(jid_, password)
except error.StanzaError as e:
prefix = jid_.user
idx = 0
- while e.condition == 'conflict':
+ while e.condition == "conflict":
if idx >= SUFFIX_MAX:
raise exceptions.ConflictError(_("Can't create XMPP account"))
- jid_.user = prefix + '_' + str(idx)
- log.info(_("requested jid already exists, trying with {}".format(
- jid_.full())))
+ jid_.user = prefix + "_" + str(idx)
+ log.info(
+ _(
+ "requested jid already exists, trying with {}".format(
+ jid_.full()
+ )
+ )
+ )
try:
- await self.host.plugins['XEP-0077'].register_new_account(
- jid_,
- password
+ await self.host.plugins["XEP-0077"].register_new_account(
+ jid_, password
)
except error.StanzaError:
idx += 1
else:
break
- if e.condition != 'conflict':
+ if e.condition != "conflict":
raise e
log.info(_("account {jid_} created").format(jid_=jid_.full()))
## profile creation
- extra[KEY_GUEST_PROFILE] = guest_profile = INVITEE_PROFILE_TPL.format(
- uuid=id_
- )
+ extra[KEY_GUEST_PROFILE] = guest_profile = INVITEE_PROFILE_TPL.format(uuid=id_)
# profile creation should not fail as we generate unique name ourselves
await self.host.memory.create_profile(guest_profile, password)
await self.host.memory.start_session(password, guest_profile)
- await self.host.memory.param_set("JabberID", jid_.full(), "Connection",
- profile_key=guest_profile)
- await self.host.memory.param_set("Password", password, "Connection",
- profile_key=guest_profile)
+ await self.host.memory.param_set(
+ "JabberID", jid_.full(), "Connection", profile_key=guest_profile
+ )
+ await self.host.memory.param_set(
+ "Password", password, "Connection", profile_key=guest_profile
+ )
async def create(self, **kwargs):
r"""Create an invitation
@@ -284,15 +345,17 @@ class InvitationsPlugin(object):
- filled extra dictionary, as saved in the databae
"""
## initial checks
- extra = kwargs.pop('extra', {})
+ extra = kwargs.pop("extra", {})
if set(kwargs).intersection(extra):
raise ValueError(
_("You can't use following key(s) in both args and extra: {}").format(
- ', '.join(set(kwargs).intersection(extra))))
+ ", ".join(set(kwargs).intersection(extra))
+ )
+ )
self.check_extra(extra)
- email = kwargs.pop('email', None)
+ email = kwargs.pop("email", None)
existing = await self.get_existing_invitation(email)
if existing is not None:
@@ -300,16 +363,20 @@ class InvitationsPlugin(object):
extra.update(existing)
del extra[KEY_ID]
- emails_extra = kwargs.pop('emails_extra', [])
+ emails_extra = kwargs.pop("emails_extra", [])
if not email and emails_extra:
raise ValueError(
- _('You need to provide a main email address before using emails_extra'))
+ _("You need to provide a main email address before using emails_extra")
+ )
- if (email is not None
- and not 'url_template' in kwargs
- and not 'message_body' in kwargs):
+ if (
+ email is not None
+ and not "url_template" in kwargs
+ and not "message_body" in kwargs
+ ):
raise ValueError(
- _("You need to provide url_template if you use default message body"))
+ _("You need to provide url_template if you use default message body")
+ )
## uuid
log.info(_("creating an invitation"))
@@ -318,64 +385,62 @@ class InvitationsPlugin(object):
if existing is None:
await self._create_account_and_profile(id_, kwargs, extra)
- profile = kwargs.pop('profile', None)
+ profile = kwargs.pop("profile", None)
guest_profile = extra[KEY_GUEST_PROFILE]
jid_ = jid.JID(extra[KEY_JID])
## identity
- name = kwargs.pop('name', None)
+ name = kwargs.pop("name", None)
password = extra[KEY_PASSWORD]
if name is not None:
- extra['name'] = name
+ extra["name"] = name
try:
- id_plugin = self.host.plugins['IDENTITY']
+ id_plugin = self.host.plugins["IDENTITY"]
except KeyError:
pass
else:
await self.host.connect(guest_profile, password)
guest_client = self.host.get_client(guest_profile)
- await id_plugin.set_identity(guest_client, {'nicknames': [name]})
+ await id_plugin.set_identity(guest_client, {"nicknames": [name]})
await self.host.disconnect(guest_profile)
## email
- language = kwargs.pop('language', None)
+ language = kwargs.pop("language", None)
if language is not None:
- extra['language'] = language.strip()
+ extra["language"] = language.strip()
if email is not None:
- extra['email'] = email
+ extra["email"] = email
data_format.iter2dict(KEY_EMAILS_EXTRA, extra)
- url_template = kwargs.pop('url_template', '')
- format_args = {
- 'uuid': id_,
- 'app_name': C.APP_NAME,
- 'app_url': C.APP_URL}
+ url_template = kwargs.pop("url_template", "")
+ format_args = {"uuid": id_, "app_name": C.APP_NAME, "app_url": C.APP_URL}
if name is None:
- format_args['name'] = email
+ format_args["name"] = email
else:
- format_args['name'] = name
+ format_args["name"] = name
if profile is None:
- format_args['profile'] = ''
+ format_args["profile"] = ""
else:
- format_args['profile'] = extra['profile'] = profile
+ format_args["profile"] = extra["profile"] = profile
- host_name = kwargs.pop('host_name', None)
+ host_name = kwargs.pop("host_name", None)
if host_name is None:
- format_args['host_name'] = profile or _("somebody")
+ format_args["host_name"] = profile or _("somebody")
else:
- format_args['host_name'] = extra['host_name'] = host_name
+ format_args["host_name"] = extra["host_name"] = host_name
invite_url = url_template.format(**format_args)
- format_args['url'] = invite_url
+ format_args["url"] = invite_url
await sat_email.send_email(
self.host.memory.config,
[email] + emails_extra,
- (kwargs.pop('message_subject', None) or DEFAULT_SUBJECT).format(
- **format_args),
- (kwargs.pop('message_body', None) or DEFAULT_BODY).format(**format_args),
+ (kwargs.pop("message_subject", None) or DEFAULT_SUBJECT).format(
+ **format_args
+ ),
+ (kwargs.pop("message_body", None) or DEFAULT_BODY).format(**format_args),
)
## roster
@@ -388,7 +453,7 @@ class InvitationsPlugin(object):
except Exception as e:
log.error(f"Can't get host profile: {profile}: {e}")
else:
- await self.host.contact_update(client, jid_, name, ['guests'])
+ await self.host.contact_update(client, jid_, name, ["guests"])
if kwargs:
log.warning(_("Not all arguments have been consumed: {}").format(kwargs))
@@ -410,11 +475,12 @@ class InvitationsPlugin(object):
d = defer.ensureDeferred(
self.simple_create(client, invitee_email, invitee_name, url_template, extra)
)
- d.addCallback(lambda data: {k: str(v) for k,v in data.items()})
+ d.addCallback(lambda data: {k: str(v) for k, v in data.items()})
return d
async def simple_create(
- self, client, invitee_email, invitee_name, url_template, extra):
+ self, client, invitee_email, invitee_name, url_template, extra
+ ):
"""Simplified method to invite somebody by email"""
return await self.create(
name=invitee_name,
@@ -439,19 +505,20 @@ class InvitationsPlugin(object):
"""Delete an invitation data and associated XMPP account"""
log.info(f"deleting invitation {id_}")
data = await self.get(id_)
- guest_profile = data['guest_profile']
- password = data['password']
+ guest_profile = data["guest_profile"]
+ password = data["password"]
try:
await self.host.connect(guest_profile, password)
guest_client = self.host.get_client(guest_profile)
# XXX: be extra careful to use guest_client and not client below, as this will
# delete the associated XMPP account
log.debug("deleting XMPP account")
- await self.host.plugins['XEP-0077'].unregister(guest_client, None)
+ await self.host.plugins["XEP-0077"].unregister(guest_client, None)
except (error.StanzaError, sasl.SASLAuthError) as e:
log.warning(
f"Can't delete {guest_profile}'s XMPP account, maybe it as already been "
- f"deleted: {e}")
+ f"deleted: {e}"
+ )
try:
await self.host.memory.profile_delete_async(guest_profile, True)
except Exception as e:
@@ -461,8 +528,7 @@ class InvitationsPlugin(object):
log.info(f"{id_} invitation has been deleted")
def _modify(self, id_, new_extra, replace):
- return self.modify(id_, {str(k): str(v) for k,v in new_extra.items()},
- replace)
+ return self.modify(id_, {str(k): str(v) for k, v in new_extra.items()}, replace)
def modify(self, id_, new_extra, replace=False):
"""Modify invitation data
@@ -475,6 +541,7 @@ class InvitationsPlugin(object):
@raise KeyError: there is not invitation with this id_
"""
self.check_extra(new_extra)
+
def got_current_data(current_data):
if replace:
new_data = new_extra
@@ -485,7 +552,7 @@ class InvitationsPlugin(object):
continue
else:
new_data = current_data
- for k,v in new_extra.items():
+ for k, v in new_extra.items():
if k in EXTRA_RESERVED:
log.warning(_("Skipping reserved key {key}").format(key=k))
continue
@@ -515,7 +582,10 @@ class InvitationsPlugin(object):
"""
invitations = await self.invitations.all()
if profile != C.PROF_KEY_NONE:
- invitations = {id_:data for id_, data in invitations.items()
- if data.get('profile') == profile}
+ invitations = {
+ id_: data
+ for id_, data in invitations.items()
+ if data.get("profile") == profile
+ }
return invitations
diff --git a/libervia/backend/plugins/plugin_misc_extra_pep.py b/libervia/backend/plugins/plugin_misc_extra_pep.py
index 7ff3ea0b..65505a51 100644
--- a/libervia/backend/plugins/plugin_misc_extra_pep.py
+++ b/libervia/backend/plugins/plugin_misc_extra_pep.py
@@ -71,4 +71,6 @@ class ExtraPEP(object):
host.memory.update_params(self.params)
def get_followed_entities(self, profile_key):
- return self.host.memory.param_get_a(PARAM_NAME, PARAM_KEY, profile_key=profile_key)
+ return self.host.memory.param_get_a(
+ PARAM_NAME, PARAM_KEY, profile_key=profile_key
+ )
diff --git a/libervia/backend/plugins/plugin_misc_file.py b/libervia/backend/plugins/plugin_misc_file.py
index 23757f8f..bca05ca5 100644
--- a/libervia/backend/plugins/plugin_misc_file.py
+++ b/libervia/backend/plugins/plugin_misc_file.py
@@ -57,7 +57,7 @@ SENDING = D_("Please select a file to send to {peer}")
SENDING_TITLE = D_("File sending")
CONFIRM = D_(
'{peer} wants to send the file "{name}" to you:\n{desc}\n\nThe file has a size of '
- '{size_human}\n\nDo you accept ?'
+ "{size_human}\n\nDo you accept ?"
)
CONFIRM_TITLE = D_("Confirm file transfer")
CONFIRM_OVERWRITE = D_("File {} already exists, are you sure you want to overwrite ?")
@@ -97,13 +97,19 @@ class FilePlugin:
name: str,
file_desc: str,
extra_s: str,
- profile: str = C.PROF_KEY_NONE
+ profile: str = C.PROF_KEY_NONE,
) -> defer.Deferred:
client = self.host.get_client(profile)
- d = defer.ensureDeferred(self.file_send(
- client, jid.JID(peer_jid_s), filepath, name or None, file_desc or None,
- data_format.deserialise(extra_s)
- ))
+ d = defer.ensureDeferred(
+ self.file_send(
+ client,
+ jid.JID(peer_jid_s),
+ filepath,
+ name or None,
+ file_desc or None,
+ data_format.deserialise(extra_s),
+ )
+ )
d.addCallback(data_format.serialise)
return d
@@ -111,10 +117,10 @@ class FilePlugin:
self,
client: SatXMPPEntity,
peer_jid: jid.JID,
- filepath: str|Path,
- filename: str|None=None,
- file_desc: str|None=None,
- extra: dict|None=None
+ filepath: str | Path,
+ filename: str | None = None,
+ file_desc: str | None = None,
+ extra: dict | None = None,
) -> dict:
"""Send a file using best available method
@@ -132,8 +138,9 @@ class FilePlugin:
if not filename:
filename = filepath.name
for manager, __ in self._file_managers:
- if await utils.as_deferred(manager.can_handle_file_send,
- client, peer_jid, str(filepath)):
+ if await utils.as_deferred(
+ manager.can_handle_file_send, client, peer_jid, str(filepath)
+ ):
try:
method_name = manager.name
except AttributeError:
@@ -144,19 +151,26 @@ class FilePlugin:
)
)
try:
- file_data= await utils.as_deferred(
- manager.file_send, client, peer_jid, str(filepath), filename, file_desc,
- extra
+ file_data = await utils.as_deferred(
+ manager.file_send,
+ client,
+ peer_jid,
+ str(filepath),
+ filename,
+ file_desc,
+ extra,
)
except Exception as e:
log.warning(
- _("Can't send {filepath} to {peer_jid} with {method_name}: "
- "{reason}").format(
- filepath=filepath,
- peer_jid=peer_jid,
- method_name=method_name,
- reason=e
- )
+ _(
+ "Can't send {filepath} to {peer_jid} with {method_name}: "
+ "{reason}"
+ ).format(
+ filepath=filepath,
+ peer_jid=peer_jid,
+ method_name=method_name,
+ reason=e,
+ )
)
continue
if "progress" not in file_data:
@@ -181,7 +195,7 @@ class FilePlugin:
return self.file_send(client, peer_jid, path)
def _file_send_menu(self, data, profile):
- """ XMLUI activated by menu: return file sending UI
+ """XMLUI activated by menu: return file sending UI
@param profile: %(doc_profile)s
"""
@@ -215,13 +229,14 @@ class FilePlugin:
"""
m_data = (manager, priority)
if m_data in self._file_managers:
- raise exceptions.ConflictError(
- f"Manager {manager} is already registered"
- )
- if not hasattr(manager, "can_handle_file_send") or not hasattr(manager, "file_send"):
+ raise exceptions.ConflictError(f"Manager {manager} is already registered")
+ if not hasattr(manager, "can_handle_file_send") or not hasattr(
+ manager, "file_send"
+ ):
raise ValueError(
f'{manager} must have both "can_handle_file_send" and "file_send" methods to '
- 'be registered')
+ "be registered"
+ )
self._file_managers.append(m_data)
self._file_managers.sort(key=lambda m: m[1], reverse=True)
@@ -237,8 +252,7 @@ class FilePlugin:
# the overwrite check is done here
def open_file_write(self, client, file_path, transfer_data, file_data, stream_object):
- """create SatFile or FileStremaObject for the requested file and fill suitable data
- """
+ """create SatFile or FileStremaObject for the requested file and fill suitable data"""
if stream_object:
assert "stream_object" not in transfer_data
transfer_data["stream_object"] = stream.FileStreamObject(
diff --git a/libervia/backend/plugins/plugin_misc_groupblog.py b/libervia/backend/plugins/plugin_misc_groupblog.py
index f86bced1..1873a67c 100644
--- a/libervia/backend/plugins/plugin_misc_groupblog.py
+++ b/libervia/backend/plugins/plugin_misc_groupblog.py
@@ -35,9 +35,7 @@ except ImportError:
NS_PUBSUB = "http://jabber.org/protocol/pubsub"
NS_GROUPBLOG = "http://salut-a-toi.org/protocol/groupblog"
# NS_PUBSUB_EXP = 'http://goffi.org/protocol/pubsub' #for non official features
-NS_PUBSUB_EXP = (
- NS_PUBSUB
-) # XXX: we can't use custom namespace as Wokkel's PubSubService use official NS
+NS_PUBSUB_EXP = NS_PUBSUB # XXX: we can't use custom namespace as Wokkel's PubSubService use official NS
NS_PUBSUB_GROUPBLOG = NS_PUBSUB_EXP + "#groupblog"
NS_PUBSUB_ITEM_CONFIG = NS_PUBSUB_EXP + "#item-config"
@@ -107,7 +105,7 @@ class GroupBlog(object):
access_model = config_form.get(self._p.OPT_ACCESS_MODEL, self._p.ACCESS_OPEN)
if access_model == self._p.ACCESS_PUBLISHER_ROSTER:
opt = self._p.OPT_ROSTER_GROUPS_ALLOWED
- microblog_data['groups'] = config_form.fields[opt].values
+ microblog_data["groups"] = config_form.fields[opt].values
def _data_2_entry_trigger(self, client, mb_data, entry_elt, item_elt):
"""Build fine access permission if needed
@@ -115,7 +113,7 @@ class GroupBlog(object):
This trigger check if "group*" key are present,
and create a fine item config to restrict view to these groups
"""
- groups = mb_data.get('groups', [])
+ groups = mb_data.get("groups", [])
if not groups:
return
if not client.server_groupblog_available:
@@ -137,7 +135,8 @@ class GroupBlog(object):
"""
if "group" in mb_data:
options[self._p.OPT_ACCESS_MODEL] = self._p.ACCESS_PUBLISHER_ROSTER
- options[self._p.OPT_ROSTER_GROUPS_ALLOWED] = mb_data['groups']
+ options[self._p.OPT_ROSTER_GROUPS_ALLOWED] = mb_data["groups"]
+
@implementer(iwokkel.IDisco)
class GroupBlog_handler(XMPPHandler):
diff --git a/libervia/backend/plugins/plugin_misc_identity.py b/libervia/backend/plugins/plugin_misc_identity.py
index 85d01f8a..4560a640 100644
--- a/libervia/backend/plugins/plugin_misc_identity.py
+++ b/libervia/backend/plugins/plugin_misc_identity.py
@@ -43,7 +43,6 @@ except:
)
-
log = getLogger(__name__)
@@ -86,7 +85,7 @@ class Identity:
# (it is stored only for roster entities)
"store": True,
"store_serialisation": self._avatar_ser,
- "store_deserialisation": self._avatar_deser
+ "store_deserialisation": self._avatar_deser,
},
"nicknames": {
"type": list,
@@ -103,7 +102,7 @@ class Identity:
"get_all": True,
"get_post_treatment": self.description_get_post_treatment,
"store": True,
- }
+ },
}
host.trigger.add("roster_update", self._roster_update_trigger)
host.memory.set_signal_on_update("avatar")
@@ -161,14 +160,15 @@ class Identity:
client._identity_update_lock = []
# we restore known identities from database
client._identity_storage = persistent.LazyPersistentBinaryDict(
- "identity", client.profile)
+ "identity", client.profile
+ )
stored_data = await client._identity_storage.all()
to_delete = []
for key, value in stored_data.items():
- entity_s, name = key.split('\n')
+ entity_s, name = key.split("\n")
try:
metadata = self.metadata[name]
except KeyError:
@@ -181,31 +181,31 @@ class Identity:
value = deser_method(value)
entity = jid.JID(entity_s)
- if name == 'avatar':
+ if name == "avatar":
if value is not None:
try:
- cache_uid = value['cache_uid']
+ cache_uid = value["cache_uid"]
if not cache_uid:
raise ValueError
- filename = value['filename']
+ filename = value["filename"]
if not filename:
raise ValueError
except (ValueError, KeyError):
log.warning(
f"invalid data for {entity} avatar, it will be deleted: "
- f"{value}")
+ f"{value}"
+ )
to_delete.append(key)
continue
cache = self.host.common_cache.get_metadata(cache_uid)
if cache is None:
log.debug(
- f"purging avatar for {entity}: it is not in cache anymore")
+ f"purging avatar for {entity}: it is not in cache anymore"
+ )
to_delete.append(key)
continue
- self.host.memory.update_entity_data(
- client, entity, name, value, silent=True
- )
+ self.host.memory.update_entity_data(client, entity, name, value, silent=True)
for key in to_delete:
await client._identity_storage.adel(key)
@@ -219,22 +219,19 @@ class Identity:
)
defer.ensureDeferred(
self.update(
- client,
- IMPORT_NAME,
- "nicknames",
- [roster_item.name],
- roster_item.jid
+ client, IMPORT_NAME, "nicknames", [roster_item.name], roster_item.jid
)
)
return True
def register(
- self,
- origin: str,
- metadata_name: str,
- cb_get: Union[Coroutine, defer.Deferred],
- cb_set: Union[Coroutine, defer.Deferred],
- priority: int=0):
+ self,
+ origin: str,
+ metadata_name: str,
+ cb_get: Union[Coroutine, defer.Deferred],
+ cb_set: Union[Coroutine, defer.Deferred],
+ priority: int = 0,
+ ):
"""Register callbacks to handle identity metadata
@param origin: namespace of the plugin managing this metadata
@@ -251,7 +248,7 @@ class Identity:
if not metadata_name in self.metadata.keys():
raise ValueError(f"Invalid metadata_name: {metadata_name!r}")
callback = Callback(origin=origin, get=cb_get, set=cb_set, priority=priority)
- cb_list = self.metadata[metadata_name].setdefault('callbacks', [])
+ cb_list = self.metadata[metadata_name].setdefault("callbacks", [])
cb_list.append(callback)
cb_list.sort(key=lambda c: c.priority, reverse=True)
@@ -276,7 +273,8 @@ class Identity:
if not isinstance(value, value_type):
raise ValueError(
f"{value} has wrong type: it is {type(value)} while {value_type} was "
- f"expected")
+ f"expected"
+ )
def get_field_type(self, metadata_name: str) -> str:
"""Return the type the requested field
@@ -287,13 +285,13 @@ class Identity:
return self.metadata[metadata_name]["type"]
async def get(
- self,
- client: SatXMPPEntity,
- metadata_name: str,
- entity: Optional[jid.JID],
- use_cache: bool=True,
- prefilled_values: Optional[Dict[str, Any]]=None
- ):
+ self,
+ client: SatXMPPEntity,
+ metadata_name: str,
+ entity: Optional[jid.JID],
+ use_cache: bool = True,
+ prefilled_values: Optional[Dict[str, Any]] = None,
+ ):
"""Retrieve identity metadata of an entity
if metadata is already in cache, it is returned. Otherwise, registered callbacks
@@ -311,28 +309,31 @@ class Identity:
metadata = self.metadata[metadata_name]
except KeyError:
raise ValueError(f"Invalid metadata name: {metadata_name!r}")
- get_all = metadata.get('get_all', False)
+ get_all = metadata.get("get_all", False)
if use_cache:
try:
- data = self.host.memory.get_entity_datum(
- client, entity, metadata_name)
+ data = self.host.memory.get_entity_datum(client, entity, metadata_name)
except (KeyError, exceptions.UnknownEntityError):
pass
else:
return data
try:
- callbacks = metadata['callbacks']
+ callbacks = metadata["callbacks"]
except KeyError:
- log.warning(_("No callback registered for {metadata_name}")
- .format(metadata_name=metadata_name))
+ log.warning(
+ _("No callback registered for {metadata_name}").format(
+ metadata_name=metadata_name
+ )
+ )
return [] if get_all else None
if get_all:
all_data = []
elif prefilled_values is not None:
raise exceptions.InternalError(
- "prefilled_values can only be used when `get_all` is set")
+ "prefilled_values can only be used when `get_all` is set"
+ )
for callback in callbacks:
try:
@@ -340,15 +341,18 @@ class Identity:
data = prefilled_values[callback.origin]
log.debug(
f"using prefilled values {data!r} for {metadata_name} with "
- f"{callback.origin}")
+ f"{callback.origin}"
+ )
else:
data = await defer.ensureDeferred(callback.get(client, entity))
except exceptions.CancelError:
continue
except Exception as e:
log.warning(
- _("Error while trying to get {metadata_name} with {callback}: {e}")
- .format(callback=callback.get, metadata_name=metadata_name, e=e))
+ _(
+ "Error while trying to get {metadata_name} with {callback}: {e}"
+ ).format(callback=callback.get, metadata_name=metadata_name, e=e)
+ )
else:
if data:
self.check_type(metadata_name, data)
@@ -369,10 +373,9 @@ class Identity:
if post_treatment is not None:
data = await utils.as_deferred(post_treatment, client, entity, data)
- self.host.memory.update_entity_data(
- client, entity, metadata_name, data)
+ self.host.memory.update_entity_data(client, entity, metadata_name, data)
- if metadata.get('store', False):
+ if metadata.get("store", False):
if data is not None:
ser_method = metadata.get("store_serialisation")
if ser_method is not None:
@@ -401,10 +404,13 @@ class Identity:
self.check_type(metadata_name, data)
try:
- callbacks = metadata['callbacks']
+ callbacks = metadata["callbacks"]
except KeyError:
- log.warning(_("No callback registered for {metadata_name}")
- .format(metadata_name=metadata_name))
+ log.warning(
+ _("No callback registered for {metadata_name}").format(
+ metadata_name=metadata_name
+ )
+ )
return exceptions.FeatureNotFound(f"Can't set {metadata_name} for {entity}")
for callback in callbacks:
@@ -414,8 +420,10 @@ class Identity:
continue
except Exception as e:
log.warning(
- _("Error while trying to set {metadata_name} with {callback}: {e}")
- .format(callback=callback.set, metadata_name=metadata_name, e=e))
+ _(
+ "Error while trying to set {metadata_name} with {callback}: {e}"
+ ).format(callback=callback.set, metadata_name=metadata_name, e=e)
+ )
else:
break
else:
@@ -431,7 +439,7 @@ class Identity:
origin: str,
metadata_name: str,
data: Any,
- entity: Optional[jid.JID]
+ entity: Optional[jid.JID],
):
"""Update a metadata in cache
@@ -445,8 +453,7 @@ class Identity:
metadata = self.metadata[metadata_name]
try:
- cached_data = self.host.memory.get_entity_datum(
- client, entity, metadata_name)
+ cached_data = self.host.memory.get_entity_datum(client, entity, metadata_name)
except (KeyError, exceptions.UnknownEntityError):
# metadata is not cached, we do the update
pass
@@ -461,21 +468,22 @@ class Identity:
if cached_data is None:
log.debug(
f"{metadata_name} for {entity} is already disabled, nothing to "
- f"do")
+ f"do"
+ )
return
elif cached_data is None:
pass
elif not update_is_new_data(client, entity, cached_data, data):
log.debug(
- f"{metadata_name} for {entity} is already in cache, nothing to "
- f"do")
+ f"{metadata_name} for {entity} is already in cache, nothing to " f"do"
+ )
return
# we can't use the cache, so we do the update
log.debug(f"updating {metadata_name} for {entity}")
- if metadata.get('get_all', False):
+ if metadata.get("get_all", False):
# get_all is set, meaning that we have to check all plugins
# so we first delete current cache
try:
@@ -491,14 +499,14 @@ class Identity:
return
if data is not None:
- data_filter = metadata['update_data_filter']
+ data_filter = metadata["update_data_filter"]
if data_filter is not None:
data = await utils.as_deferred(data_filter, client, entity, data)
self.check_type(metadata_name, data)
self.host.memory.update_entity_data(client, entity, metadata_name, data)
- if metadata.get('store', False):
+ if metadata.get("store", False):
key = f"{entity}\n{metadata_name}"
if data is not None:
ser_method = metadata.get("store_serialisation")
@@ -519,14 +527,9 @@ class Identity:
def _set_avatar(self, file_path, entity, profile_key=C.PROF_KEY_NONE):
client = self.host.get_client(profile_key)
entity = jid.JID(entity) if entity else None
- return defer.ensureDeferred(
- self.set(client, "avatar", file_path, entity))
+ return defer.ensureDeferred(self.set(client, "avatar", file_path, entity))
- def _blocking_cache_avatar(
- self,
- source: str,
- avatar_data: dict[str, Any]
- ):
+ def _blocking_cache_avatar(self, source: str, avatar_data: dict[str, Any]):
"""This method is executed in a separated thread"""
if avatar_data["media_type"] == "image/svg+xml":
# for vector image, we save directly
@@ -562,13 +565,11 @@ class Identity:
img_buf.seek(0)
image_hash = hashlib.sha1(img_buf.read()).hexdigest()
img_buf.seek(0)
- with self.host.common_cache.cache_data(
- source, image_hash, media_type
- ) as f:
+ with self.host.common_cache.cache_data(source, image_hash, media_type) as f:
f.write(img_buf.read())
- avatar_data['path'] = Path(f.name)
- avatar_data['filename'] = avatar_data['path'].name
- avatar_data['cache_uid'] = image_hash
+ avatar_data["path"] = Path(f.name)
+ avatar_data["filename"] = avatar_data["path"].name
+ avatar_data["cache_uid"] = image_hash
async def cache_avatar(self, source: str, avatar_data: Dict[str, Any]) -> None:
"""Resize if necessary and cache avatar
@@ -590,14 +591,14 @@ class Identity:
if not file_path.is_file():
raise ValueError(f"There is no file at {file_path} to use as avatar")
avatar_data = {
- 'path': file_path,
- 'filename': file_path.name,
- 'media_type': image.guess_type(file_path),
+ "path": file_path,
+ "filename": file_path.name,
+ "media_type": image.guess_type(file_path),
}
- media_type = avatar_data['media_type']
+ media_type = avatar_data["media_type"]
if media_type is None:
raise ValueError(f"Can't identify type of image at {file_path}")
- if not media_type.startswith('image/'):
+ if not media_type.startswith("image/"):
raise ValueError(f"File at {file_path} doesn't appear to be an image")
await self.cache_avatar(IMPORT_NAME, avatar_data)
return avatar_data
@@ -607,10 +608,10 @@ class Identity:
await self.update(client, IMPORT_NAME, "avatar", avatar_data, entity)
def avatar_build_metadata(
- self,
- path: Path,
- media_type: Optional[str] = None,
- cache_uid: Optional[str] = None
+ self,
+ path: Path,
+ media_type: Optional[str] = None,
+ cache_uid: Optional[str] = None,
) -> Optional[Dict[str, Union[str, Path, None]]]:
"""Helper method to generate avatar metadata
@@ -639,12 +640,12 @@ class Identity:
}
def avatar_update_is_new_data(self, client, entity, cached_data, new_data):
- return new_data['path'] != cached_data['path']
+ return new_data["path"] != cached_data["path"]
async def avatar_update_data_filter(self, client, entity, data):
if not isinstance(data, dict):
raise ValueError(f"Invalid data type ({type(data)}), a dict is expected")
- mandatory_keys = {'path', 'filename', 'cache_uid'}
+ mandatory_keys = {"path", "filename", "cache_uid"}
if not data.keys() >= mandatory_keys:
raise ValueError(f"missing avatar data keys: {mandatory_keys - data.keys()}")
return data
@@ -702,19 +703,17 @@ class Identity:
return not set(new_nicknames).issubset(cached_data)
async def description_get_post_treatment(
- self,
- client: SatXMPPEntity,
- entity: jid.JID,
- plugin_description: List[str]
+ self, client: SatXMPPEntity, entity: jid.JID, plugin_description: List[str]
) -> str:
"""Join all descriptions in a unique string"""
- return '\n'.join(plugin_description)
+ return "\n".join(plugin_description)
def _get_identity(self, entity_s, metadata_filter, use_cache, profile):
entity = jid.JID(entity_s)
client = self.host.get_client(profile)
d = defer.ensureDeferred(
- self.get_identity(client, entity, metadata_filter, use_cache))
+ self.get_identity(client, entity, metadata_filter, use_cache)
+ )
d.addCallback(data_format.serialise)
return d
@@ -723,7 +722,7 @@ class Identity:
client: SatXMPPEntity,
entity: Optional[jid.JID] = None,
metadata_filter: Optional[List[str]] = None,
- use_cache: bool = True
+ use_cache: bool = True,
) -> Dict[str, Any]:
"""Retrieve identity of an entity
@@ -743,7 +742,8 @@ class Identity:
for metadata_name in metadata_names:
id_data[metadata_name] = await self.get(
- client, metadata_name, entity, use_cache)
+ client, metadata_name, entity, use_cache
+ )
return id_data
@@ -751,7 +751,7 @@ class Identity:
entities = [jid.JID(e) for e in entities_s]
client = self.host.get_client(profile)
d = defer.ensureDeferred(self.get_identities(client, entities, metadata_filter))
- d.addCallback(lambda d: data_format.serialise({str(j):i for j, i in d.items()}))
+ d.addCallback(lambda d: data_format.serialise({str(j): i for j, i in d.items()}))
return d
async def get_identities(
@@ -792,7 +792,7 @@ class Identity:
def _get_base_identities(self, profile_key):
client = self.host.get_client(profile_key)
d = defer.ensureDeferred(self.get_base_identities(client))
- d.addCallback(lambda d: data_format.serialise({str(j):i for j, i in d.items()}))
+ d.addCallback(lambda d: data_format.serialise({str(j): i for j, i in d.items()}))
return d
async def get_base_identities(
@@ -809,11 +809,7 @@ class Identity:
else:
entities = client.roster.get_jids() + [client.jid.userhostJID()]
- return await self.get_identities(
- client,
- entities,
- ['avatar', 'nicknames']
- )
+ return await self.get_identities(client, entities, ["avatar", "nicknames"])
def _set_identity(self, id_data_s, profile):
client = self.host.get_client(profile)
@@ -827,11 +823,14 @@ class Identity:
"""
if not id_data.keys() <= self.metadata.keys():
raise ValueError(
- f"Invalid metadata names: {id_data.keys() - self.metadata.keys()}")
+ f"Invalid metadata names: {id_data.keys() - self.metadata.keys()}"
+ )
for metadata_name, data in id_data.items():
try:
await self.set(client, metadata_name, data)
except Exception as e:
log.warning(
- _("Can't set metadata {metadata_name!r}: {reason}")
- .format(metadata_name=metadata_name, reason=e))
+ _("Can't set metadata {metadata_name!r}: {reason}").format(
+ metadata_name=metadata_name, reason=e
+ )
+ )
diff --git a/libervia/backend/plugins/plugin_misc_ip.py b/libervia/backend/plugins/plugin_misc_ip.py
index 624f2f92..e9ddd5af 100644
--- a/libervia/backend/plugins/plugin_misc_ip.py
+++ b/libervia/backend/plugins/plugin_misc_ip.py
@@ -58,9 +58,7 @@ PLUGIN_INFO = {
}
# TODO: GET_IP_PAGE should be configurable in sat.conf
-GET_IP_PAGE = (
- "http://salut-a-toi.org/whereami/"
-) # This page must only return external IP of the requester
+GET_IP_PAGE = "http://salut-a-toi.org/whereami/" # This page must only return external IP of the requester
GET_IP_LABEL = D_("Allow external get IP")
GET_IP_CATEGORY = "General"
GET_IP_NAME = "allow_get_ip"
@@ -75,7 +73,9 @@ IP address is an identifier to locate you on Internet (similar to a phone number
Do you agree to do this request ?
"""
).format(
- page=GET_IP_PAGE, domain=urllib.parse.urlparse(GET_IP_PAGE).netloc, app_name=C.APP_NAME
+ page=GET_IP_PAGE,
+ domain=urllib.parse.urlparse(GET_IP_PAGE).netloc,
+ app_name=C.APP_NAME,
)
NS_IP_CHECK = "urn:xmpp:sic:1"
diff --git a/libervia/backend/plugins/plugin_misc_jid_search.py b/libervia/backend/plugins/plugin_misc_jid_search.py
index 7c7fa3c7..921dc9e3 100644
--- a/libervia/backend/plugins/plugin_misc_jid_search.py
+++ b/libervia/backend/plugins/plugin_misc_jid_search.py
@@ -152,7 +152,6 @@ class JidSearch:
)
)
-
matches.sort(
key=lambda item: (item.exact_match, item.relevance or 0, item.in_roster),
reverse=True,
diff --git a/libervia/backend/plugins/plugin_misc_lists.py b/libervia/backend/plugins/plugin_misc_lists.py
index 1ee86ab8..f3b2e45b 100644
--- a/libervia/backend/plugins/plugin_misc_lists.py
+++ b/libervia/backend/plugins/plugin_misc_lists.py
@@ -40,8 +40,13 @@ PLUGIN_INFO = {
C.PI_IMPORT_NAME: "LISTS",
C.PI_TYPE: "EXP",
C.PI_PROTOCOLS: [],
- C.PI_DEPENDENCIES: ["XEP-0060", "XEP-0346", "XEP-0277", "IDENTITY",
- "PUBSUB_INVITATION"],
+ C.PI_DEPENDENCIES: [
+ "XEP-0060",
+ "XEP-0346",
+ "XEP-0277",
+ "IDENTITY",
+ "PUBSUB_INVITATION",
+ ],
C.PI_MAIN: "PubsubLists",
C.PI_HANDLER: "no",
C.PI_DESCRIPTION: _("""Pubsub lists management plugin"""),
@@ -63,44 +68,26 @@ TEMPLATES = {
"label": D_("status"),
"type": "list-single",
"options": [
- {
- "label": D_("to do"),
- "value": "todo"
- },
- {
- "label": D_("in progress"),
- "value": "in_progress"
- },
- {
- "label": D_("done"),
- "value": "done"
- },
+ {"label": D_("to do"), "value": "todo"},
+ {"label": D_("in progress"), "value": "in_progress"},
+ {"label": D_("done"), "value": "done"},
],
- "value": "todo"
+ "value": "todo",
},
{
"name": "priority",
"label": D_("priority"),
"type": "list-single",
"options": [
- {
- "label": D_("major"),
- "value": "major"
- },
- {
- "label": D_("normal"),
- "value": "normal"
- },
- {
- "label": D_("minor"),
- "value": "minor"
- },
+ {"label": D_("major"), "value": "major"},
+ {"label": D_("normal"), "value": "normal"},
+ {"label": D_("minor"), "value": "minor"},
],
- "value": "normal"
+ "value": "normal",
},
{"name": "body", "type": "xhtml"},
{"name": "comments_uri"},
- ]
+ ],
},
"grocery": {
"name": D_("Grocery List"),
@@ -113,18 +100,12 @@ TEMPLATES = {
"label": D_("status"),
"type": "list-single",
"options": [
- {
- "label": D_("to buy"),
- "value": "to_buy"
- },
- {
- "label": D_("bought"),
- "value": "bought"
- },
+ {"label": D_("to buy"), "value": "to_buy"},
+ {"label": D_("bought"), "value": "bought"},
],
- "value": "to_buy"
+ "value": "to_buy",
},
- ]
+ ],
},
"tickets": {
"name": D_("Tickets"),
@@ -140,65 +121,38 @@ TEMPLATES = {
"label": D_("type"),
"type": "list-single",
"options": [
- {
- "label": D_("bug"),
- "value": "bug"
- },
- {
- "label": D_("feature request"),
- "value": "feature"
- },
+ {"label": D_("bug"), "value": "bug"},
+ {"label": D_("feature request"), "value": "feature"},
],
- "value": "bug"
+ "value": "bug",
},
{
"name": "status",
"label": D_("status"),
"type": "list-single",
"options": [
- {
- "label": D_("queued"),
- "value": "queued"
- },
- {
- "label": D_("started"),
- "value": "started"
- },
- {
- "label": D_("review"),
- "value": "review"
- },
- {
- "label": D_("closed"),
- "value": "closed"
- },
+ {"label": D_("queued"), "value": "queued"},
+ {"label": D_("started"), "value": "started"},
+ {"label": D_("review"), "value": "review"},
+ {"label": D_("closed"), "value": "closed"},
],
- "value": "queued"
+ "value": "queued",
},
{
"name": "priority",
"label": D_("priority"),
"type": "list-single",
"options": [
- {
- "label": D_("major"),
- "value": "major"
- },
- {
- "label": D_("normal"),
- "value": "normal"
- },
- {
- "label": D_("minor"),
- "value": "minor"
- },
+ {"label": D_("major"), "value": "major"},
+ {"label": D_("normal"), "value": "normal"},
+ {"label": D_("minor"), "value": "minor"},
],
- "value": "normal"
+ "value": "normal",
},
{"name": "body", "type": "xhtml"},
{"name": "comments_uri"},
- ]
- }
+ ],
+ },
}
@@ -211,9 +165,7 @@ class PubsubLists:
self.namespace = self._s.get_submitted_ns(APP_NS_TICKETS)
host.register_namespace("tickets", APP_NS_TICKETS)
host.register_namespace("tickets_type", NS_TICKETS_TYPE)
- self.host.plugins["PUBSUB_INVITATION"].register(
- APP_NS_TICKETS, self
- )
+ self.host.plugins["PUBSUB_INVITATION"].register(APP_NS_TICKETS, self)
self._p = self.host.plugins["XEP-0060"]
self._m = self.host.plugins["XEP-0277"]
host.bridge.add_method(
@@ -221,8 +173,7 @@ class PubsubLists:
".plugin",
in_sign="ssiassss",
out_sign="s",
- method=lambda service, node, max_items, items_ids, sub_id, extra, profile_key:
- self._s._get(
+ method=lambda service, node, max_items, items_ids, sub_id, extra, profile_key: self._s._get(
service,
node,
max_items,
@@ -237,7 +188,8 @@ class PubsubLists:
"updated": self._s.date_filter,
"time_limit": self._s.date_filter,
},
- profile_key=profile_key),
+ profile_key=profile_key,
+ ),
async_=True,
)
host.bridge.add_method(
@@ -262,8 +214,11 @@ class PubsubLists:
in_sign="sss",
out_sign="s",
method=lambda service, nodeIdentifier, profile_key: self._s._get_ui_schema(
- service, nodeIdentifier, default_node=self.namespace,
- profile_key=profile_key),
+ service,
+ nodeIdentifier,
+ default_node=self.namespace,
+ profile_key=profile_key,
+ ),
async_=True,
)
host.bridge.add_method(
@@ -306,7 +261,7 @@ class PubsubLists:
service: jid.JID,
node: str,
item_id: Optional[str],
- item_elt: domish.Element
+ item_elt: domish.Element,
) -> None:
try:
schema = await self._s.get_schema_form(client, service, node)
@@ -321,20 +276,38 @@ class PubsubLists:
list_elt = extra["element"] = domish.Element((APP_NS_TICKETS, "list"))
list_elt["type"] = field_type
- def _set(self, service, node, values, schema=None, item_id=None, extra_s='',
- profile_key=C.PROF_KEY_NONE):
+ def _set(
+ self,
+ service,
+ node,
+ values,
+ schema=None,
+ item_id=None,
+ extra_s="",
+ profile_key=C.PROF_KEY_NONE,
+ ):
client, service, node, schema, item_id, extra = self._s.prepare_bridge_set(
service, node, schema, item_id, extra_s, profile_key
)
- d = defer.ensureDeferred(self.set(
- client, service, node, values, schema, item_id, extra, deserialise=True
- ))
+ d = defer.ensureDeferred(
+ self.set(
+ client, service, node, values, schema, item_id, extra, deserialise=True
+ )
+ )
d.addCallback(lambda ret: ret or "")
return d
async def set(
- self, client, service, node, values, schema=None, item_id=None, extra=None,
- deserialise=False, form_ns=APP_NS_TICKETS
+ self,
+ client,
+ service,
+ node,
+ values,
+ schema=None,
+ item_id=None,
+ extra=None,
+ deserialise=False,
+ form_ns=APP_NS_TICKETS,
):
"""Publish a tickets
@@ -361,9 +334,7 @@ class PubsubLists:
# we need to use uuid for comments node, because we don't know item id in
# advance (we don't want to set it ourselves to let the server choose, so we
# can have a nicer id if serial ids is activated)
- comments_node = self._m.get_comments_node(
- node + "_" + str(shortuuid.uuid())
- )
+ comments_node = self._m.get_comments_node(node + "_" + str(shortuuid.uuid()))
options = {
self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN,
self._p.OPT_PERSIST_ITEMS: 1,
@@ -383,17 +354,17 @@ class PubsubLists:
client, service, node, values, schema, item_id, extra, deserialise, form_ns
)
- def _delete(
- self, service_s, nodeIdentifier, itemIdentifier, notify, profile_key
- ):
+ def _delete(self, service_s, nodeIdentifier, itemIdentifier, notify, profile_key):
client = self.host.get_client(profile_key)
- return defer.ensureDeferred(self.delete(
- client,
- jid.JID(service_s) if service_s else None,
- nodeIdentifier,
- itemIdentifier,
- notify
- ))
+ return defer.ensureDeferred(
+ self.delete(
+ client,
+ jid.JID(service_s) if service_s else None,
+ nodeIdentifier,
+ itemIdentifier,
+ notify,
+ )
+ )
async def delete(
self,
@@ -401,7 +372,7 @@ class PubsubLists:
service: Optional[jid.JID],
node: Optional[str],
itemIdentifier: str,
- notify: Optional[bool] = None
+ notify: Optional[bool] = None,
) -> None:
if not node:
node = self.namespace
@@ -418,14 +389,15 @@ class PubsubLists:
return d
async def lists_list(
- self, client, service: Optional[jid.JID], node: Optional[str]=None
+ self, client, service: Optional[jid.JID], node: Optional[str] = None
) -> List[dict]:
"""Retrieve list of pubsub lists registered in personal interests
@return list: list of lists metadata
"""
- items, metadata = await self.host.plugins['LIST_INTEREST'].list_interests(
- client, service, node, namespace=APP_NS_TICKETS)
+ items, metadata = await self.host.plugins["LIST_INTEREST"].list_interests(
+ client, service, node, namespace=APP_NS_TICKETS
+ )
lists = []
for item in items:
interest_elt = item.interest
@@ -461,8 +433,10 @@ class PubsubLists:
def get_templates_names(self, client, language: str) -> list:
"""Retrieve well known list templates"""
- templates = [{"id": tpl_id, "name": d["name"], "icon": d["icon"]}
- for tpl_id, d in TEMPLATES.items()]
+ templates = [
+ {"id": tpl_id, "name": d["name"], "icon": d["icon"]}
+ for tpl_id, d in TEMPLATES.items()
+ ]
return templates
def _get_template(self, name, language, profile):
@@ -475,9 +449,9 @@ class PubsubLists:
def _create_template(self, template_id, name, access_model, profile):
client = self.host.get_client(profile)
- d = defer.ensureDeferred(self.create_template(
- client, template_id, name, access_model
- ))
+ d = defer.ensureDeferred(
+ self.create_template(client, template_id, name, access_model)
+ )
d.addCallback(lambda node_data: (node_data[0].full(), node_data[1]))
return d
@@ -490,8 +464,7 @@ class PubsubLists:
name = shortuuid.uuid()
fields = TEMPLATES[template_id]["fields"].copy()
fields.insert(
- 0,
- {"type": "hidden", "name": NS_TICKETS_TYPE, "value": template_id}
+ 0, {"type": "hidden", "name": NS_TICKETS_TYPE, "value": template_id}
)
schema = xml_tools.data_dict_2_data_form(
{"namespace": APP_NS_TICKETS, "fields": fields}
@@ -511,9 +484,15 @@ class PubsubLists:
list_elt = domish.Element((APP_NS_TICKETS, "list"))
list_elt["type"] = template_id
try:
- await self.host.plugins['LIST_INTEREST'].register_pubsub(
- client, APP_NS_TICKETS, service, node, creator=True,
- name=name, element=list_elt)
+ await self.host.plugins["LIST_INTEREST"].register_pubsub(
+ client,
+ APP_NS_TICKETS,
+ service,
+ node,
+ creator=True,
+ name=name,
+ element=list_elt,
+ )
except Exception as e:
log.warning(f"Can't add list to interests: {e}")
return service, node
diff --git a/libervia/backend/plugins/plugin_misc_merge_requests.py b/libervia/backend/plugins/plugin_misc_merge_requests.py
index 7e13537c..27c4a200 100644
--- a/libervia/backend/plugins/plugin_misc_merge_requests.py
+++ b/libervia/backend/plugins/plugin_misc_merge_requests.py
@@ -29,7 +29,7 @@ from libervia.backend.core.log import getLogger
log = getLogger(__name__)
-APP_NS_MERGE_REQUESTS = 'org.salut-a-toi.merge_requests:0'
+APP_NS_MERGE_REQUESTS = "org.salut-a-toi.merge_requests:0"
PLUGIN_INFO = {
C.PI_NAME: _("Merge requests management"),
@@ -39,69 +39,86 @@ PLUGIN_INFO = {
C.PI_DEPENDENCIES: ["XEP-0060", "XEP-0346", "LISTS", "TEXT_SYNTAXES"],
C.PI_MAIN: "MergeRequests",
C.PI_HANDLER: "no",
- C.PI_DESCRIPTION: _("""Merge requests management plugin""")
+ C.PI_DESCRIPTION: _("""Merge requests management plugin"""),
}
-FIELD_DATA_TYPE = 'type'
-FIELD_DATA = 'request_data'
+FIELD_DATA_TYPE = "type"
+FIELD_DATA = "request_data"
-MergeRequestHandler = namedtuple("MergeRequestHandler", ['name',
- 'handler',
- 'data_types',
- 'short_desc',
- 'priority'])
+MergeRequestHandler = namedtuple(
+ "MergeRequestHandler", ["name", "handler", "data_types", "short_desc", "priority"]
+)
class MergeRequests(object):
- META_AUTHOR = 'author'
- META_EMAIL = 'email'
- META_TIMESTAMP = 'timestamp'
- META_HASH = 'hash'
- META_PARENT_HASH = 'parent_hash'
- META_COMMIT_MSG = 'commit_msg'
- META_DIFF = 'diff'
+ META_AUTHOR = "author"
+ META_EMAIL = "email"
+ META_TIMESTAMP = "timestamp"
+ META_HASH = "hash"
+ META_PARENT_HASH = "parent_hash"
+ META_COMMIT_MSG = "commit_msg"
+ META_DIFF = "diff"
# index of the diff in the whole data
# needed to retrieve comments location
- META_DIFF_IDX = 'diff_idx'
+ META_DIFF_IDX = "diff_idx"
def __init__(self, host):
log.info(_("Merge requests plugin initialization"))
self.host = host
self._s = self.host.plugins["XEP-0346"]
self.namespace = self._s.get_submitted_ns(APP_NS_MERGE_REQUESTS)
- host.register_namespace('merge_requests', self.namespace)
+ host.register_namespace("merge_requests", self.namespace)
self._p = self.host.plugins["XEP-0060"]
self._t = self.host.plugins["LISTS"]
self._handlers = {}
- self._handlers_list = [] # handlers sorted by priority
+ self._handlers_list = [] # handlers sorted by priority
self._type_handlers = {} # data type => handler map
- host.bridge.add_method("merge_requests_get", ".plugin",
- in_sign='ssiassss', out_sign='s',
- method=self._get,
- async_=True
- )
- host.bridge.add_method("merge_request_set", ".plugin",
- in_sign='ssssa{sas}ssss', out_sign='s',
- method=self._set,
- async_=True)
- host.bridge.add_method("merge_requests_schema_get", ".plugin",
- in_sign='sss', out_sign='s',
- method=lambda service, nodeIdentifier, profile_key:
- self._s._get_ui_schema(service,
- nodeIdentifier,
- default_node=self.namespace,
- profile_key=profile_key),
- async_=True)
- host.bridge.add_method("merge_request_parse_data", ".plugin",
- in_sign='ss', out_sign='aa{ss}',
- method=self._parse_data,
- async_=True)
- host.bridge.add_method("merge_requests_import", ".plugin",
- in_sign='ssssa{ss}s', out_sign='',
- method=self._import,
- async_=True
- )
+ host.bridge.add_method(
+ "merge_requests_get",
+ ".plugin",
+ in_sign="ssiassss",
+ out_sign="s",
+ method=self._get,
+ async_=True,
+ )
+ host.bridge.add_method(
+ "merge_request_set",
+ ".plugin",
+ in_sign="ssssa{sas}ssss",
+ out_sign="s",
+ method=self._set,
+ async_=True,
+ )
+ host.bridge.add_method(
+ "merge_requests_schema_get",
+ ".plugin",
+ in_sign="sss",
+ out_sign="s",
+ method=lambda service, nodeIdentifier, profile_key: self._s._get_ui_schema(
+ service,
+ nodeIdentifier,
+ default_node=self.namespace,
+ profile_key=profile_key,
+ ),
+ async_=True,
+ )
+ host.bridge.add_method(
+ "merge_request_parse_data",
+ ".plugin",
+ in_sign="ss",
+ out_sign="aa{ss}",
+ method=self._parse_data,
+ async_=True,
+ )
+ host.bridge.add_method(
+ "merge_requests_import",
+ ".plugin",
+ in_sign="ssssa{ss}s",
+ out_sign="",
+ method=self._import,
+ async_=True,
+ )
def register(self, name, handler, data_types, short_desc, priority=0):
"""register an merge request handler
@@ -118,49 +135,81 @@ class MergeRequests(object):
@aram data_types(list[unicode]): data types that his handler can generate or parse
"""
if name in self._handlers:
- raise exceptions.ConflictError(_("a handler with name {name} already "
- "exists!").format(name = name))
- self._handlers[name] = MergeRequestHandler(name,
- handler,
- data_types,
- short_desc,
- priority)
+ raise exceptions.ConflictError(
+ _("a handler with name {name} already " "exists!").format(name=name)
+ )
+ self._handlers[name] = MergeRequestHandler(
+ name, handler, data_types, short_desc, priority
+ )
self._handlers_list.append(name)
self._handlers_list.sort(key=lambda name: self._handlers[name].priority)
if isinstance(data_types, str):
data_types = [data_types]
for data_type in data_types:
if data_type in self._type_handlers:
- log.warning(_('merge requests of type {type} are already handled by '
- '{old_handler}, ignoring {new_handler}').format(
- type = data_type,
- old_handler = self._type_handlers[data_type].name,
- new_handler = name))
+ log.warning(
+ _(
+ "merge requests of type {type} are already handled by "
+ "{old_handler}, ignoring {new_handler}"
+ ).format(
+ type=data_type,
+ old_handler=self._type_handlers[data_type].name,
+ new_handler=name,
+ )
+ )
continue
self._type_handlers[data_type] = self._handlers[name]
def serialise(self, get_data):
tickets_xmlui, metadata, items_patches = get_data
tickets_xmlui_s, metadata = self._p.trans_items_data((tickets_xmlui, metadata))
- return data_format.serialise({
- "items": tickets_xmlui_s,
- "metadata": metadata,
- "items_patches": items_patches,
- })
+ return data_format.serialise(
+ {
+ "items": tickets_xmlui_s,
+ "metadata": metadata,
+ "items_patches": items_patches,
+ }
+ )
- def _get(self, service='', node='', max_items=10, item_ids=None, sub_id=None,
- extra="", profile_key=C.PROF_KEY_NONE):
+ def _get(
+ self,
+ service="",
+ node="",
+ max_items=10,
+ item_ids=None,
+ sub_id=None,
+ extra="",
+ profile_key=C.PROF_KEY_NONE,
+ ):
extra = data_format.deserialise(extra)
client, service, node, max_items, extra, sub_id = self._s.prepare_bridge_get(
- service, node, max_items, sub_id, extra, profile_key)
- d = self.get(client, service, node or None, max_items, item_ids, sub_id or None,
- extra.rsm_request, extra.extra)
+ service, node, max_items, sub_id, extra, profile_key
+ )
+ d = self.get(
+ client,
+ service,
+ node or None,
+ max_items,
+ item_ids,
+ sub_id or None,
+ extra.rsm_request,
+ extra.extra,
+ )
d.addCallback(self.serialise)
return d
@defer.inlineCallbacks
- def get(self, client, service=None, node=None, max_items=None, item_ids=None,
- sub_id=None, rsm_request=None, extra=None):
+ def get(
+ self,
+ client,
+ service=None,
+ node=None,
+ max_items=None,
+ item_ids=None,
+ sub_id=None,
+ rsm_request=None,
+ extra=None,
+ ):
"""Retrieve merge requests and convert them to XMLUI
@param extra(XEP-0060.parse, None): can have following keys:
@@ -177,8 +226,8 @@ class MergeRequests(object):
extra = {}
# XXX: Q&D way to get list for labels when displaying them, but text when we
# have to modify them
- if C.bool(extra.get('labels_as_list', C.BOOL_FALSE)):
- filters = {'labels': self._s.textbox_2_list_filter}
+ if C.bool(extra.get("labels_as_list", C.BOOL_FALSE)):
+ filters = {"labels": self._s.textbox_2_list_filter}
else:
filters = {}
tickets_xmlui, metadata = yield defer.ensureDeferred(
@@ -192,10 +241,11 @@ class MergeRequests(object):
rsm_request=rsm_request,
extra=extra,
form_ns=APP_NS_MERGE_REQUESTS,
- filters = filters)
+ filters=filters,
+ )
)
parsed_patches = []
- if extra.get('parse', False):
+ if extra.get("parse", False):
for ticket in tickets_xmlui:
request_type = ticket.named_widgets[FIELD_DATA_TYPE].value
request_data = ticket.named_widgets[FIELD_DATA].value
@@ -203,21 +253,51 @@ class MergeRequests(object):
parsed_patches.append(parsed_data)
defer.returnValue((tickets_xmlui, metadata, parsed_patches))
- def _set(self, service, node, repository, method, values, schema=None, item_id=None,
- extra="", profile_key=C.PROF_KEY_NONE):
+ def _set(
+ self,
+ service,
+ node,
+ repository,
+ method,
+ values,
+ schema=None,
+ item_id=None,
+ extra="",
+ profile_key=C.PROF_KEY_NONE,
+ ):
client, service, node, schema, item_id, extra = self._s.prepare_bridge_set(
- service, node, schema, item_id, extra, profile_key)
+ service, node, schema, item_id, extra, profile_key
+ )
d = defer.ensureDeferred(
self.set(
- client, service, node, repository, method, values, schema,
- item_id or None, extra, deserialise=True
+ client,
+ service,
+ node,
+ repository,
+ method,
+ values,
+ schema,
+ item_id or None,
+ extra,
+ deserialise=True,
)
)
- d.addCallback(lambda ret: ret or '')
+ d.addCallback(lambda ret: ret or "")
return d
- async def set(self, client, service, node, repository, method='auto', values=None,
- schema=None, item_id=None, extra=None, deserialise=False):
+ async def set(
+ self,
+ client,
+ service,
+ node,
+ repository,
+ method="auto",
+ values=None,
+ schema=None,
+ item_id=None,
+ extra=None,
+ deserialise=False,
+ ):
"""Publish a tickets
@param service(None, jid.JID): Pubsub service to use
@@ -233,18 +313,21 @@ class MergeRequests(object):
node = self.namespace
if values is None:
values = {}
- update = extra.get('update', False)
+ update = extra.get("update", False)
if not repository and not update:
# in case of update, we may re-user former patches data
# so repository is not mandatory
raise exceptions.DataError(_("repository must be specified"))
if FIELD_DATA in values:
- raise exceptions.DataError(_("{field} is set by backend, you must not set "
- "it in frontend").format(field = FIELD_DATA))
+ raise exceptions.DataError(
+ _("{field} is set by backend, you must not set " "it in frontend").format(
+ field=FIELD_DATA
+ )
+ )
if repository:
- if method == 'auto':
+ if method == "auto":
for name in self._handlers_list:
handler = self._handlers[name].handler
can_handle = await handler.check(repository)
@@ -252,11 +335,15 @@ class MergeRequests(object):
log.info(_("{name} handler will be used").format(name=name))
break
else:
- log.warning(_("repository {path} can't be handled by any installed "
- "handler").format(
- path = repository))
- raise exceptions.NotFound(_("no handler for this repository has "
- "been found"))
+ log.warning(
+ _(
+ "repository {path} can't be handled by any installed "
+ "handler"
+ ).format(path=repository)
+ )
+ raise exceptions.NotFound(
+ _("no handler for this repository has " "been found")
+ )
else:
try:
handler = self._handlers[name].handler
@@ -265,35 +352,50 @@ class MergeRequests(object):
data = await handler.export(repository)
if not data.strip():
- raise exceptions.DataError(_('export data is empty, do you have any '
- 'change to send?'))
+ raise exceptions.DataError(
+ _("export data is empty, do you have any " "change to send?")
+ )
- if not values.get('title') or not values.get('body'):
+ if not values.get("title") or not values.get("body"):
patches = handler.parse(data, values.get(FIELD_DATA_TYPE))
commits_msg = patches[-1][self.META_COMMIT_MSG]
msg_lines = commits_msg.splitlines()
- if not values.get('title'):
- values['title'] = msg_lines[0]
- if not values.get('body'):
- ts = self.host.plugins['TEXT_SYNTAXES']
+ if not values.get("title"):
+ values["title"] = msg_lines[0]
+ if not values.get("body"):
+ ts = self.host.plugins["TEXT_SYNTAXES"]
xhtml = await ts.convert(
- '\n'.join(msg_lines[1:]),
- syntax_from = ts.SYNTAX_TEXT,
- syntax_to = ts.SYNTAX_XHTML,
- profile = client.profile)
- values['body'] = '
{xhtml}
'.format(
- ns=C.NS_XHTML, xhtml=xhtml)
+ "\n".join(msg_lines[1:]),
+ syntax_from=ts.SYNTAX_TEXT,
+ syntax_to=ts.SYNTAX_XHTML,
+ profile=client.profile,
+ )
+ values["body"] = '{xhtml}
'.format(
+ ns=C.NS_XHTML, xhtml=xhtml
+ )
values[FIELD_DATA] = data
- item_id = await self._t.set(client, service, node, values, schema, item_id, extra,
- deserialise, form_ns=APP_NS_MERGE_REQUESTS)
+ item_id = await self._t.set(
+ client,
+ service,
+ node,
+ values,
+ schema,
+ item_id,
+ extra,
+ deserialise,
+ form_ns=APP_NS_MERGE_REQUESTS,
+ )
return item_id
def _parse_data(self, data_type, data):
d = self.parse_data(data_type, data)
- d.addCallback(lambda parsed_patches:
- {key: str(value) for key, value in parsed_patches.items()})
+ d.addCallback(
+ lambda parsed_patches: {
+ key: str(value) for key, value in parsed_patches.items()
+ }
+ )
return d
def parse_data(self, data_type, data):
@@ -308,21 +410,31 @@ class MergeRequests(object):
try:
handler = self._type_handlers[data_type]
except KeyError:
- raise exceptions.NotFound(_('No handler can handle data type "{type}"')
- .format(type=data_type))
+ raise exceptions.NotFound(
+ _('No handler can handle data type "{type}"').format(type=data_type)
+ )
return defer.maybeDeferred(handler.handler.parse, data, data_type)
- def _import(self, repository, item_id, service=None, node=None, extra=None,
- profile_key=C.PROF_KEY_NONE):
+ def _import(
+ self,
+ repository,
+ item_id,
+ service=None,
+ node=None,
+ extra=None,
+ profile_key=C.PROF_KEY_NONE,
+ ):
client = self.host.get_client(profile_key)
service = jid.JID(service) if service else None
- d = self.import_request(client, repository, item_id, service, node or None,
- extra=extra or None)
+ d = self.import_request(
+ client, repository, item_id, service, node or None, extra=extra or None
+ )
return d
@defer.inlineCallbacks
- def import_request(self, client, repository, item, service=None, node=None,
- extra=None):
+ def import_request(
+ self, client, repository, item, service=None, node=None, extra=None
+ ):
"""import a merge request in specified directory
@param repository(unicode): path to the repository where the code stands
@@ -336,7 +448,8 @@ class MergeRequests(object):
node,
max_items=1,
item_ids=[item],
- form_ns=APP_NS_MERGE_REQUESTS)
+ form_ns=APP_NS_MERGE_REQUESTS,
+ )
)
ticket_xmlui = tickets_xmlui[0]
data = ticket_xmlui.named_widgets[FIELD_DATA].value
@@ -344,10 +457,14 @@ class MergeRequests(object):
try:
handler = self._type_handlers[data_type]
except KeyError:
- raise exceptions.NotFound(_('No handler found to import {data_type}')
- .format(data_type=data_type))
- log.info(_("Importing patch [{item_id}] using {name} handler").format(
- item_id = item,
- name = handler.name))
- yield handler.handler.import_(repository, data, data_type, item, service, node,
- extra)
+ raise exceptions.NotFound(
+ _("No handler found to import {data_type}").format(data_type=data_type)
+ )
+ log.info(
+ _("Importing patch [{item_id}] using {name} handler").format(
+ item_id=item, name=handler.name
+ )
+ )
+ yield handler.handler.import_(
+ repository, data, data_type, item, service, node, extra
+ )
diff --git a/libervia/backend/plugins/plugin_misc_nat_port.py b/libervia/backend/plugins/plugin_misc_nat_port.py
index 10aac421..97915e51 100644
--- a/libervia/backend/plugins/plugin_misc_nat_port.py
+++ b/libervia/backend/plugins/plugin_misc_nat_port.py
@@ -46,9 +46,7 @@ PLUGIN_INFO = {
}
STARTING_PORT = 6000 # starting point to automatically find a port
-DEFAULT_DESC = (
- "SaT port mapping"
-) # we don't use "à" here as some bugged NAT don't manage charset correctly
+DEFAULT_DESC = "SaT port mapping" # we don't use "à" here as some bugged NAT don't manage charset correctly
class MappingError(Exception):
@@ -67,7 +65,9 @@ class NatPort(object):
self._upnp.discoverdelay = 200
self._mutex = threading.Lock() # used to protect access to self._upnp
self._starting_port_cache = None # used to cache the first available port
- self._to_unmap = [] # list of tuples (ext_port, protocol) of ports to unmap on unload
+ self._to_unmap = (
+ []
+ ) # list of tuples (ext_port, protocol) of ports to unmap on unload
discover_d = threads.deferToThread(self._discover)
discover_d.chainDeferred(self._initialised)
self._initialised.addErrback(self._init_failed)
diff --git a/libervia/backend/plugins/plugin_misc_radiocol.py b/libervia/backend/plugins/plugin_misc_radiocol.py
index 5de7e06d..649f5380 100644
--- a/libervia/backend/plugins/plugin_misc_radiocol.py
+++ b/libervia/backend/plugins/plugin_misc_radiocol.py
@@ -195,13 +195,11 @@ class Radiocol(object):
] # FIXME: referee comes from Libervia's client side, it's unsecure
radio_data["to_delete"][
attrs["filename"]
- ] = (
- song_path
- ) # FIXME: works only because of the same host trick, see the note under the docstring
+ ] = song_path # FIXME: works only because of the same host trick, see the note under the docstring
return self.send(referee, ("", "song_added"), attrs, profile=profile)
def play_next(self, room_jid, profile):
- """"Play next song in queue if exists, and put a timer
+ """ "Play next song in queue if exists, and put a timer
which trigger after the song has been played to play next one"""
# TODO: songs need to be erased once played or found invalids
# ==> unlink done the Q&D way with the same host trick (see above)
@@ -245,9 +243,7 @@ class Radiocol(object):
try:
file_to_delete = radio_data["to_delete"][filename]
except KeyError:
- log.error(
- _("INTERNAL ERROR: can't find full path of the song to delete")
- )
+ log.error(_("INTERNAL ERROR: can't find full path of the song to delete"))
return False
else:
file_to_delete = filename
diff --git a/libervia/backend/plugins/plugin_misc_remote_control.py b/libervia/backend/plugins/plugin_misc_remote_control.py
index 9cb56e66..3a6414aa 100644
--- a/libervia/backend/plugins/plugin_misc_remote_control.py
+++ b/libervia/backend/plugins/plugin_misc_remote_control.py
@@ -49,7 +49,6 @@ PLUGIN_INFO = {
}
-
class RemoteControl(BaseApplicationHandler):
def __init__(self, host):
@@ -84,19 +83,18 @@ class RemoteControl(BaseApplicationHandler):
) -> defer.Deferred[str]:
client = self.host.get_client(profile)
extra = data_format.deserialise(extra_s)
- d = defer.ensureDeferred(self.remote_control_start(
- client,
- jid.JID(peer_jid_s),
- extra,
- ))
+ d = defer.ensureDeferred(
+ self.remote_control_start(
+ client,
+ jid.JID(peer_jid_s),
+ extra,
+ )
+ )
d.addCallback(data_format.serialise)
return d
async def remote_control_start(
- self,
- client: SatXMPPEntity,
- peer_jid: jid.JID,
- extra: dict
+ self, client: SatXMPPEntity, peer_jid: jid.JID, extra: dict
) -> dict:
"""Start a remote control session.
@@ -104,9 +102,7 @@ class RemoteControl(BaseApplicationHandler):
@return: progress id
"""
if not extra:
- raise exceptions.DataError(
- '"extra" must be set.'
- )
+ raise exceptions.DataError('"extra" must be set.')
# webrtc is always used for remote control
extra["webrtc"] = True
content = {
@@ -127,9 +123,7 @@ class RemoteControl(BaseApplicationHandler):
try:
application_data = call_data["application"]
except KeyError:
- raise exceptions.DataError(
- '"call_data" must have an application media.'
- )
+ raise exceptions.DataError('"call_data" must have an application media.')
try:
content["transport_data"] = {
"sctp-port": metadata["sctp-port"],
@@ -139,7 +133,7 @@ class RemoteControl(BaseApplicationHandler):
"pwd": metadata["ice-pwd"],
"candidates": application_data.pop("ice-candidates"),
"fingerprint": application_data.pop("fingerprint", {}),
- }
+ },
}
name = application_data.get("id")
if name:
@@ -208,9 +202,7 @@ class RemoteControl(BaseApplicationHandler):
session_id = session["id"]
peer_jid = session["peer_jid"]
- is_in_roster, confirm_msg, confirm_title = self._get_confirm_msg(
- client, peer_jid
- )
+ is_in_roster, confirm_msg, confirm_title = self._get_confirm_msg(client, peer_jid)
if is_in_roster:
action_type = C.META_TYPE_CONFIRM
else:
@@ -227,7 +219,7 @@ class RemoteControl(BaseApplicationHandler):
confirm_msg,
confirm_title,
profile=client.profile,
- action_extra=action_extra
+ action_extra=action_extra,
)
if accepted:
session["pre_accepted"] = True
@@ -248,11 +240,7 @@ class RemoteControl(BaseApplicationHandler):
"""The remote control has been rejected"""
def jingle_session_init(
- self,
- client: SatXMPPEntity,
- session: dict,
- content_name: str,
- extra: dict
+ self, client: SatXMPPEntity, session: dict, content_name: str, extra: dict
) -> domish.Element:
"""Initializes a jingle session.
@@ -305,9 +293,7 @@ class RemoteControl(BaseApplicationHandler):
client, session, content_data, content_name
)
else:
- raise exceptions.InternalError(
- f"Invalid role {role!r}"
- )
+ raise exceptions.InternalError(f"Invalid role {role!r}")
async def _remote_control_request_conf(
self,
@@ -340,7 +326,7 @@ class RemoteControl(BaseApplicationHandler):
confirm_msg,
confirm_title,
profile=client.profile,
- action_extra=action_extra
+ action_extra=action_extra,
)
async def jingle_handler(self, client, action, session, content_name, desc_elt):
diff --git a/libervia/backend/plugins/plugin_misc_room_game.py b/libervia/backend/plugins/plugin_misc_room_game.py
index f719bfb0..f3da30bd 100644
--- a/libervia/backend/plugins/plugin_misc_room_game.py
+++ b/libervia/backend/plugins/plugin_misc_room_game.py
@@ -112,7 +112,9 @@ class RoomGame(object):
self.game_init = game_init
self.player_init = player_init
self.games = {}
- self.invitations = {} # values are a couple (x, y) with x the time and y a list of users
+ self.invitations = (
+ {}
+ ) # values are a couple (x, y) with x the time and y a list of users
# These are the default settings, which can be overwritten by child class after initialization
self.invite_mode = self.FROM_PLAYERS if self.player_init == {} else self.FROM_NONE
@@ -460,7 +462,9 @@ class RoomGame(object):
other_players = [jid.JID(player).userhostJID() for player in other_players]
return self.prepare_room(other_players, room_jid, profile_key)
- def prepare_room(self, other_players=None, room_jid=None, profile_key=C.PROF_KEY_NONE):
+ def prepare_room(
+ self, other_players=None, room_jid=None, profile_key=C.PROF_KEY_NONE
+ ):
"""Prepare the room for a game: create it if it doesn't exist and invite players.
@param other_players (list[JID]): list of other players JID (bare)
@@ -716,7 +720,7 @@ class RoomGame(object):
return started_elt
def _send_elements(self, to_jid, data, profile=None):
- """ TODO
+ """TODO
@param to_jid: recipient JID
@param data: list of (elem, attr, content) with:
@@ -745,7 +749,7 @@ class RoomGame(object):
return defer.succeed(None)
def send(self, to_jid, elem=None, attrs=None, content=None, profile=None):
- """ TODO
+ """TODO
@param to_jid: recipient JID
@param elem: domish.Element, unicode or a couple:
diff --git a/libervia/backend/plugins/plugin_misc_tarot.py b/libervia/backend/plugins/plugin_misc_tarot.py
index 009949a5..e38349f2 100644
--- a/libervia/backend/plugins/plugin_misc_tarot.py
+++ b/libervia/backend/plugins/plugin_misc_tarot.py
@@ -141,7 +141,12 @@ class Tarot(object):
for value in ["excuse"] + list(map(str, list(range(1, 22)))):
self.deck_ordered.append(TarotCard(("atout", value)))
for suit in ["pique", "coeur", "carreau", "trefle"]:
- for value in list(map(str, list(range(1, 11)))) + ["valet", "cavalier", "dame", "roi"]:
+ for value in list(map(str, list(range(1, 11)))) + [
+ "valet",
+ "cavalier",
+ "dame",
+ "roi",
+ ]:
self.deck_ordered.append(TarotCard((suit, value)))
self.__choose_contrat_id = host.register_callback(
self._contrat_choosed, with_data=True
@@ -474,9 +479,7 @@ class Tarot(object):
if played_card.suit == "atout" and played_card > biggest_atout:
biggest_atout = played_card
idx = (idx + 1) % len(players)
- has_suit = (
- False
- ) # True if there is one card of the asked suit in the hand of the player
+ has_suit = False # True if there is one card of the asked suit in the hand of the player
has_atout = False
biggest_hand_atout = None
@@ -608,7 +611,7 @@ class Tarot(object):
hand[players[i]] = deck[0:hand_size]
del deck[0:hand_size]
chien.extend(deck)
- del (deck[:])
+ del deck[:]
msg_elts = {}
for player in players:
msg_elts[player] = self.__card_list_to_xml(hand[player], "hand")
diff --git a/libervia/backend/plugins/plugin_misc_text_commands.py b/libervia/backend/plugins/plugin_misc_text_commands.py
index 2223b63d..09829092 100644
--- a/libervia/backend/plugins/plugin_misc_text_commands.py
+++ b/libervia/backend/plugins/plugin_misc_text_commands.py
@@ -151,7 +151,7 @@ class TextCommands(object):
return data
def register_text_commands(self, instance):
- """ Add a text command
+ """Add a text command
@param instance: instance of a class containing text commands
"""
@@ -196,12 +196,12 @@ class TextCommands(object):
def send_message_trigger(
self, client, mess_data, pre_xml_treatments, post_xml_treatments
):
- """Install SendMessage command hook """
+ """Install SendMessage command hook"""
pre_xml_treatments.addCallback(self._send_message_cmd_hook, client)
return True
def _send_message_cmd_hook(self, mess_data, client):
- """ Check text commands in message, and react consequently
+ """Check text commands in message, and react consequently
msg starting with / are potential command. If a command is found, it is executed,
else an help message is sent.
@@ -248,7 +248,7 @@ class TextCommands(object):
# looks like an actual command, we try to call the corresponding method
def ret_handling(ret):
- """ Handle command return value:
+ """Handle command return value:
if ret is True, normally send message (possibly modified by command)
else, abord message sending
"""
@@ -381,9 +381,7 @@ class TextCommands(object):
d = defer.succeed(None)
for __, callback in self._whois:
- d.addCallback(
- lambda __: callback(client, whois_msg, mess_data, target_jid)
- )
+ d.addCallback(lambda __: callback(client, whois_msg, mess_data, target_jid))
def feed_back(__):
self.feed_back(client, "\n".join(whois_msg), mess_data)
@@ -464,7 +462,10 @@ class TextCommands(object):
short_help=cmd_data["doc_short_help"],
syntax=_(" " * 4 + "syntax: {}\n").format(syntax) if syntax else "",
args_help="\n".join(
- [" " * 8 + "{}".format(line) for line in self._get_args_help(cmd_data)]
+ [
+ " " * 8 + "{}".format(line)
+ for line in self._get_args_help(cmd_data)
+ ]
),
)
diff --git a/libervia/backend/plugins/plugin_misc_text_syntaxes.py b/libervia/backend/plugins/plugin_misc_text_syntaxes.py
index a897c849..05b8a6b1 100644
--- a/libervia/backend/plugins/plugin_misc_text_syntaxes.py
+++ b/libervia/backend/plugins/plugin_misc_text_syntaxes.py
@@ -115,16 +115,80 @@ VOID_ELEMENTS = (
"param",
"source",
"track",
- "wbr")
+ "wbr",
+)
SAFE_ATTRS = html.defs.safe_attrs.union({"style", "poster", "controls"}) - {"id"}
SAFE_CLASSES = {
# those classes are used for code highlighting
- "bp", "c", "ch", "cm", "cp", "cpf", "cs", "dl", "err", "fm", "gd", "ge", "get", "gh",
- "gi", "go", "gp", "gr", "gs", "gt", "gu", "highlight", "hll", "il", "k", "kc", "kd",
- "kn", "kp", "kr", "kt", "m", "mb", "mf", "mh", "mi", "mo", "na", "nb", "nc", "nd",
- "ne", "nf", "ni", "nl", "nn", "no", "nt", "nv", "o", "ow", "s", "sa", "sb", "sc",
- "sd", "se", "sh", "si", "sr", "ss", "sx", "vc", "vg", "vi", "vm", "w", "write",
+ "bp",
+ "c",
+ "ch",
+ "cm",
+ "cp",
+ "cpf",
+ "cs",
+ "dl",
+ "err",
+ "fm",
+ "gd",
+ "ge",
+ "get",
+ "gh",
+ "gi",
+ "go",
+ "gp",
+ "gr",
+ "gs",
+ "gt",
+ "gu",
+ "highlight",
+ "hll",
+ "il",
+ "k",
+ "kc",
+ "kd",
+ "kn",
+ "kp",
+ "kr",
+ "kt",
+ "m",
+ "mb",
+ "mf",
+ "mh",
+ "mi",
+ "mo",
+ "na",
+ "nb",
+ "nc",
+ "nd",
+ "ne",
+ "nf",
+ "ni",
+ "nl",
+ "nn",
+ "no",
+ "nt",
+ "nv",
+ "o",
+ "ow",
+ "s",
+ "sa",
+ "sb",
+ "sc",
+ "sd",
+ "se",
+ "sh",
+ "si",
+ "sr",
+ "ss",
+ "sx",
+ "vc",
+ "vg",
+ "vi",
+ "vm",
+ "w",
+ "write",
}
STYLES_VALUES_REGEX = (
r"^("
@@ -157,7 +221,7 @@ PLUGIN_INFO = {
class TextSyntaxes(object):
- """ Text conversion class
+ """Text conversion class
XHTML utf-8 is used as intermediate language for conversions
"""
@@ -170,7 +234,6 @@ class TextSyntaxes(object):
# default_syntax must be lower case
default_syntax = SYNTAX_XHTML
-
def __init__(self, host):
log.info(_("Text syntaxes plugin initialization"))
self.host = host
@@ -218,8 +281,8 @@ class TextSyntaxes(object):
# when the user is not aware of markdown and HTML
class EscapeHTML(Extension):
def extendMarkdown(self, md):
- md.preprocessors.deregister('html_block')
- md.inlinePatterns.deregister('html')
+ md.preprocessors.deregister("html_block")
+ md.inlinePatterns.deregister("html")
def _html2text(html, baseurl=""):
h = html2text.HTML2Text(baseurl=baseurl)
@@ -228,20 +291,22 @@ class TextSyntaxes(object):
self.add_syntax(
self.SYNTAX_MARKDOWN,
- partial(markdown.markdown,
- extensions=[
- EscapeHTML(),
- 'nl2br',
- 'codehilite',
- 'fenced_code',
- 'sane_lists',
- 'tables',
- ],
- extension_configs = {
- "codehilite": {
- "css_class": "highlight",
- }
- }),
+ partial(
+ markdown.markdown,
+ extensions=[
+ EscapeHTML(),
+ "nl2br",
+ "codehilite",
+ "fenced_code",
+ "sane_lists",
+ "tables",
+ ],
+ extension_configs={
+ "codehilite": {
+ "css_class": "highlight",
+ }
+ },
+ ),
_html2text,
[TextSyntaxes.OPT_DEFAULT],
)
@@ -287,7 +352,7 @@ class TextSyntaxes(object):
self.host.memory.update_params(self.params % self.params_data)
def get_current_syntax(self, profile):
- """ Return the selected syntax for the given profile
+ """Return the selected syntax for the given profile
@param profile: %(doc_profile)s
@return: profile selected syntax
@@ -301,7 +366,7 @@ class TextSyntaxes(object):
return failure
def clean_style(self, styles_raw: str) -> str:
- """"Clean unsafe CSS styles
+ """ "Clean unsafe CSS styles
Remove styles not in the whitelist, or where the value doesn't match the regex
@param styles_raw: CSS styles
@@ -323,9 +388,7 @@ class TextSyntaxes(object):
if value == "none":
continue
cleaned_styles.append((key, value))
- return "; ".join(
- ["%s: %s" % (key_, value_) for key_, value_ in cleaned_styles]
- )
+ return "; ".join(["%s: %s" % (key_, value_) for key_, value_ in cleaned_styles])
def clean_classes(self, classes_raw: str) -> str:
"""Remove any non whitelisted class
@@ -355,9 +418,7 @@ class TextSyntaxes(object):
else:
log.error("Only strings and HtmlElements can be cleaned")
raise exceptions.DataError
- cleaner = clean.Cleaner(
- style=False, add_nofollow=False, safe_attrs=SAFE_ATTRS
- )
+ cleaner = clean.Cleaner(style=False, add_nofollow=False, safe_attrs=SAFE_ATTRS)
xhtml_elt = cleaner.clean_html(xhtml_elt)
for elt in xhtml_elt.xpath("//*[@style]"):
elt.set("style", self.clean_style(elt.get("style")))
@@ -369,11 +430,12 @@ class TextSyntaxes(object):
if element.tag in VOID_ELEMENTS:
element.text = None
else:
- element.text = ''
+ element.text = ""
return html.tostring(xhtml_elt, encoding=str, method="xml")
- def convert(self, text, syntax_from, syntax_to=_SYNTAX_XHTML, safe=True,
- profile=None):
+ def convert(
+ self, text, syntax_from, syntax_to=_SYNTAX_XHTML, safe=True, profile=None
+ ):
"""Convert a text between two syntaxes
@param text: text to convert
diff --git a/libervia/backend/plugins/plugin_misc_upload.py b/libervia/backend/plugins/plugin_misc_upload.py
index 401c0f60..885a1175 100644
--- a/libervia/backend/plugins/plugin_misc_upload.py
+++ b/libervia/backend/plugins/plugin_misc_upload.py
@@ -68,15 +68,15 @@ class UploadPlugin(object):
self._upload_callbacks = []
def _file_upload(
- self, filepath, filename, upload_jid_s="", options='', profile=C.PROF_KEY_NONE
+ self, filepath, filename, upload_jid_s="", options="", profile=C.PROF_KEY_NONE
):
client = self.host.get_client(profile)
upload_jid = jid.JID(upload_jid_s) if upload_jid_s else None
options = data_format.deserialise(options)
- return defer.ensureDeferred(self.file_upload(
- client, filepath, filename or None, upload_jid, options
- ))
+ return defer.ensureDeferred(
+ self.file_upload(client, filepath, filename or None, upload_jid, options)
+ )
async def file_upload(self, client, filepath, filename, upload_jid, options):
"""Send a file using best available method
@@ -87,10 +87,13 @@ class UploadPlugin(object):
"""
try:
progress_id, __ = await self.upload(
- client, filepath, filename, upload_jid, options)
+ client, filepath, filename, upload_jid, options
+ )
except Exception as e:
- if (isinstance(e, jabber_error.StanzaError)
- and e.condition == 'not-acceptable'):
+ if (
+ isinstance(e, jabber_error.StanzaError)
+ and e.condition == "not-acceptable"
+ ):
reason = e.text
else:
reason = str(e)
@@ -110,7 +113,7 @@ class UploadPlugin(object):
filepath: Union[Path, str],
filename: Optional[str] = None,
upload_jid: Optional[jid.JID] = None,
- extra: Optional[dict]=None
+ extra: Optional[dict] = None,
) -> Tuple[str, defer.Deferred]:
"""Send a file using best available method
diff --git a/libervia/backend/plugins/plugin_misc_uri_finder.py b/libervia/backend/plugins/plugin_misc_uri_finder.py
index 1a82ebd9..17420d3f 100644
--- a/libervia/backend/plugins/plugin_misc_uri_finder.py
+++ b/libervia/backend/plugins/plugin_misc_uri_finder.py
@@ -22,6 +22,7 @@ from libervia.backend.core.constants import Const as C
from libervia.backend.core.log import getLogger
from twisted.internet import defer
import textwrap
+
log = getLogger(__name__)
import json
import os.path
@@ -36,14 +37,18 @@ PLUGIN_INFO = {
C.PI_DEPENDENCIES: [],
C.PI_MAIN: "URIFinder",
C.PI_HANDLER: "no",
- C.PI_DESCRIPTION: textwrap.dedent(_("""\
+ C.PI_DESCRIPTION: textwrap.dedent(
+ _(
+ """\
Plugin to find URIs in well know location.
This allows to retrieve settings to work with a project (e.g. pubsub node used for merge-requests).
- """))
+ """
+ )
+ ),
}
-SEARCH_FILES = ('readme', 'contributing')
+SEARCH_FILES = ("readme", "contributing")
class URIFinder(object):
@@ -51,10 +56,14 @@ class URIFinder(object):
def __init__(self, host):
log.info(_("URI finder plugin initialization"))
self.host = host
- host.bridge.add_method("uri_find", ".plugin",
- in_sign='sas', out_sign='a{sa{ss}}',
- method=self.find,
- async_=True)
+ host.bridge.add_method(
+ "uri_find",
+ ".plugin",
+ in_sign="sas",
+ out_sign="a{sa{ss}}",
+ method=self.find,
+ async_=True,
+ )
def find(self, path, keys):
"""Look for URI in well known locations
@@ -64,31 +73,38 @@ class URIFinder(object):
e.g.: "tickets", "merge-requests"
@return (dict[unicode, unicode]): map from key to found uri
"""
- keys_re = '|'.join(keys)
+ keys_re = "|".join(keys)
label_re = r'"(?P