Mercurial > libervia-backend
changeset 3403:404d4b29de52
plugin file, XEP-0234: registering is now done by class + use of async:
- instead of registering a callback, a file sending manager now register itself and must
implement some well known method (`fileSend`, `canHandleFileSend`) and optionally a
`name` attribute
- `utils.asDeferred` is now used for callbacks, so all type of methods including
coroutines can be used.
- feature checking is now handled by `canHandleFileSend` method instead of simple
namespace check, this allows to use a method when namespace can't be checked (this is
the case when a file is sent to a bare jid with jingle)
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 12 Nov 2020 14:53:15 +0100 |
parents | 08a3e34aead1 |
children | 26a0af6e32c1 |
files | sat/plugins/plugin_misc_file.py sat/plugins/plugin_xep_0234.py |
diffstat | 2 files changed, 101 insertions(+), 78 deletions(-) [+] |
line wrap: on
line diff
--- a/sat/plugins/plugin_misc_file.py Thu Nov 12 14:53:15 2020 +0100 +++ b/sat/plugins/plugin_misc_file.py Thu Nov 12 14:53:15 2020 +0100 @@ -17,18 +17,21 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +import os +import os.path +from functools import partial +from twisted.internet import defer +from twisted.words.protocols.jabber import jid from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger - -log = getLogger(__name__) from sat.core import exceptions from sat.tools import xml_tools from sat.tools import stream -from twisted.internet import defer -from twisted.words.protocols.jabber import jid -import os -import os.path +from sat.tools import utils + + +log = getLogger(__name__) PLUGIN_INFO = { @@ -73,7 +76,7 @@ method=self._fileSend, async_=True, ) - self._file_callbacks = [] + self._file_managers = [] host.importMenu( (D_("Action"), D_("send file")), self._fileSendMenu, @@ -85,12 +88,11 @@ def _fileSend(self, peer_jid_s, filepath, name="", file_desc="", extra=None, profile=C.PROF_KEY_NONE): client = self.host.getClient(profile) - return self.fileSend( + return defer.ensureDeferred(self.fileSend( client, jid.JID(peer_jid_s), filepath, name or None, file_desc or None, extra - ) + )) - @defer.inlineCallbacks - def fileSend( + async def fileSend( self, client, peer_jid, filepath, filename=None, file_desc=None, extra=None ): """Send a file using best available method @@ -107,29 +109,45 @@ raise exceptions.DataError("The given path doesn't link to a file") if not filename: filename = os.path.basename(filepath) or "_" - for namespace, callback, priority, method_name in self._file_callbacks: - has_feature = yield self.host.hasFeature(client, namespace, peer_jid) - if has_feature: + for manager, priority in self._file_managers: + if await utils.asDeferred(manager.canHandleFileSend, + client, peer_jid, filepath): + try: + method_name = manager.name + except AttributeError: + method_name = manager.__class__.__name__ log.info( - "{name} method will be used to send the file".format( + _("{name} method will be used to send the file").format( name=method_name ) ) - progress_id = yield callback( - client, peer_jid, filepath, filename, file_desc, extra - ) - defer.returnValue({"progress": progress_id}) + try: + progress_id = await utils.asDeferred( + manager.fileSend, client, peer_jid, 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 + ) + ) + continue + return {"progress": progress_id} msg = "Can't find any method to send file to {jid}".format(jid=peer_jid.full()) log.warning(msg) - defer.returnValue( - { - "xmlui": xml_tools.note( - "Can't transfer file", msg, C.XMLUI_DATA_LVL_WARNING - ).toXml() - } - ) + return { + "xmlui": xml_tools.note( + "Can't transfer file", msg, C.XMLUI_DATA_LVL_WARNING + ).toXml() + } - def _onFileChoosed(self, client, peer_jid, data): + def _onFileChoosed(self, peer_jid, data, profile): + client = self.host.getClient(profile) cancelled = C.bool(data.get("cancelled", C.BOOL_FALSE)) if cancelled: return @@ -147,9 +165,7 @@ raise exceptions.DataError(_("Invalid JID")) file_choosed_id = self.host.registerCallback( - lambda data, profile: self._onFileChoosed( - self.host.getClient(profile), jid_, data - ), + partial(self._onFileChoosed, jid_), with_data=True, one_shot=True, ) @@ -165,30 +181,31 @@ return {"xmlui": xml_ui.toXml()} - def register(self, namespace, callback, priority=0, method_name=None): - """Register a fileSending method + def register(self, manager, priority: int = 0) -> None: + """Register a fileSending manager - @param namespace(unicode): XEP namespace - @param callback(callable): method to call (must have the same signature as [fileSend]) - @param priority(int): pririoty of this method, the higher available will be used - @param method_name(unicode): short name for the method, namespace will be used if None + @param manager: object implementing canHandleFileSend, and fileSend methods + @param priority: pririoty of this manager, the higher available will be used """ - for data in self._file_callbacks: - if namespace == data[0]: - raise exceptions.ConflictError( - "A method with this namespace is already registered" - ) - self._file_callbacks.append( - (namespace, callback, priority, method_name or namespace) - ) - self._file_callbacks.sort(key=lambda data: data[2], reverse=True) + m_data = (manager, priority) + if m_data in self._file_managers: + raise exceptions.ConflictError( + f"Manager {manager} is already registered" + ) + if not hasattr(manager, "canHandleFileSend") or not hasattr(manager, "fileSend"): + raise ValueError( + f'{manager} must have both "canHandleFileSend" and "fileSend" methods to ' + 'be registered') + self._file_managers.append(m_data) + self._file_managers.sort(key=lambda m: m[1], reverse=True) - def unregister(self, namespace): - for idx, data in enumerate(self._file_callbacks): - if data[0] == namespace: - del [idx] - return - raise exceptions.NotFound("The namespace to unregister doesn't exist") + def unregister(self, manager): + for idx, data in enumerate(self._file_managers): + if data[0] == manager: + break + else: + raise exceptions.NotFound("The file manager {manager} is not registered") + del self._file_managers[idx] # Dialogs with user # the overwrite check is done here
--- a/sat/plugins/plugin_xep_0234.py Thu Nov 12 14:53:15 2020 +0100 +++ b/sat/plugins/plugin_xep_0234.py Thu Nov 12 14:53:15 2020 +0100 @@ -16,18 +16,10 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -from sat.core.i18n import _ -from sat.core.constants import Const as C -from sat.core.log import getLogger - -log = getLogger(__name__) -from sat.core import exceptions -from wokkel import disco, iwokkel +import os.path +import mimetypes +from collections import namedtuple from zope.interface import implementer -from sat.tools import utils -from sat.tools import stream -from sat.tools.common import date_utils -import os.path from twisted.words.xish import domish from twisted.words.protocols.jabber import jid from twisted.python import failure @@ -35,10 +27,18 @@ from twisted.internet import defer from twisted.internet import reactor from twisted.internet import error as internet_error -from collections import namedtuple +from wokkel import disco, iwokkel +from sat.core.i18n import _, D_ +from sat.core.constants import Const as C +from sat.core.log import getLogger +from sat.core import exceptions +from sat.tools import utils +from sat.tools import stream +from sat.tools.common import date_utils from sat.tools.common import regex -import mimetypes + +log = getLogger(__name__) NS_JINGLE_FT = "urn:xmpp:jingle:apps:file-transfer:5" @@ -58,10 +58,13 @@ Range = namedtuple("Range", ("offset", "length")) -class XEP_0234(object): +class XEP_0234: # TODO: assure everything is closed when file is sent or session terminate is received - # TODO: call self._f.unregister when unloading order will be managing (i.e. when dependencies will be unloaded at the end) + # TODO: call self._f.unregister when unloading order will be managing (i.e. when + # dependencies will be unloaded at the end) Range = Range # we copy the class here, so it can be used by other plugins + name = PLUGIN_INFO[C.PI_NAME] + human_name = D_("file transfer") def __init__(self, host): log.info(_("plugin Jingle File Transfer initialization")) @@ -70,16 +73,14 @@ self._j = host.plugins["XEP-0166"] # shortcut to access jingle self._j.registerApplication(NS_JINGLE_FT, self) self._f = host.plugins["FILE"] - self._f.register( - NS_JINGLE_FT, self.fileJingleSend, priority=10000, method_name="Jingle" - ) + self._f.register(self, priority=10000) self._hash = self.host.plugins["XEP-0300"] host.bridge.addMethod( "fileJingleSend", ".plugin", in_sign="ssssa{ss}s", out_sign="", - method=self._fileJingleSend, + method=self._fileSend, async_=True, ) host.bridge.addMethod( @@ -103,6 +104,13 @@ """ return "{}_{}".format(session["id"], content_name) + async def canHandleFileSend(self, client, peer_jid, filepath): + if peer_jid.resource: + return await self.host.hasFeature(client, NS_JINGLE_FT, peer_jid) + else: + # if we have a bare jid, Jingle Message Initiation will be tried + return True + # generic methods def buildFileElement( @@ -286,7 +294,7 @@ # bridge methods - def _fileJingleSend( + def _fileSend( self, peer_jid, filepath, @@ -296,17 +304,16 @@ profile=C.PROF_KEY_NONE, ): client = self.host.getClient(profile) - return self.fileJingleSend( + return defer.ensureDeferred(self.fileSend( client, jid.JID(peer_jid), filepath, name or None, file_desc or None, extra or None, - ) + )) - @defer.inlineCallbacks - def fileJingleSend( + async def fileSend( self, client, peer_jid, filepath, name, file_desc=None, extra=None ): """Send a file using jingle file transfer @@ -322,7 +329,7 @@ extra = {} if file_desc is not None: extra["file_desc"] = file_desc - yield self._j.initiate( + await self._j.initiate( client, peer_jid, [ @@ -338,8 +345,7 @@ } ], ) - progress_id = yield progress_id_d - defer.returnValue(progress_id) + return await progress_id_d def _fileJingleRequest( self, peer_jid, filepath, name="", file_hash="", hash_algo="", extra=None,