Mercurial > libervia-backend
changeset 1585:846a39900fa6
plugins XEP-0096, XEP-0260, file: sendFile method is managed by file plugin, which choose the best available method + progress_id fix
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 14 Nov 2015 19:18:05 +0100 (2015-11-14) |
parents | b57b4683dc33 |
children | 42285d993e68 |
files | src/core/sat_main.py src/plugins/plugin_misc_file.py src/plugins/plugin_xep_0096.py src/plugins/plugin_xep_0166.py src/plugins/plugin_xep_0234.py |
diffstat | 5 files changed, 97 insertions(+), 8 deletions(-) [+] |
line wrap: on
line diff
--- a/src/core/sat_main.py Fri Nov 13 16:46:32 2015 +0100 +++ b/src/core/sat_main.py Sat Nov 14 19:18:05 2015 +0100 @@ -839,7 +839,7 @@ 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)
--- a/src/plugins/plugin_misc_file.py Fri Nov 13 16:46:32 2015 +0100 +++ b/src/plugins/plugin_misc_file.py Sat Nov 14 19:18:05 2015 +0100 @@ -21,9 +21,12 @@ 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_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 @@ @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 @@ 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 @@ self.host, file_path, 'w', + uid=file_data[PROGRESS_ID_KEY], size=file_data['size'], profile=profile, ) @@ -167,6 +225,7 @@ - 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 @@ """ 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,
--- a/src/plugins/plugin_xep_0096.py Fri Nov 13 16:46:32 2015 +0100 +++ b/src/plugins/plugin_xep_0096.py Sat Nov 14 19:18:05 2015 +0100 @@ -46,6 +46,7 @@ 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 @@ 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 @@ #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 @@ file_obj = self._f.File(self.host, filepath, + uid=sid, size=size, profile=client.profile ) @@ -333,7 +336,7 @@ 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:
--- a/src/plugins/plugin_xep_0166.py Fri Nov 13 16:46:32 2015 +0100 +++ b/src/plugins/plugin_xep_0166.py Sat Nov 14 19:18:05 2015 +0100 @@ -275,6 +275,7 @@ - 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)
--- a/src/plugins/plugin_xep_0234.py Fri Nov 13 16:46:32 2015 +0100 +++ b/src/plugins/plugin_xep_0234.py Sat Nov 14 19:18:05 2015 +0100 @@ -49,6 +49,7 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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)