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`
This commit is contained in:
Goffi 2020-08-13 23:46:18 +02:00
commit 88174c2100
4 changed files with 75 additions and 61 deletions

View file

@ -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 @@ class XEP_0166(object):
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 @@ class XEP_0166(object):
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 @@ class XEP_0166(object):
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,

View file

@ -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/>.
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
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 functools import partial
import base64
import time
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__)
PLUGIN_INFO = {
@ -90,7 +91,7 @@ class XEP_0231(object):
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

View file

@ -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 @@ class XEP_0234(object):
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 @@ class XEP_0234(object):
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 @@ class XEP_0234(object):
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 @@ class XEP_0234(object):
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 @@ class XEP_0234(object):
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):

View file

@ -44,7 +44,7 @@ PLUGIN_INFO = {
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 @@ class XEP_0329(object):
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 @@ class XEP_0329(object):
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 @@ class XEP_0329(object):
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 @@ class XEP_0329(object):
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)