diff --git a/sat/plugins/plugin_misc_file.py b/sat/plugins/plugin_misc_file.py
index a4456a31..a5e7dd3c 100644
--- a/sat/plugins/plugin_misc_file.py
+++ b/sat/plugins/plugin_misc_file.py
@@ -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 @@ class FilePlugin:
method=self._fileSend,
async_=True,
)
- self._file_callbacks = []
+ self._file_managers = []
host.importMenu(
(D_("Action"), D_("send file")),
self._fileSendMenu,
@@ -85,12 +88,11 @@ class FilePlugin:
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 @@ class FilePlugin:
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 @@ class FilePlugin:
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 @@ class FilePlugin:
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 --git a/sat/plugins/plugin_xep_0234.py b/sat/plugins/plugin_xep_0234.py
index 7ece2f18..7083d474 100644
--- a/sat/plugins/plugin_xep_0234.py
+++ b/sat/plugins/plugin_xep_0234.py
@@ -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
-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
+import mimetypes
+from collections import namedtuple
+from zope.interface import implementer
from twisted.words.xish import domish
from twisted.words.protocols.jabber import jid
from twisted.python import failure
@@ -35,11 +27,19 @@ from twisted.words.protocols.jabber.xmlstream import XMPPHandler
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"
PLUGIN_INFO = {
@@ -58,10 +58,13 @@ EXTRA_ALLOWED = {"path", "namespace", "file_desc", "file_hash", "hash_algo"}
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 @@ class XEP_0234(object):
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 @@ class XEP_0234(object):
"""
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 @@ class XEP_0234(object):
# bridge methods
- def _fileJingleSend(
+ def _fileSend(
self,
peer_jid,
filepath,
@@ -296,17 +304,16 @@ class XEP_0234(object):
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 @@ class XEP_0234(object):
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 @@ class XEP_0234(object):
}
],
)
- 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,