changeset 1626:63cef4dbf2a4

core, plugins file, XEP-0234, bridge: progression api enhancement: - progressStarted have a new metadata parameter, useful to know the kind of progression, direction, etc. Check bridge doc - progressGetAllMetadata can be used to retrieve this data and discover on currently running progressions - progressFinished also have a new metadata parameter, used to e.g. indicate that hash is checked - core: fixed progressGetAll - file, XEP-0234: implemented the API modifications, hash is returned on progressFinished - file: SatFile.checkSize allows to check size independently of close (be sure that all the data have been transfered though)
author Goffi <goffi@goffi.org>
date Thu, 19 Nov 2015 18:13:26 +0100
parents 8b8b1af5905f
children 5a641e7b858a
files frontends/src/bridge/DBus.py src/bridge/DBus.py src/bridge/bridge_constructor/bridge_template.ini src/core/sat_main.py src/plugins/plugin_misc_file.py src/plugins/plugin_xep_0234.py
diffstat 6 files changed, 162 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/frontends/src/bridge/DBus.py	Thu Nov 19 11:15:06 2015 +0100
+++ b/frontends/src/bridge/DBus.py	Thu Nov 19 18:13:26 2015 +0100
@@ -622,6 +622,20 @@
             kwargs['error_handler'] = error_handler
         return self.db_core_iface.progressGetAll(profile, **kwargs)
 
+    def progressGetAllMetadata(self, profile, callback=None, errback=None):
+        if callback is None:
+            error_handler = None
+        else:
+            if errback is None:
+                errback = log.error
+            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
+        kwargs={}
+        if callback is not None:
+            kwargs['timeout'] = const_TIMEOUT
+            kwargs['reply_handler'] = callback
+            kwargs['error_handler'] = error_handler
+        return self.db_core_iface.progressGetAllMetadata(profile, **kwargs)
+
     def saveParamsTemplate(self, filename, callback=None, errback=None):
         if callback is None:
             error_handler = None
--- a/src/bridge/DBus.py	Thu Nov 19 11:15:06 2015 +0100
+++ b/src/bridge/DBus.py	Thu Nov 19 18:13:26 2015 +0100
@@ -196,13 +196,13 @@
         pass
 
     @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
-                         signature='ss')
-    def progressFinished(self, id, profile):
+                         signature='sa{ss}s')
+    def progressFinished(self, id, metadata, profile):
         pass
 
     @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
-                         signature='ss')
-    def progressStarted(self, id, profile):
+                         signature='sa{ss}s')
+    def progressStarted(self, id, metadata, profile):
         pass
 
     @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
@@ -459,6 +459,12 @@
         return self._callback("progressGetAll", unicode(profile))
 
     @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
+                         in_signature='s', out_signature='a{sa{sa{ss}}}',
+                         async_callbacks=None)
+    def progressGetAllMetadata(self, profile):
+        return self._callback("progressGetAllMetadata", unicode(profile))
+
+    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
                          in_signature='s', out_signature='b',
                          async_callbacks=None)
     def saveParamsTemplate(self, filename):
@@ -638,11 +644,11 @@
     def progressError(self, error, profile, arg_2):
         self.dbus_bridge.progressError(error, profile, arg_2)
 
-    def progressFinished(self, id, profile):
-        self.dbus_bridge.progressFinished(id, profile)
+    def progressFinished(self, id, metadata, profile):
+        self.dbus_bridge.progressFinished(id, metadata, profile)
 
-    def progressStarted(self, id, profile):
-        self.dbus_bridge.progressStarted(id, profile)
+    def progressStarted(self, id, metadata, profile):
+        self.dbus_bridge.progressStarted(id, metadata, profile)
 
     def subscribe(self, sub_type, entity_jid, profile):
         self.dbus_bridge.subscribe(sub_type, entity_jid, profile)
--- a/src/bridge/bridge_constructor/bridge_template.ini	Thu Nov 19 11:15:06 2015 +0100
+++ b/src/bridge/bridge_constructor/bridge_template.ini	Thu Nov 19 18:13:26 2015 +0100
@@ -170,18 +170,28 @@
 [progressStarted]
 type=signal
 category=core
-sig_in=ss
+sig_in=sa{ss}s
 doc=A progressing operation has just started
 doc_param_0=id: id of the progression operation
-doc_param_1=%(doc_profile)s
+doc_param_1=metadata: dict of progress metadata, key can be:
+    - name: name of the progression, full path for a file
+    - direction: "in" for incoming data, "out" else
+    - type: type of the progression:
+        C.META_TYPE_FILE: file transfer
+doc_param_2=%(doc_profile)s
 
 [progressFinished]
 type=signal
 category=core
-sig_in=ss
+sig_in=sa{ss}s
 doc=A progressing operation is finished
 doc_param_0=id: id of the progression operation
-doc_param_1=%(doc_profile)s
+doc_param_1=metadata: dict of progress status metadata, key can be:
+    - hash: value of the computed hash
+    - hash_algo: alrorithm used to compute hash
+    - hash_verified: C.BOOL_TRUE if hash is verified and OK
+        C.BOOL_FALSE if hash was not received ([progressError] will be used if there is a mismatch)
+doc_param_2=%(doc_profile)s
 
 [progressError]
 type=signal
@@ -666,6 +676,17 @@
  - position: current position
  - size: end position
 
+[progressGetAllMetadata]
+type=method
+category=core
+sig_in=s
+sig_out=a{sa{sa{ss}}}
+doc=Get all active progress informations
+doc_param_0=%(doc_profile)s or C.PROF_KEY_ALL for all profiles
+doc_return= a dict which map profile to progress_dict
+    progress_dict map progress_id to progress_metadata
+    progress_metadata is the same dict as sent by [progressStarted]
+
 [progressGetAll]
 type=method
 category=core
--- a/src/core/sat_main.py	Thu Nov 19 11:15:06 2015 +0100
+++ b/src/core/sat_main.py	Thu Nov 19 18:13:26 2015 +0100
@@ -824,12 +824,14 @@
         client = self.getClient(profile)
         return [action_tuple[:-1] for action_tuple in client.actions.itervalues()]
 
-    def registerProgressCb(self, progress_id, callback, profile):
+    def registerProgressCb(self, progress_id, callback, metadata=None, profile=C.PROF_KEY_NONE):
         """Register a callback called when progress is requested for id"""
+        if metadata is None:
+            metadata = {}
         client = self.getClient(profile)
         if progress_id in client._progress_cb:
             raise exceptions.ConflictError(u"Progress ID is not unique !")
-        client._progress_cb[progress_id] = callback
+        client._progress_cb[progress_id] = (callback, metadata)
 
     def removeProgressCb(self, progress_id, profile):
         """Remove a progress callback"""
@@ -855,7 +857,7 @@
         """
         client = self.getClient(profile)
         try:
-            data = client._progress_cb[progress_id](progress_id, profile)
+            data = client._progress_cb[progress_id][0](progress_id, profile)
         except KeyError:
             data = {}
         return data
@@ -868,11 +870,30 @@
                     data[key] = unicode(value)
         return progress_all
 
-    def progressGetAll(self, profile_key):
-        """Return all progress informations
+    def progressGetAllMetadata(self, profile_key):
+        """Return all progress metadata at once
 
-        @param profile_key: %(doc_profile)s get all progress from this profile
-            if C.PROF_KEY_ALL is used, all progress from all profiles are returned
+        @param profile_key: %(doc_profile)s
+            if C.PROF_KEY_ALL is used, all progress metadata from all profiles are returned
+        @return (dict[dict[dict]]): a dict which map profile to progress_dict
+            progress_dict map progress_id to progress_data
+            progress_metadata is the same dict as sent by [progressStarted]
+        """
+        clients = self.getClients(profile_key)
+        progress_all = {}
+        for client in clients:
+            profile = client.profile
+            progress_dict = {}
+            progress_all[profile] = progress_dict
+            for progress_id, (dummy, progress_metadata) in client._progress_cb.iteritems():
+                progress_dict[progress_id] = progress_metadata
+        return progress_all
+
+    def progressGetAll(self, profile_key):
+        """Return all progress status at once
+
+        @param profile_key: %(doc_profile)s
+            if C.PROF_KEY_ALL is used, all progress status from all profiles are returned
         @return (dict[dict[dict]]): a dict which map profile to progress_dict
             progress_dict map progress_id to progress_data
             progress_data is the same dict as returned by [progressGet]
@@ -883,10 +904,8 @@
             profile = client.profile
             progress_dict = {}
             progress_all[profile] = progress_dict
-            for progress_id, progress_cb in client._progress_cb.iteritems():
-                data = {}
-                progress_dict[progress_id] = data
-                progress_dict[progress_id] = progress_cb(progress_id, data, profile)
+            for progress_id, (progress_cb, dummy) in client._progress_cb.iteritems():
+                progress_dict[progress_id] = progress_cb(progress_id, profile)
         return progress_all
 
     def registerCallback(self, callback, *args, **kwargs):
--- a/src/plugins/plugin_misc_file.py	Thu Nov 19 11:15:06 2015 +0100
+++ b/src/plugins/plugin_misc_file.py	Thu Nov 19 18:13:26 2015 +0100
@@ -71,16 +71,52 @@
         self.size = size
         self.data_cb = data_cb
         self.profile = profile
-        self.host.registerProgressCb(self.uid, self.getProgress, profile)
-        self.host.bridge.progressStarted(self.uid, self.profile)
+        metadata = self.getProgressMetadata()
+        self.host.registerProgressCb(self.uid, self.getProgress, metadata, profile=profile)
+        self.host.bridge.progressStarted(self.uid, metadata, self.profile)
 
-    def close(self):
+    def checkSize(self):
+        """Check that current size correspond to given size
+
+        must be used when the transfer is supposed to be finished
+        @return (bool): True if the position is the same as given size
+        @raise exceptions.NotFound: size has not be specified
+        """
         position = self._file.tell()
+        if self.size is None:
+            raise exceptions.NotFound
+        return position == self.size
+
+
+    def close(self, progress_metadata=None, error=None):
+        """Close the current file
+
+        @param progress_metadata(None, dict): metadata to send with _onProgressFinished message
+        @param error(None, unicode): set to an error message if progress was not successful
+            mutually exclusive with progress_metadata
+            error can happen even if error is None, if current size differ from given size
+        """
+        if error is None:
+            try:
+                size_ok = self.checkSize()
+            except exceptions.NotFound:
+                size_ok = True
+            finally:
+                if not size_ok:
+                    error = u'declared size and actual mismatch'
+                    log.warning(u"successful close was requested, but there is a size mismatch")
+                    progress_metadata = None
+
         self._file.close()
-        if not self.size or self.size == position:
-            self.host.bridge.progressFinished(self.uid, self.profile)
+
+        if error is None:
+            if progress_metadata is None:
+                progress_metadata = {}
+            self.host.bridge.progressFinished(self.uid, progress_metadata, self.profile)
         else:
-            self.host.bridge.progressError(self.uid, u"size doesn't match", self.profile)
+            assert progress_metadata is None
+            self.host.bridge.progressError(self.uid, error, self.profile)
+
         self.host.removeProgressCb(self.uid, self.profile)
 
     def flush(self):
@@ -103,6 +139,32 @@
     def tell(self):
         return self._file.tell()
 
+    def mode(self):
+        return self._file.mode()
+
+    def getProgressMetadata(self):
+        """Return progression metadata as given to progressStarted
+
+        @return (dict): metadata (check bridge for documentation)
+        """
+        metadata = {'type': C.META_TYPE_FILE}
+
+        mode = self._file.mode
+        if '+' in mode:
+            pass # we have no direction in read/write modes
+        elif mode in ('r', 'rb'):
+            metadata['direction'] = 'out'
+        elif mode in ('w', 'wb'):
+            metadata['direction'] = 'in'
+        elif 'U' in mode:
+            metadata['direction'] = 'out'
+        else:
+            raise exceptions.InternalError
+
+        metadata['name'] = self._file.name
+
+        return metadata
+
     def getProgress(self, progress_id, profile):
         ret = {'position': self._file.tell()}
         if self.size:
--- a/src/plugins/plugin_xep_0234.py	Thu Nov 19 11:15:06 2015 +0100
+++ b/src/plugins/plugin_xep_0234.py	Thu Nov 19 18:13:26 2015 +0100
@@ -283,11 +283,21 @@
 
         if hash_ == hash_given:
             log.info(u"Hash checked, file was successfully transfered: {}".format(hash_))
+            progress_metadata = {'hash': hash_,
+                                 'hash_algo': file_data['hash_algo'],
+                                 'hash_verified': C.BOOL_TRUE
+                                }
+            error = None
         else:
             log.warning(u"Hash mismatch, the file was not transfered correctly")
+            progress_metadata=None
+            error = u"Hash mismatch: given={algo}:{given}, calculated={algo}:{our}".format(
+                algo = file_data['hash_algo'],
+                given = hash_given,
+                our = hash_)
 
         self._j.delayedContentTerminate(session, content_name, profile=profile)
-        content_data['file_obj'].close()
+        content_data['file_obj'].close(progress_metadata, error)
         # we may have the last_try timer still active, so we try to cancel it
         try:
             content_data['last_try_timer'].cancel()