# HG changeset patch # User Goffi # Date 1605189195 -3600 # Node ID 404d4b29de523b2545b9d3610e4906f6f1adf875 # Parent 08a3e34aead10564b8088c5ab376ae18bbf988cb 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) diff -r 08a3e34aead1 -r 404d4b29de52 sat/plugins/plugin_misc_file.py --- 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 . +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 diff -r 08a3e34aead1 -r 404d4b29de52 sat/plugins/plugin_xep_0234.py --- 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 . -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,