changeset 4333:e94799a0908f

core (main): Let plugins have several handlers + various improvements: `get_handler` can now return several handlers, which is notably useful for component with several included services. Various improvements such move to `async` method, docstring/type hints update, and slight improvment of SatXMPPComponent. rel 453
author Goffi <goffi@goffi.org>
date Tue, 03 Dec 2024 00:11:00 +0100
parents 71c939e34ca6
children 111dce64dcb5
files libervia/backend/core/core_types.py libervia/backend/core/main.py libervia/backend/core/xmpp.py libervia/backend/memory/memory.py
diffstat 4 files changed, 81 insertions(+), 77 deletions(-) [+]
line wrap: on
line diff
--- a/libervia/backend/core/core_types.py	Sat Jul 13 18:28:28 2024 +0200
+++ b/libervia/backend/core/core_types.py	Tue Dec 03 00:11:00 2024 +0100
@@ -37,6 +37,12 @@
     identities: list[disco.DiscoIdentity]
 
 
+class SatXMPPComponent(SatXMPPEntity):
+
+    def is_local(self, jid_: t_jid.JID) -> bool:
+        ...
+
+
 EncryptionPlugin = namedtuple(
     "EncryptionPlugin", ("instance", "name", "namespace", "priority", "directed")
 )
--- a/libervia/backend/core/main.py	Sat Jul 13 18:28:28 2024 +0200
+++ b/libervia/backend/core/main.py	Tue Dec 03 00:11:00 2024 +0100
@@ -516,9 +516,9 @@
                 return
             raise ImportError("Error during initiation")
         if C.bool(plugin_info.get(C.PI_HANDLER, C.BOOL_FALSE)):
-            self.plugins[import_name].is_handler = True
+            self.plugins[import_name].has_handlers = True
         else:
-            self.plugins[import_name].is_handler = False
+            self.plugins[import_name].has_handlers = False
         # we keep metadata as a Class attribute
         self.plugins[import_name]._info = plugin_info
         # TODO: test xmppclient presence and register handler parent
--- a/libervia/backend/core/xmpp.py	Sat Jul 13 18:28:28 2024 +0200
+++ b/libervia/backend/core/xmpp.py	Tue Dec 03 00:11:00 2024 +0100
@@ -18,6 +18,7 @@
 
 import asyncio
 import calendar
+from collections.abc import Iterable
 import copy
 from functools import partial
 import mimetypes
@@ -146,8 +147,12 @@
         self.plugins = plugins = self._get_plugins_list()
         for plugin in plugins:
             # we check if plugin handle client mode
-            if plugin.is_handler:
-                plugin.get_handler(self).setHandlerParent(self)
+            if plugin.has_handlers:
+                handlers = plugin.get_handler(self)
+                if not isinstance(handlers, Iterable):
+                    handlers = [handlers]
+                for handler in handlers:
+                    handler.setHandlerParent(self)
 
             # profile_connecting/profile_connected methods handling
 
@@ -1085,7 +1090,7 @@
 
 
 @implementer(iwokkel.IDisco)
-class SatXMPPComponent(SatXMPPEntity, component.Component):
+class SatXMPPComponent(SatXMPPEntity, core_types.SatXMPPComponent, component.Component):
     """XMPP component
 
     This component are similar but not identical to clients.
--- a/libervia/backend/memory/memory.py	Sat Jul 13 18:28:28 2024 +0200
+++ b/libervia/backend/memory/memory.py	Tue Dec 03 00:11:00 2024 +0100
@@ -1653,70 +1653,64 @@
 
     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,),
-    ):
-        """Set a file metadata
+        client: SatXMPPEntity,
+        name: str,
+        file_id: str | None = None,
+        version: str = "",
+        parent: str | None = None,
+        path: str | None = None,
+        type_: str = C.FILE_TYPE_FILE,
+        file_hash: str | None = None,
+        hash_algo: str | None = None,
+        size: int | None = None,
+        namespace: str | None = None,
+        mime_type: str | None = None,
+        public_id: str | None = None,
+        created: float | None = None,
+        modified: int | None = None,
+        owner: jid.JID | None = None,
+        access: dict | None = None,
+        extra: dict | None = None,
+        peer_jid: Any | None = None,
+        perms_to_check: tuple[str]|None = (C.ACCESS_PERM_WRITE,),
+    ) -> None:
+        """Set a file metadata.
 
-        @param name(unicode): basename of the file
-        @param file_id(unicode): unique id of the file
-        @param version(unicode): version of this file
-            empty string for current version or when there is no versioning
-        @param parent(unicode, None): id of the directory containing the files
-        @param path(unicode, None): virtual path of the file in the namespace
-            if set, parent must be None. All intermediate directories will be created
-            if needed, using current access.
-        @param type_(str, None): type of file filter, can be one of C.FILE_TYPE_*
-        @param file_hash(unicode): unique hash of the payload
-        @param hash_algo(unicode): algorithm used for hashing the file (usually sha-256)
-        @param size(int): size in bytes
-        @param namespace(unicode, None): identifier (human readable is better) to group
-                                         files
-            For instance, namespace could be used to group files in a specific photo album
-        @param mime_type(unicode): MIME type of the file, or None if not known/guessed
-        @param public_id(unicode): id used to share publicly the file via HTTP
-        @param created(int): UNIX time of creation
-        @param modified(int,None): UNIX time of last modification, or None to use
-                                   created date
-        @param owner(jid.JID, None): jid of the owner of the file (mainly useful for
-                                     component)
-            will be used to check permission (only bare jid is used, don't use with MUC).
-            Use None to ignore permission (perms_to_check must be None too)
-        @param access(dict, None): serialisable dictionary with access rules.
+        @param name: basename of the file
+        @param file_id: unique id of the file
+        @param version: version of this file, empty string for current version or when
+            there is no versioning
+        @param parent: id of the directory containing the files
+        @param path: virtual path of the file in the namespace, if set, parent must be
+        None. All intermediate directories will be created if needed, using current
+            access.
+        @param type_: type of file filter, can be one of C.FILE_TYPE_*
+        @param file_hash: unique hash of the payload
+        @param hash_algo: algorithm used for hashing the file (usually sha-256)
+        @param size: size in bytes
+        @param namespace: identifier (human readable is better) to group files. For
+            instance, namespace could be used to group files in a specific photo album.
+        @param mime_type: MIME type of the file, or None if not known/guessed
+        @param public_id: id used to share publicly the file via HTTP
+        @param created: UNIX time of creation
+        @param modified: UNIX time of last modification, or None to use created date
+        @param owner: jid of the owner of the file (mainly useful for component). will be
+            used to check permission (only bare jid is used, don't use with MUC). Use None
+            to ignore permission (perms_to_check must be None too)
+        @param access: serialisable dictionary with access rules.
             None (or empty dict) to use private access, i.e. allow only profile's jid to
-            access the file
-            key can be on on C.ACCESS_PERM_*,
-            then a sub dictionary with a type key is used (one of C.ACCESS_TYPE_*).
+            access the file.
+            Key can be on on C.ACCESS_PERM_*, then a sub dictionary with a type key is
+            used (one of C.ACCESS_TYPE_*).
             According to type, extra keys can be used:
                 - C.ACCESS_TYPE_PUBLIC: the permission is granted for everybody
                 - C.ACCESS_TYPE_WHITELIST: the permission is granted for jids (as unicode)
-                  in the 'jids' key
-            will be encoded to json in database
-        @param extra(dict, None): serialisable dictionary of any extra data
-            will be encoded to json in database
-        @param perms_to_check(tuple[unicode],None): permission to check
-            must be a tuple of C.ACCESS_PERM_* or None
-            if None, permission will not be checked (peer_jid must be None too in this
-            case)
-        @param profile(unicode): profile owning the file
+                  in the 'jids' key. will be encoded to json in database
+        @param extra: serialisable dictionary of any extra data. will be encoded to json
+            in database
+        @param perms_to_check: permission to check. must be a tuple of C.ACCESS_PERM_* or
+            None. if None, permission will not be checked (peer_jid must be None too in
+            this case)
         """
         if "/" in name:
             raise ValueError('name must not contain a slash ("/")')
@@ -1768,7 +1762,7 @@
                         parent=parent,
                         type_=C.FILE_TYPE_DIRECTORY,
                         namespace=namespace,
-                        created=time.time(),
+                        created=created,
                         owner=owner,
                         access=access,
                         extra={},
@@ -1827,10 +1821,9 @@
         """
         return self.storage.file_update(file_id, column, update_cb)
 
-    @defer.inlineCallbacks
-    def _delete_file(
+    async def _delete_file(
         self,
-        client,
+        client: SatXMPPEntity,
         peer_jid: jid.JID,
         recursive: bool,
         files_path: Path,
@@ -1838,11 +1831,11 @@
     ):
         """Internal method to delete files/directories recursively
 
-        @param peer_jid(jid.JID): entity requesting the deletion (must be owner of files
+        @param peer_jid: entity requesting the deletion (must be owner of files
             to delete)
-        @param recursive(boolean): True if recursive deletion is needed
-        @param files_path(unicode): path of the directory containing the actual files
-        @param file_data(dict): data of the file to delete
+        @param recursive: True if recursive deletion is needed
+        @param files_path: path of the directory containing the actual files
+        @param file_data: data of the file to delete
         """
         if file_data["owner"] != peer_jid:
             raise exceptions.PermissionError(
@@ -1851,7 +1844,7 @@
                 )
             )
         if file_data["type"] == C.FILE_TYPE_DIRECTORY:
-            sub_files = yield self.get_files(client, peer_jid, parent=file_data["id"])
+            sub_files = await 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
@@ -1860,19 +1853,19 @@
                     sub_file_path = files_path / sub_file_data["name"]
                 else:
                     sub_file_path = files_path
-                yield self._delete_file(
+                await self._delete_file(
                     client, peer_jid, recursive, sub_file_path, sub_file_data
                 )
             # then the directory itself
-            yield self.storage.file_delete(file_data["id"])
+            await 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(
+            await self.storage.file_delete(file_data["id"])
+            references = await self.get_files(
                 client, peer_jid, file_hash=file_data["file_hash"]
             )
             if references: