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)
This commit is contained in:
parent
afacc8d4c1
commit
46c763cbf7
2 changed files with 102 additions and 79 deletions
|
|
@ -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 @@ 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue