# HG changeset patch # User Goffi # Date 1597355178 -7200 # Node ID ac9342f359e9b176130c00bb38c942c1193855c4 # Parent 1512cbd6c4ac690ed36591ebe1419eb704b72dd7 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` diff -r 1512cbd6c4ac -r ac9342f359e9 sat/plugins/plugin_xep_0166.py --- 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 . + +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, diff -r 1512cbd6c4ac -r ac9342f359e9 sat/plugins/plugin_xep_0231.py --- 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 . +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 diff -r 1512cbd6c4ac -r ac9342f359e9 sat/plugins/plugin_xep_0234.py --- 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 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): diff -r 1512cbd6c4ac -r ac9342f359e9 sat/plugins/plugin_xep_0329.py --- 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)