changeset 3333:ac9342f359e9

plugin XEP-0329: download thumbnails: - thumbnails are now downloaded directly when BoB is used, `filename` is then filled - `utils.asDeferred` is now used for Jingle methods, so `async` coroutines can be used - (XEP-0231): fixed `dumpData`
author Goffi <goffi@goffi.org>
date Thu, 13 Aug 2020 23:46:18 +0200
parents 1512cbd6c4ac
children 2cd54c72fae4
files sat/plugins/plugin_xep_0166.py sat/plugins/plugin_xep_0231.py sat/plugins/plugin_xep_0234.py sat/plugins/plugin_xep_0329.py
diffstat 4 files changed, 77 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/sat/plugins/plugin_xep_0166.py	Thu Aug 13 23:46:18 2020 +0200
+++ b/sat/plugins/plugin_xep_0166.py	Thu Aug 13 23:46:18 2020 +0200
@@ -1,7 +1,6 @@
 #!/usr/bin/env python3
 
-
-# SAT plugin for Jingle (XEP-0166)
+# SàT plugin for Jingle (XEP-0166)
 # Copyright (C) 2009-2020 Jérôme Poisson (goffi@goffi.org)
 
 # This program is free software: you can redistribute it and/or modify
@@ -17,25 +16,27 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+
+import uuid
+import time
+from collections import namedtuple
+from zope.interface import implementer
+from twisted.words.protocols.jabber import jid
+from twisted.internet import defer
+from twisted.internet import reactor
+from twisted.words.protocols.jabber import error
+from twisted.words.protocols.jabber import xmlstream
+from twisted.python import failure
+from wokkel import disco, iwokkel
+from sat.core import exceptions
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
 from sat.tools import xml_tools
+from sat.tools import utils
+
 
 log = getLogger(__name__)
-from sat.core import exceptions
-from twisted.words.protocols.jabber import jid
-from twisted.internet import defer
-from twisted.internet import reactor
-from wokkel import disco, iwokkel
-from twisted.words.protocols.jabber import error
-from twisted.words.protocols.jabber import xmlstream
-from twisted.python import failure
-from collections import namedtuple
-import uuid
-import time
-
-from zope.interface import implementer
 
 
 IQ_SET = '/iq[@type="set"]'
@@ -757,7 +758,7 @@
                     elt = content_data.pop(elt_name) if delete else content_data[elt_name]
                 else:
                     elt = force_element
-                d = defer.maybeDeferred(
+                d = utils.asDeferred(
                     method, client, action, session, content_name, elt
                 )
                 defers_list.append(d)
@@ -846,7 +847,7 @@
             application = content_data["application"]
             app_session_accept_cb = application.handler.jingleHandler
 
-            app_d = defer.maybeDeferred(
+            app_d = utils.asDeferred(
                 app_session_accept_cb,
                 client,
                 XEP_0166.A_SESSION_INITIATE,
@@ -860,7 +861,7 @@
             transport = content_data["transport"]
             transport_session_accept_cb = transport.handler.jingleHandler
 
-            transport_d = defer.maybeDeferred(
+            transport_d = utils.asDeferred(
                 transport_session_accept_cb,
                 client,
                 XEP_0166.A_SESSION_INITIATE,
--- a/sat/plugins/plugin_xep_0231.py	Thu Aug 13 23:46:18 2020 +0200
+++ b/sat/plugins/plugin_xep_0231.py	Thu Aug 13 23:46:18 2020 +0200
@@ -17,23 +17,24 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import base64
+import time
+from pathlib import Path
+from functools import partial
+from zope.interface import implementer
+from twisted.python import failure
+from twisted.words.protocols.jabber import xmlstream
+from twisted.words.protocols.jabber import jid
+from twisted.words.protocols.jabber import error as jabber_error
+from twisted.internet import defer
+from wokkel import disco, iwokkel
+from sat.tools import xml_tools
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core import exceptions
 from sat.core.log import getLogger
 
 log = getLogger(__name__)
-from sat.tools import xml_tools
-from wokkel import disco, iwokkel
-from zope.interface import implementer
-from twisted.python import failure
-from twisted.words.protocols.jabber import xmlstream
-from twisted.words.protocols.jabber import jid
-from twisted.words.protocols.jabber import error as jabber_error
-from twisted.internet import defer
-from functools import partial
-import base64
-import time
 
 
 PLUGIN_INFO = {
@@ -90,7 +91,7 @@
             PLUGIN_INFO[C.PI_IMPORT_NAME], cid, data_elt.getAttribute("type"), max_age
         ) as f:
 
-            file_path = f.name
+            file_path = Path(f.name)
             f.write(base64.b64decode(str(data_elt)))
 
         return file_path
--- a/sat/plugins/plugin_xep_0234.py	Thu Aug 13 23:46:18 2020 +0200
+++ b/sat/plugins/plugin_xep_0234.py	Thu Aug 13 23:46:18 2020 +0200
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 
-# SAT plugin for Jingle File Transfer (XEP-0234)
+# SàT plugin for Jingle File Transfer (XEP-0234)
 # Copyright (C) 2009-2020 Jérôme Poisson (goffi@goffi.org)
 
 # This program is free software: you can redistribute it and/or modify
@@ -181,7 +181,7 @@
             file_data.update(kwargs)
         return self.buildFileElement(client, **file_data)
 
-    def parseFileElement(
+    async def parseFileElement(
             self, client, file_elt, file_data=None, given=False, parent_elt=None,
             keep_empty_range=False):
         """Parse a <file> element and file dictionary accordingly
@@ -451,7 +451,9 @@
 
         return desc_elt
 
-    def jingleRequestConfirmation(self, client, action, session, content_name, desc_elt):
+    async def jingleRequestConfirmation(
+        self, client, action, session, content_name, desc_elt
+    ):
         """This method request confirmation for a jingle session"""
         content_data = session["contents"][content_name]
         senders = content_data["senders"]
@@ -467,21 +469,20 @@
 
         if senders == self._j.ROLE_RESPONDER:
             # we send the file
-            return self._fileSendingRequestConf(
+            return await self._fileSendingRequestConf(
                 client, session, content_data, content_name, file_data, file_elt
             )
         else:
             # we receive the file
-            return self._fileReceivingRequestConf(
+            return await self._fileReceivingRequestConf(
                 client, session, content_data, content_name, file_data, file_elt
             )
 
-    @defer.inlineCallbacks
-    def _fileSendingRequestConf(
+    async def _fileSendingRequestConf(
         self, client, session, content_data, content_name, file_data, file_elt
     ):
         """parse file_elt, and handle file retrieving/permission checking"""
-        self.parseFileElement(client, file_elt, file_data)
+        await self.parseFileElement(client, file_elt, file_data)
         content_data["application_data"]["file_data"] = file_data
         finished_d = content_data["finished_d"] = defer.Deferred()
 
@@ -496,22 +497,22 @@
             file_elt,
         )
         if not cont:
-            confirmed = yield confirmed_d
+            confirmed = await confirmed_d
             if confirmed:
                 args = [client, session, content_name, content_data]
                 finished_d.addCallbacks(
                     self._finishedCb, self._finishedEb, args, None, args
                 )
-            defer.returnValue(confirmed)
+            return confirmed
 
         log.warning(_("File continue is not implemented yet"))
-        defer.returnValue(False)
+        return False
 
-    def _fileReceivingRequestConf(
+    async def _fileReceivingRequestConf(
         self, client, session, content_data, content_name, file_data, file_elt
     ):
         """parse file_elt, and handle user permission/file opening"""
-        self.parseFileElement(client, file_elt, file_data, given=True)
+        await self.parseFileElement(client, file_elt, file_data, given=True)
         try:
             hash_algo, file_data["given_file_hash"] = self._hash.parseHashElt(file_elt)
         except exceptions.NotFound:
@@ -540,21 +541,18 @@
         content_data["application_data"]["file_data"] = file_data
 
         # now we actualy request permission to user
-        def gotConfirmation(confirmed):
-            if confirmed:
-                args = [client, session, content_name, content_data]
-                finished_d.addCallbacks(
-                    self._finishedCb, self._finishedEb, args, None, args
-                )
-            return confirmed
 
         # deferred to track end of transfer
         finished_d = content_data["finished_d"] = defer.Deferred()
-        d = self._f.getDestDir(
+        confirmed = await self._f.getDestDir(
             client, session["peer_jid"], content_data, file_data, stream_object=True
         )
-        d.addCallback(gotConfirmation)
-        return d
+        if confirmed:
+            args = [client, session, content_name, content_data]
+            finished_d.addCallbacks(
+                self._finishedCb, self._finishedEb, args, None, args
+            )
+        return confirmed
 
     @defer.inlineCallbacks
     def jingleHandler(self, client, action, session, content_name, desc_elt):
--- a/sat/plugins/plugin_xep_0329.py	Thu Aug 13 23:46:18 2020 +0200
+++ b/sat/plugins/plugin_xep_0329.py	Thu Aug 13 23:46:18 2020 +0200
@@ -44,7 +44,7 @@
     C.PI_TYPE: "XEP",
     C.PI_MODES: C.PLUG_MODE_BOTH,
     C.PI_PROTOCOLS: ["XEP-0329"],
-    C.PI_DEPENDENCIES: ["XEP-0234", "XEP-0300", "XEP-0106"],
+    C.PI_DEPENDENCIES: ["XEP-0231", "XEP-0234", "XEP-0300", "XEP-0106"],
     C.PI_MAIN: "XEP_0329",
     C.PI_HANDLER: "yes",
     C.PI_DESCRIPTION: _("""Implementation of File Information Sharing"""),
@@ -272,6 +272,7 @@
         log.info(_("File Information Sharing initialization"))
         self.host = host
         ShareNode.host = host
+        self._b = host.plugins["XEP-0231"]
         self._h = host.plugins["XEP-0300"]
         self._jf = host.plugins["XEP-0234"]
         host.bridge.addMethod(
@@ -672,7 +673,7 @@
             client, iq_elt, self._compGetRootNodesCb, self._compGetFilesFromNodeCb
         )
 
-    def _parseResult(self, iq_elt, client):
+    async def _parseResult(self, client, peer_jid, iq_elt):
         query_elt = next(iq_elt.elements(NS_FIS, "query"))
         files = []
 
@@ -680,10 +681,24 @@
             if elt.name == "file":
                 # we have a file
                 try:
-                    file_data = self._jf.parseFileElement(client, elt)
+                    file_data = await self._jf.parseFileElement(client, elt)
                 except exceptions.DataError:
                     continue
                 file_data["type"] = C.FILE_TYPE_FILE
+                try:
+                    thumbs = file_data['extra'][C.KEY_THUMBNAILS]
+                except KeyError:
+                    log.debug(f"No thumbnail found for {file_data}")
+                else:
+                    for thumb in thumbs:
+                        if 'url' not in thumb and "id" in thumb:
+                            try:
+                                file_path = await self._b.getFile(client, peer_jid, thumb['id'])
+                            except Exception as e:
+                                log.warning(f"Can't get thumbnail {thumb['id']!r} for {file_data}: {e}")
+                            else:
+                                thumb['filename'] = file_path.name
+
             elif elt.name == "directory" and elt.uri == NS_FIS:
                 # we have a directory
 
@@ -1018,27 +1033,26 @@
     def _listFiles(self, target_jid, path, extra, profile):
         client = self.host.getClient(profile)
         target_jid = client.jid if not target_jid else jid.JID(target_jid)
-        d = self.listFiles(client, target_jid, path or None)
+        d = defer.ensureDeferred(self.listFiles(client, target_jid, path or None))
         d.addCallback(self._serializeData)
         return d
 
-    def listFiles(self, client, target_jid, path=None, extra=None):
+    async def listFiles(self, client, peer_jid, path=None, extra=None):
         """List file shared by an entity
 
-        @param target_jid(jid.JID): jid of the sharing entity
+        @param peer_jid(jid.JID): jid of the sharing entity
         @param path(unicode, None): path to the directory containing shared files
             None to get root directories
         @param extra(dict, None): extra data
         @return list(dict): shared files
         """
         iq_elt = client.IQ("get")
-        iq_elt["to"] = target_jid.full()
+        iq_elt["to"] = peer_jid.full()
         query_elt = iq_elt.addElement((NS_FIS, "query"))
         if path:
             query_elt["node"] = path
-        d = iq_elt.send()
-        d.addCallback(self._parseResult, client)
-        return d
+        iq_result_elt = await iq_elt.send()
+        return await self._parseResult(client, peer_jid, iq_result_elt)
 
     def _localSharesGet(self, profile):
         client = self.host.getClient(profile)