diff src/plugins/plugin_misc_file.py @ 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
parents 833bdb227b16
children b144babc2658
line wrap: on
line diff
--- 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,