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
This commit is contained in:
Goffi 2024-12-03 00:11:00 +01:00
commit 1f3c6f0329
4 changed files with 82 additions and 78 deletions

View file

@ -37,6 +37,12 @@ class SatXMPPEntity:
identities: list[disco.DiscoIdentity]
class SatXMPPComponent(SatXMPPEntity):
def is_local(self, jid_: t_jid.JID) -> bool:
...
EncryptionPlugin = namedtuple(
"EncryptionPlugin", ("instance", "name", "namespace", "priority", "directed")
)

View file

@ -516,9 +516,9 @@ class LiberviaBackend(service.Service):
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

View file

@ -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 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
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 @@ class SatXMPPClient(SatXMPPEntity, wokkel_client.XMPPClient):
@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.

View file

@ -1653,70 +1653,64 @@ class Memory:
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 @@ class Memory:
parent=parent,
type_=C.FILE_TYPE_DIRECTORY,
namespace=namespace,
created=time.time(),
created=created,
owner=owner,
access=access,
extra={},
@ -1827,10 +1821,9 @@ class Memory:
"""
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 @@ class Memory:
):
"""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 @@ class Memory:
)
)
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 @@ class Memory:
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: