diff sat/plugins/plugin_xep_0234.py @ 3040:fee60f17ebac

jp: jp asyncio port: /!\ this commit is huge. Jp is temporarily not working with `dbus` bridge /!\ This patch implements the port of jp to asyncio, so it is now correctly using the bridge asynchronously, and it can be used with bridges like `pb`. This also simplify the code, notably for things which were previously implemented with many callbacks (like pagination with RSM). During the process, some behaviours have been modified/fixed, in jp and backends, check diff for details.
author Goffi <goffi@goffi.org>
date Wed, 25 Sep 2019 08:56:41 +0200
parents ab2696e34d29
children 9d0df638c8b4
line wrap: on
line diff
--- a/sat/plugins/plugin_xep_0234.py	Wed Sep 25 08:53:38 2019 +0200
+++ b/sat/plugins/plugin_xep_0234.py	Wed Sep 25 08:56:41 2019 +0200
@@ -55,7 +55,7 @@
     C.PI_DESCRIPTION: _("""Implementation of Jingle File Transfer"""),
 }
 
-EXTRA_ALLOWED = {"path", "namespace", "file_desc", "file_hash"}
+EXTRA_ALLOWED = {"path", "namespace", "file_desc", "file_hash", "hash_algo"}
 Range = namedtuple("Range", ("offset", "length"))
 
 
@@ -106,7 +106,7 @@
 
     # generic methods
 
-    def buildFileElement(self, name, file_hash=None, hash_algo=None, size=None,
+    def buildFileElement(self, name=None, file_hash=None, hash_algo=None, size=None,
         mime_type=None, desc=None, modified=None, transfer_range=None, path=None,
         namespace=None, file_elt=None, **kwargs):
         """Generate a <file> element with available metadata
@@ -177,14 +177,8 @@
             file_data.update(kwargs)
         return self.buildFileElement(**file_data)
 
-    def parseFileElement(
-        self,
-        file_elt,
-        file_data=None,
-        given=False,
-        parent_elt=None,
-        keep_empty_range=False,
-    ):
+    def parseFileElement(self, file_elt, file_data=None, given=False, parent_elt=None,
+                         keep_empty_range=False,):
         """Parse a <file> element and file dictionary accordingly
 
         @param file_data(dict, None): dict where the data will be set
@@ -194,10 +188,12 @@
         @param given(bool): if True, prefix hash key with "given_"
         @param parent_elt(domish.Element, None): parent of the file element
             if set, file_elt must not be set
-        @param keep_empty_range(bool): if True, keep empty range (i.e. range when offset and length are None)
-            empty range are useful to know if a peer_jid can handle range
+        @param keep_empty_range(bool): if True, keep empty range (i.e. range when offset
+            and length are None).
+            Empty range is useful to know if a peer_jid can handle range
         @return (dict): file_data
-        @trigger XEP-0234_parseFileElement(file_elt, file_data): can be used to parse new elements
+        @trigger XEP-0234_parseFileElement(file_elt, file_data): can be used to parse new
+            elements
         @raise exceptions.NotFound: there is not <file> element in parent_elt
         @raise exceptions.DataError: if file_elt uri is not NS_JINGLE_FT
         """
@@ -230,7 +226,7 @@
             # we don't want to go to parent dir when joining to a path
             name = "--"
             file_data["name"] = name
-        elif name is not None and "/" in name or "\\" in name:
+        elif name is not None and ("/" in name or "\\" in name):
             file_data["name"] = regex.pathEscape(name)
 
         try:
@@ -335,15 +331,8 @@
         defer.returnValue(progress_id)
 
     def _fileJingleRequest(
-        self,
-        peer_jid,
-        filepath,
-        name="",
-        file_hash="",
-        hash_algo="",
-        extra=None,
-        profile=C.PROF_KEY_NONE,
-    ):
+            self, peer_jid, filepath, name="", file_hash="", hash_algo="", extra=None,
+            profile=C.PROF_KEY_NONE):
         client = self.host.getClient(profile)
         return self.fileJingleRequest(
             client,
@@ -357,15 +346,8 @@
 
     @defer.inlineCallbacks
     def fileJingleRequest(
-        self,
-        client,
-        peer_jid,
-        filepath,
-        name=None,
-        file_hash=None,
-        hash_algo=None,
-        extra=None,
-    ):
+            self, client, peer_jid, filepath, name=None, file_hash=None, hash_algo=None,
+            extra=None):
         """Request a file using jingle file transfer
 
         @param peer_jid(jid.JID): destinee jid
@@ -568,6 +550,7 @@
         d.addCallback(gotConfirmation)
         return d
 
+    @defer.inlineCallbacks
     def jingleHandler(self, client, action, session, content_name, desc_elt):
         content_data = session["contents"][content_name]
         application_data = content_data["application_data"]
@@ -579,7 +562,7 @@
                 next(file_elt.elements(NS_JINGLE_FT, "range"))
             except StopIteration:
                 # initiator doesn't manage <range>, but we do so we advertise it
-                #  FIXME: to be checked
+                # FIXME: to be checked
                 log.debug("adding <range> element")
                 file_elt.addElement("range")
         elif action == self._j.A_SESSION_ACCEPT:
@@ -596,21 +579,32 @@
                     size = int(str(size_elt))
                 except (StopIteration, ValueError):
                     size = None
-                # XXX: hash security is not critical here, so we just take the higher mandatory one
+                # XXX: hash security is not critical here, so we just take the higher
+                #      mandatory one
                 hasher = file_data["hash_hasher"] = self._hash.getHasher()
-                content_data["stream_object"] = stream.FileStreamObject(
-                    self.host,
-                    client,
-                    file_path,
-                    mode="wb",
-                    uid=self.getProgressId(session, content_name),
-                    size=size,
-                    data_cb=lambda data: hasher.update(data),
-                )
+                progress_id = self.getProgressId(session, content_name)
+                try:
+                    content_data["stream_object"] = stream.FileStreamObject(
+                        self.host,
+                        client,
+                        file_path,
+                        mode="wb",
+                        uid=progress_id,
+                        size=size,
+                        data_cb=lambda data: hasher.update(data),
+                    )
+                except Exception as e:
+                    self.host.bridge.progressError(
+                        progress_id, C.PROGRESS_ERROR_FAILED, client.profile
+                    )
+                    yield self._j.terminate(
+                        client, self._j.REASON_FAILED_APPLICATION, session)
+                    raise e
             else:
                 # we are sending the file
                 size = file_data["size"]
-                # XXX: hash security is not critical here, so we just take the higher mandatory one
+                # XXX: hash security is not critical here, so we just take the higher
+                #      mandatory one
                 hasher = file_data["hash_hasher"] = self._hash.getHasher()
                 content_data["stream_object"] = stream.FileStreamObject(
                     self.host,
@@ -625,7 +619,7 @@
             finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args)
         else:
             log.warning("FIXME: unmanaged action {}".format(action))
-        return desc_elt
+        defer.returnValue(desc_elt)
 
     def jingleSessionInfo(self, client, action, session, content_name, jingle_elt):
         """Called on session-info action
@@ -679,6 +673,16 @@
             self.host.bridge.progressError(
                 progress_id, C.PROGRESS_ERROR_DECLINED, client.profile
             )
+        elif not jingle_elt.success:
+            progress_id = self.getProgressId(session, content_name)
+            first_child = jingle_elt.firstChildElement()
+            if first_child is not None:
+                reason = first_child.name
+            else:
+                reason = C.PROGRESS_ERROR_FAILED
+            self.host.bridge.progressError(
+                progress_id, reason, client.profile
+            )
 
     def _sendCheckSum(self, client, session, content_name, content_data):
         """Send the session-info with the hash checksum"""
@@ -721,10 +725,10 @@
                 return True
             return False
         hasher = file_data["hash_hasher"]
-        hash_ = hasher.hexdigest().encode('utf-8')
+        hash_ = hasher.hexdigest()
 
         if hash_ == given_hash:
-            log.info("Hash checked, file was successfully transfered: {}".format(hash_))
+            log.info(f"Hash checked, file was successfully transfered: {hash_}")
             progress_metadata = {
                 "hash": hash_,
                 "hash_algo": file_data["hash_algo"],