plugins XEP-0096, XEP-0260, file: sendFile method is managed by file plugin, which choose the best available method + progress_id fix
This commit is contained in:
parent
f61c88f784
commit
aaa3c32378
5 changed files with 97 additions and 8 deletions
|
|
@ -839,7 +839,7 @@ class SAT(service.Service):
|
|||
try:
|
||||
del client._progress_cb[progress_id]
|
||||
except KeyError:
|
||||
log.error(_("Trying to remove an unknow progress callback"))
|
||||
log.error(_(u"Trying to remove an unknow progress callback"))
|
||||
|
||||
def _progressGet(self, progress_id, profile):
|
||||
data = self.progressGet(progress_id, profile)
|
||||
|
|
|
|||
|
|
@ -21,9 +21,12 @@ 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 twisted.internet import defer
|
||||
from twisted.words.protocols.jabber import jid
|
||||
import os
|
||||
import os.path
|
||||
import uuid
|
||||
|
||||
|
||||
|
|
@ -43,6 +46,8 @@ CONFIRM_TITLE = D_(u'Confirm file transfer')
|
|||
CONFIRM_OVERWRITE = D_(u'File {} already exists, are you sure you want to overwrite ?')
|
||||
CONFIRM_OVERWRITE_TITLE = D_(u'File exists')
|
||||
|
||||
PROGRESS_ID_KEY = 'progress_id'
|
||||
|
||||
|
||||
class SatFile(object):
|
||||
"""A file-like object to have high level files manipulation"""
|
||||
|
|
@ -54,6 +59,7 @@ class SatFile(object):
|
|||
@param path(str): path of the file to get
|
||||
@param mode(str): same as for built-in "open" function
|
||||
@param uid(unicode, None): unique id identifing this progressing element
|
||||
This uid will be used with self.host.progressGet
|
||||
will be automaticaly generated if None
|
||||
@param size(None, int): size of the file
|
||||
"""
|
||||
|
|
@ -100,6 +106,57 @@ class FilePlugin(object):
|
|||
def __init__(self, host):
|
||||
log.info(_("plugin File initialization"))
|
||||
self.host = host
|
||||
host.bridge.addMethod("fileSend", ".plugin", in_sign='sssss', out_sign='a{ss}', method=self._fileSend, async=True)
|
||||
self._file_callbacks = []
|
||||
|
||||
def _fileSend(self, peer_jid_s, filepath, name="", file_desc="", profile=C.PROF_KEY_NONE):
|
||||
return self.fileSend(jid.JID(peer_jid_s), filepath, name or None, file_desc or None, profile)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def fileSend(self, peer_jid, filepath, filename=None, file_desc=None, profile=C.PROF_KEY_NONE):
|
||||
"""Send a file using best available method
|
||||
|
||||
@param peer_jid(jid.JID): jid of the destinee
|
||||
@param filepath(str): absolute path to the file
|
||||
@param filename(unicode, None): name to use, or None to find it from filepath
|
||||
@param file_desc(unicode, None): description of the file
|
||||
@param profile: %(doc_profile)s
|
||||
@return (dict): action dictionary, with progress id in case of success, else xmlui message
|
||||
"""
|
||||
if not os.path.isfile(filepath):
|
||||
raise exceptions.DataError(u"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(namespace, peer_jid, profile)
|
||||
if has_feature:
|
||||
log.info(u"{name} method will be used to send the file".format(name=method_name))
|
||||
progress_id = yield defer.maybeDeferred(callback, peer_jid, filepath, filename, file_desc, profile)
|
||||
defer.returnValue({'progress': progress_id})
|
||||
msg = u"Can't find any method to send file to {jid}".format(jid=peer_jid.full())
|
||||
log.warning(msg)
|
||||
defer.returnValue({'xmlui': xml_tools.note(u"Can't transfer file", msg, C.XMLUI_DATA_LVL_WARNING).toXml()})
|
||||
|
||||
def register(self, namespace, callback, priority=0, method_name=None):
|
||||
"""Register a fileSending method
|
||||
|
||||
@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
|
||||
"""
|
||||
for data in self._file_callbacks:
|
||||
if namespace == data[0]:
|
||||
raise exceptions.ConflictError(u'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)
|
||||
|
||||
def unregister(self, namespace):
|
||||
for idx, data in enumerate(self._file_callbacks):
|
||||
if data[0] == namespace:
|
||||
del [idx]
|
||||
return
|
||||
raise exceptions.NotFound(u"The namespace to unregister doesn't exist")
|
||||
|
||||
# Dialogs with user
|
||||
# the overwrite check is done here
|
||||
|
|
@ -110,6 +167,7 @@ class FilePlugin(object):
|
|||
self.host,
|
||||
file_path,
|
||||
'w',
|
||||
uid=file_data[PROGRESS_ID_KEY],
|
||||
size=file_data['size'],
|
||||
profile=profile,
|
||||
)
|
||||
|
|
@ -167,6 +225,7 @@ class FilePlugin(object):
|
|||
- name (unicode): name of the file to trasnsfer
|
||||
the name must not be empty or contain a "/" character
|
||||
- size (int): size of the file
|
||||
- progress_id (unicode): id to use for progression
|
||||
It may content the key used in CONFIRM constant
|
||||
It *MUST NOT* contain the "peer" key
|
||||
"file_path" will be added to this dict once destination selected
|
||||
|
|
@ -176,6 +235,7 @@ class FilePlugin(object):
|
|||
"""
|
||||
filename = file_data['name']
|
||||
assert filename and not '/' in filename
|
||||
assert PROGRESS_ID_KEY in file_data
|
||||
# human readable size
|
||||
file_data['size_human'] = u'{:.6n} Mio'.format(float(file_data['size'])/(1024**2))
|
||||
d = xml_tools.deferDialog(self.host,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ PLUGIN_INFO = {
|
|||
|
||||
|
||||
class XEP_0096(object):
|
||||
# TODO: call self._f.unregister when unloading order will be managing (i.e. when depenencies will be unloaded at the end)
|
||||
|
||||
def __init__(self, host):
|
||||
log.info(_("Plugin XEP_0096 initialization"))
|
||||
|
|
@ -53,6 +54,7 @@ class XEP_0096(object):
|
|||
self.managed_stream_m = [self.host.plugins["XEP-0065"].NAMESPACE,
|
||||
self.host.plugins["XEP-0047"].NAMESPACE] # Stream methods managed
|
||||
self._f = self.host.plugins["FILE"]
|
||||
self._f.register(NS_SI_FT, self.sendFile, priority=0, method_name=u"Stream Initiation")
|
||||
self._si = self.host.plugins["XEP-0095"]
|
||||
self._si.registerSIProfile(SI_PROFILE_NAME, self._transferRequest)
|
||||
host.bridge.addMethod("siSendFile", ".plugin", in_sign='sssss', out_sign='s', method=self._sendFile)
|
||||
|
|
@ -167,7 +169,7 @@ class XEP_0096(object):
|
|||
#if we are here, the transfer can start, we just need user's agreement
|
||||
data = {"name": filename, "peer_jid": peer_jid, "size": file_size, "date": file_date, "hash": file_hash, "desc": file_desc,
|
||||
"range": range_, "range_offset": range_offset, "range_length": range_length,
|
||||
"si_id": si_id, "stream_method": stream_method, "stream_plugin": plugin}
|
||||
"si_id": si_id, "progress_id": si_id, "stream_method": stream_method, "stream_plugin": plugin}
|
||||
|
||||
d = self._f.getDestDir(peer_jid, data, data, profile)
|
||||
d.addCallback(self.confirmationCb, iq_elt, data, profile)
|
||||
|
|
@ -317,6 +319,7 @@ class XEP_0096(object):
|
|||
|
||||
file_obj = self._f.File(self.host,
|
||||
filepath,
|
||||
uid=sid,
|
||||
size=size,
|
||||
profile=client.profile
|
||||
)
|
||||
|
|
@ -333,7 +336,7 @@ class XEP_0096(object):
|
|||
self.host.bridge.newAlert(_("The contact {} has refused your file").format(from_s), _("File refused"), "INFO", client.profile)
|
||||
else:
|
||||
log.warning(_(u"Error during file transfer"))
|
||||
self.host.bridge.newAlert(_(u"Something went wrong during the file transfer session intialisation: {reason}").format(reason=unicode(stanza_err.value)), _("File transfer error"), "ERROR", client.profile)
|
||||
self.host.bridge.newAlert(_(u"Something went wrong during the file transfer session initialisation: {reason}").format(reason=unicode(stanza_err.value)), _("File transfer error"), "ERROR", client.profile)
|
||||
elif failure.check(exceptions.DataError):
|
||||
log.warning(u'Invalid stanza received')
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -275,6 +275,7 @@ class XEP_0166(object):
|
|||
- app_args(list): args to pass to the application plugin
|
||||
- app_kwargs(dict): keyword args to pass to the application plugin
|
||||
@param profile: %(doc_profile)s
|
||||
@return D(unicode): jingle session id
|
||||
"""
|
||||
assert contents # there must be at least one content
|
||||
client = self.host.getClient(profile)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ PLUGIN_INFO = {
|
|||
|
||||
class XEP_0234(object):
|
||||
# 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 depenencies will be unloaded at the end)
|
||||
|
||||
def __init__(self, host):
|
||||
log.info(_("plugin Jingle File Transfer initialization"))
|
||||
|
|
@ -56,27 +57,50 @@ 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=u"Jingle")
|
||||
host.bridge.addMethod("fileJingleSend", ".plugin", in_sign='sssss', out_sign='', method=self._fileJingleSend)
|
||||
|
||||
def getHandler(self, profile):
|
||||
return XEP_0234_handler()
|
||||
|
||||
def _getProgressId(self, session, content_name):
|
||||
"""Return a unique progress ID
|
||||
|
||||
@param session(dict): jingle session
|
||||
@param content_name(unicode): name of the content
|
||||
@return (unicode): unique progress id
|
||||
"""
|
||||
return u'{}_{}'.format(session['id'], content_name)
|
||||
|
||||
def _fileJingleSend(self, peer_jid, filepath, name="", file_desc="", profile=C.PROF_KEY_NONE):
|
||||
return self.fileJingleSend(jid.JID(peer_jid), filepath, name or None, file_desc or None, profile)
|
||||
|
||||
def fileJingleSend(self, peer_jid, filepath, name=None, file_desc=None, profile=C.PROF_KEY_NONE):
|
||||
def fileJingleSend(self, peer_jid, filepath, name, file_desc=None, profile=C.PROF_KEY_NONE):
|
||||
"""Send a file using jingle file transfer
|
||||
|
||||
@param peer_jid(jid.JID): destinee jid
|
||||
@param filepath(str): absolute path of the file
|
||||
@param name(unicode, None): name of the file
|
||||
@param file_desc(unicode, None): description of the file
|
||||
@param profile: %(doc_profile)s
|
||||
@return (D(unicode)): progress id
|
||||
"""
|
||||
progress_id_d = defer.Deferred()
|
||||
self._j.initiate(peer_jid,
|
||||
[{'app_ns': NS_JINGLE_FT,
|
||||
'senders': self._j.ROLE_INITIATOR,
|
||||
'app_kwargs': {'filepath': filepath,
|
||||
'name': name,
|
||||
'file_desc': file_desc},
|
||||
'file_desc': file_desc,
|
||||
'progress_id_d': progress_id_d},
|
||||
}],
|
||||
profile=profile)
|
||||
return progress_id_d
|
||||
|
||||
# jingle callbacks
|
||||
|
||||
def jingleSessionInit(self, session, content_name, filepath, name=None, file_desc=None, profile=C.PROF_KEY_NONE):
|
||||
def jingleSessionInit(self, session, content_name, filepath, name, file_desc, progress_id_d, profile=C.PROF_KEY_NONE):
|
||||
progress_id_d.callback(self.getProgressId(session, content_name))
|
||||
content_data = session['contents'][content_name]
|
||||
application_data = content_data['application_data']
|
||||
assert 'file_path' not in application_data
|
||||
|
|
@ -106,7 +130,7 @@ class XEP_0234(object):
|
|||
file_elt = desc_elt.elements(NS_JINGLE_FT, 'file').next()
|
||||
except StopIteration:
|
||||
raise failure.Failure(exceptions.DataError)
|
||||
file_data = {}
|
||||
file_data = {'progress_id': self._getProgressId(session, content_name)}
|
||||
for name in ('date', 'desc', 'media-type', 'name', 'range', 'size'):
|
||||
try:
|
||||
file_data[name] = unicode(file_elt.elements(NS_JINGLE_FT, name).next())
|
||||
|
|
@ -159,6 +183,7 @@ class XEP_0234(object):
|
|||
size = application_data['file_data']['size']
|
||||
content_data['file_obj'] = self._f.File(self.host,
|
||||
file_path,
|
||||
uid=self._getProgressId(session, content_name),
|
||||
size=size,
|
||||
profile=profile
|
||||
)
|
||||
|
|
@ -170,7 +195,7 @@ class XEP_0234(object):
|
|||
return desc_elt
|
||||
|
||||
def _finishedCb(self, dummy, session, content_name, content_data, profile):
|
||||
log.debug(u"File transfer completed successfuly")
|
||||
log.info(u"File transfer completed successfuly")
|
||||
if content_data['senders'] != session['role']:
|
||||
# we terminate the session only if we are the received,
|
||||
# as recommanded in XEP-0234 §2 (after example 6)
|
||||
|
|
|
|||
Loading…
Reference in a new issue