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.
This commit is contained in:
Goffi 2019-09-25 08:56:41 +02:00
commit 76aadbb3fb
18 changed files with 3299 additions and 3300 deletions

View file

@ -236,7 +236,7 @@ class AdHocDBus(object):
flags=flags,
)
defer.returnValue((bus_name, methods))
defer.returnValue((str(bus_name), methods))
def _addCommand(self, client, adhoc_name, bus_name, methods, allowed_jids=None,
allowed_groups=None, allowed_magics=None, forbidden_jids=None,

View file

@ -381,7 +381,7 @@ class Events(object):
if register:
yield self.register(
client, service, None, {}, node, event_id, event_elt, creator=True)
client, None, {}, service, node, event_id, item_elt, creator=True)
defer.returnValue(node)
def _eventModify(self, service, node, id_, timestamp_update, data_update,
@ -447,24 +447,29 @@ class Events(object):
events.append((timestamp, data))
defer.returnValue(events)
def _eventInviteeGet(self, service, node, profile_key):
def _eventInviteeGet(self, service, node, invitee_jid_s, profile_key):
service = jid.JID(service) if service else None
node = node if node else NS_EVENT
client = self.host.getClient(profile_key)
return self.eventInviteeGet(client, service, node)
invitee_jid = jid.JID(invitee_jid_s) if invitee_jid_s else None
return self.eventInviteeGet(client, service, node, invitee_jid)
@defer.inlineCallbacks
def eventInviteeGet(self, client, service, node):
def eventInviteeGet(self, client, service, node, invitee_jid=None):
"""Retrieve attendance from event node
@param service(unicode, None): PubSub service
@param node(unicode): PubSub node of the event
@param node(unicode): PubSub node of the event's invitees
@param invitee_jid(jid.JId, None): jid of the invitee to retrieve (None to
retrieve profile's invitation). The bare jid correspond to the PubSub item id.
@return (dict): a dict with current attendance status,
an empty dict is returned if nothing has been answered yed
"""
if invitee_jid is None:
invitee_jid = client.jid
try:
items, metadata = yield self._p.getItems(
client, service, node, item_ids=[client.jid.userhost()]
client, service, node, item_ids=[invitee_jid.userhost()]
)
event_elt = next(items[0].elements(NS_EVENT, "invitee"))
except (exceptions.NotFound, IndexError):

View file

@ -18,11 +18,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from collections import Iterable
import copy
import itertools
from zope.interface import implementer
from twisted.words.protocols.jabber import jid
from twisted.words.protocols.jabber.xmlstream import XMPPHandler
from twisted.words.xish import domish
from twisted.internet import defer
from wokkel import disco, iwokkel
from wokkel import data_form
@ -174,7 +174,16 @@ class PubsubSchema(object):
)
elif isinstance(schema, data_form.Form):
if copy_form:
schema = copy.deepcopy(schema)
# XXX: we don't use deepcopy as it will do an infinite loop if a
# domish.Element is present in the form fields (happens for
# XEP-0315 data forms XML Element)
schema = data_form.Form(
formType = schema.formType,
title = schema.title,
instructions = schema.instructions[:],
formNamespace = schema.formNamespace,
fields = schema.fieldList,
)
defer.returnValue(schema)
try:
@ -394,6 +403,18 @@ class PubsubSchema(object):
if not values_list:
# if values don't map to allowed values, we use default ones
values_list = field.values
elif field.ext_type == 'xml':
# FIXME: XML elements are not handled correctly, we need to know if we
# actual XML/XHTML, or text to escape
for idx, value in enumerate(values_list[:]):
if not isinstance(value, domish.Element):
if field.value and field.value.uri == C.NS_XHTML:
div_elt = domish.Element((C.NS_XHTML, 'div'))
div_elt.addContent(str(value))
values_list[idx] = div_elt
else:
raise NotImplementedError
field.values = values_list
yield self._p.sendItem(
@ -546,9 +567,7 @@ class PubsubSchema(object):
form = data_form.findForm(item_elt, form_ns)
if form is None:
log.warning(
_(
"Can't parse previous item, update ignored: data form not found"
).format(reason=e)
_("Can't parse previous item, update ignored: data form not found")
)
else:
for name, field in form.fields.items():

View file

@ -64,7 +64,7 @@ class ImportPlugin(object):
@param name(unicode): import handler name
"""
assert name == name.lower().strip()
log.info(_("initializing {name} import handler").format(name=name))
log.info(_(f"initializing {name} import handler"))
import_handler.name = name
import_handler.register = partial(self.register, import_handler)
import_handler.unregister = partial(self.unregister, import_handler)
@ -139,16 +139,8 @@ class ImportPlugin(object):
else:
return importer.short_desc, importer.long_desc
def _doImport(
self,
import_handler,
name,
location,
options,
pubsub_service="",
pubsub_node="",
profile=C.PROF_KEY_NONE,
):
def _doImport(self, import_handler, name, location, options, pubsub_service="",
pubsub_node="", profile=C.PROF_KEY_NONE):
client = self.host.getClient(profile)
options = {key: str(value) for key, value in options.items()}
for option in import_handler.BOOL_OPTIONS:
@ -159,9 +151,11 @@ class ImportPlugin(object):
for option in import_handler.JSON_OPTIONS:
try:
options[option] = json.loads(options[option])
except KeyError:
pass
except ValueError:
raise exceptions.DataError(
_("invalid json option: {name}").format(name=option)
_(f"invalid json option: {option}")
)
pubsub_service = jid.JID(pubsub_service) if pubsub_service else None
return self.doImport(
@ -175,16 +169,8 @@ class ImportPlugin(object):
)
@defer.inlineCallbacks
def doImport(
self,
client,
import_handler,
name,
location,
options=None,
pubsub_service=None,
pubsub_node=None,
):
def doImport(self, client, import_handler, name, location, options=None,
pubsub_service=None, pubsub_node=None,):
"""Import data
@param import_handler(object): instance of the import handler
@ -193,7 +179,8 @@ class ImportPlugin(object):
can be an url, a file path, or anything which make sense
check importer description for more details
@param options(dict, None): extra options.
@param pubsub_service(jid.JID, None): jid of the PubSub service where data must be imported
@param pubsub_service(jid.JID, None): jid of the PubSub service where data must be
imported.
None to use profile's server
@param pubsub_node(unicode, None): PubSub node to use
None to use importer's default node

View file

@ -53,6 +53,7 @@ class MercurialProtocol(async_process.CommandProtocol):
@param path(unicode): path to the repository
@param command(unicode): hg command to run
@return D(bytes): stdout of the command
"""
assert "path" not in kwargs
kwargs["path"] = path
@ -86,8 +87,10 @@ class MercurialHandler(object):
return d
def export(self, repository):
return MercurialProtocol.run(repository, 'export', '-g', '-r', 'outgoing()',
'--encoding=utf-8')
d = MercurialProtocol.run(repository, 'export', '-g', '-r', 'outgoing()',
'--encoding=utf-8')
d.addCallback(lambda data: data.decode('utf-8'))
return d
def import_(self, repository, data, data_type, item_id, service, node, extra):
parsed_data = self.parse(data)
@ -97,7 +100,7 @@ class MercurialHandler(object):
except Exception:
parsed_name = ''
name = 'mr_{item_id}_{parsed_name}'.format(item_id=CLEAN_RE.sub('', item_id),
parsed_name=parsed_name)
parsed_name=parsed_name)
return MercurialProtocol.run(repository, 'qimport', '-g', '--name', name,
'--encoding=utf-8', '-', stdin=data)

View file

@ -254,7 +254,7 @@ class InvitationsPlugin(object):
try:
yield self.host.plugins['XEP-0077'].registerNewAccount(jid_,
password)
except error.StanzaError as e:
except error.StanzaError:
idx += 1
else:
break
@ -321,7 +321,7 @@ class InvitationsPlugin(object):
format_args['url'] = invite_url
yield sat_email.sendEmail(
self.host,
self.host.memory.config,
[email] + emails_extra,
(kwargs.pop('message_subject', None) or DEFAULT_SUBJECT).format(
**format_args),
@ -401,7 +401,7 @@ class InvitationsPlugin(object):
C.PROF_KEY_NONE: don't filter invitations
@return list(unicode): invitations uids
"""
invitations = yield list(self.invitations.items())
invitations = yield self.invitations.items()
if profile != C.PROF_KEY_NONE:
invitations = {id_:data for id_, data in invitations.items()
if data.get('profile') == profile}

View file

@ -106,8 +106,8 @@ class MergeRequests(object):
@param name(unicode): name of the handler
@param handler(object): instance of the handler.
It must have the following methods, which may all return a Deferred:
- check(repository): True if repository can be handled
- export(repository): return export data, i.e. the patches
- check(repository)->bool: True if repository can be handled
- export(repository)->str: return export data, i.e. the patches
- parse(export_data): parse report data and return a list of dict
(1 per patch) with:
- title: title of the commit message (first line)

View file

@ -348,9 +348,8 @@ class TextSyntaxes(object):
element.text = ''
return html.tostring(xhtml_elt, encoding=str, method="xml")
def convert(
self, text, syntax_from, syntax_to=_SYNTAX_XHTML, safe=True, profile=None
):
def convert(self, text, syntax_from, syntax_to=_SYNTAX_XHTML, safe=True,
profile=None):
"""Convert a text between two syntaxes
@param text: text to convert

View file

@ -20,11 +20,11 @@
from sat.core.i18n import _
from sat.core.constants import Const as C
from sat.core.log import getLogger
log = getLogger(__name__)
from twisted.words.xish import domish
from functools import partial
log = getLogger(__name__)
PLUGIN_INFO = {
C.PI_NAME: "Raw XML log Plugin",
C.PI_IMPORT_NAME: "XmlLog",
@ -74,9 +74,9 @@ class XmlLog(object):
def onSend(self, obj, client):
if isinstance(obj, str):
log = str(obj)
xml_log = obj
elif isinstance(obj, domish.Element):
log = obj.toXml()
xml_log = obj.toXml()
else:
log.error(_("INTERNAL ERROR: Unmanaged XML type"))
self.host.bridge.xmlLog("OUT", log, client.profile)
self.host.bridge.xmlLog("OUT", xml_log, client.profile)

View file

@ -108,7 +108,7 @@ class Context(potr.context.Context):
message data when an encrypted message is going to be sent
"""
assert isinstance(self.peer, jid.JID)
msg = msg_str
msg = msg_str.decode('utf-8')
client = self.user.client
log.debug("injecting encrypted message to {to}".format(to=self.peer))
if appdata is None:
@ -530,7 +530,7 @@ class OTR(object):
) # FIXME: temporary and unsecure, must be changed when frontends
# are refactored
otrctx = client._otr_context_manager.getContextForUser(to_jid)
query = otrctx.sendMessage(0, "?OTRv?")
query = otrctx.sendMessage(0, b"?OTRv?")
otrctx.inject(query)
def _otrSessionEnd(self, menu_data, profile):

View file

@ -241,12 +241,12 @@ class XEP_0048(object):
except KeyError:
pass
for url in urls_data:
for url, url_data in urls_data.items():
url_elt = storage_elt.addElement("url")
url_elt[XEP_0048.URL_KEY] = url
for attr in XEP_0048.URL_ATTRS:
try:
url_elt[attr] = url[attr]
url_elt[attr] = url_data[attr]
except KeyError:
pass
@ -440,7 +440,10 @@ class XEP_0048(object):
return ret
data = bookmarks_ori[type_]
for bookmark in data:
ret[_storage_location][bookmark.full()] = data[bookmark].copy()
if type_ == XEP_0048.MUC_TYPE:
ret[_storage_location][bookmark.full()] = data[bookmark].copy()
else:
ret[_storage_location][bookmark] = data[bookmark].copy()
return ret
for _storage_location in ("local", "private", "pubsub"):

View file

@ -44,7 +44,7 @@ except:
raise exceptions.MissingModule(
"Missing module pillow, please download/install it from https://python-pillow.github.io"
)
from io import StringIO
import io
try:
from twisted.words.protocols.xmlstream import XMPPHandler
@ -52,7 +52,8 @@ except ImportError:
from wokkel.subprotocols import XMPPHandler
AVATAR_PATH = "avatars"
AVATAR_DIM = (64, 64) #  FIXME: dim are not adapted to modern resolutions !
# AVATAR_DIM = (64, 64) #  FIXME: dim are not adapted to modern resolutions !
AVATAR_DIM = (128, 128)
IQ_GET = '/iq[@type="get"]'
NS_VCARD = "vcard-temp"
@ -318,7 +319,7 @@ class XEP_0054(object):
avatar_hash = yield threads.deferToThread(
self.savePhoto, client, elem, entity_jid
)
except (exceptions.DataError, exceptions.NotFound) as e:
except (exceptions.DataError, exceptions.NotFound):
avatar_hash = ""
vcard_dict["avatar"] = avatar_hash
except Exception as e:
@ -515,12 +516,13 @@ class XEP_0054(object):
left += offset
right -= offset
img = img.crop((left, upper, right, lower))
img_buf = StringIO()
img_buf = io.BytesIO()
img.save(img_buf, "PNG")
photo_elt = vcard_elt.addElement("PHOTO")
photo_elt.addElement("TYPE", content="image/png")
photo_elt.addElement("BINVAL", content=b64encode(img_buf.getvalue()))
image_b64 = b64encode(img_buf.getvalue()).decode('utf-8')
photo_elt.addElement("BINVAL", content=image_b64)
image_hash = sha1(img_buf.getvalue()).hexdigest()
with client.cache.cacheData(
PLUGIN_INFO["import_name"], image_hash, "image/png", MAX_AGE

View file

@ -175,17 +175,8 @@ ProxyInfos = namedtuple("ProxyInfos", ["host", "jid", "port"])
class Candidate(object):
def __init__(
self,
host,
port,
type_,
priority,
jid_,
id_=None,
priority_local=False,
factory=None,
):
def __init__(self, host, port, type_, priority, jid_, id_=None, priority_local=False,
factory=None,):
"""
@param host(unicode): host IP or domain
@param port(int): port
@ -227,15 +218,6 @@ class Candidate(object):
return self._priority
def __str__(self):
# similar to __unicode__ but we don't show jid and we encode id
return "Candidate ({0.priority}): host={0.host} port={0.port} type={0.type}{id}".format(
self,
id=" id={}".format(self.id if self.id is not None else "").encode(
"utf-8", "ignore"
),
)
def __unicode__(self):
return "Candidate ({0.priority}): host={0.host} port={0.port} jid={0.jid} type={0.type}{id}".format(
self, id=" id={}".format(self.id if self.id is not None else "")
)

View file

@ -113,9 +113,11 @@ class XEP_0166(object):
try:
del client.jingle_sessions[sid]
except KeyError:
log.debug("Jingle session id [{}] is unknown, nothing to delete".format(sid))
log.debug(
f"Jingle session id {sid!r} is unknown, nothing to delete "
f"[{client.profile}]")
else:
log.debug("Jingle session id [{}] deleted".format(sid))
log.debug(f"Jingle session id {sid!r} deleted [{client.profile}]")
## helpers methods to build stanzas ##
@ -146,7 +148,7 @@ class XEP_0166(object):
condition=error_condition
)
)
client.send(iq_elt)
return client.send(iq_elt)
def _terminateEb(self, failure_):
log.warning(_("Error while terminating session: {msg}").format(msg=failure_))
@ -188,23 +190,22 @@ class XEP_0166(object):
)
self._delSession(client, sid)
def _jingleErrorCb(self, fail, sid, request, client):
def _jingleErrorCb(self, failure_, session, request, client):
"""Called when something is going wrong while parsing jingle request
The error condition depend of the exceptions raised:
exceptions.DataError raise a bad-request condition
@param fail(failure.Failure): the exceptions raised
@param sid(unicode): jingle session id
@param session(dict): data of the session
@param request(domsih.Element): jingle request
@param client: %(doc_client)s
"""
log.warning("Error while processing jingle request")
if isinstance(fail, exceptions.DataError):
self.sendError(client, "bad-request", sid, request)
log.warning(f"Error while processing jingle request [{client.profile}]")
if isinstance(failure_, exceptions.DataError):
return self.sendError(client, "bad-request", session['id'], request)
else:
log.error("Unmanaged jingle exception")
self._delSession(client, sid)
raise fail
log.error(f"Unmanaged jingle exception: {failure_}")
return self.terminate(client, self.REASON_FAILED_APPLICATION, session)
## methods used by other plugins ##
@ -706,19 +707,10 @@ class XEP_0166(object):
"""
return elt
def _callPlugins(
self,
client,
action,
session,
app_method_name="jingleHandler",
transp_method_name="jingleHandler",
app_default_cb=None,
transp_default_cb=None,
delete=True,
elements=True,
force_element=None,
):
def _callPlugins(self, client, action, session, app_method_name="jingleHandler",
transp_method_name="jingleHandler", app_default_cb=None,
transp_default_cb=None, delete=True, elements=True,
force_element=None):
"""Call application and transport plugin methods for all contents
@param action(unicode): jingle action name
@ -819,7 +811,7 @@ class XEP_0166(object):
confirm_dlist = defer.gatherResults(confirm_defers)
confirm_dlist.addCallback(self._confirmationCb, session, jingle_elt, client)
confirm_dlist.addErrback(self._jingleErrorCb, session["id"], request, client)
confirm_dlist.addErrback(self._jingleErrorCb, session, request, client)
def _confirmationCb(self, confirm_results, session, jingle_elt, client):
"""Method called when confirmation from user has been received
@ -952,7 +944,7 @@ class XEP_0166(object):
negociate_defers = []
negociate_defers = self._callPlugins(client, XEP_0166.A_SESSION_ACCEPT, session)
negociate_dlist = defer.DeferredList(negociate_defers)
negociate_dlist = defer.gatherResults(negociate_defers)
# after negociations we start the transfer
negociate_dlist.addCallback(

View file

@ -55,7 +55,7 @@ PLUGIN_INFO = {
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 @@ class XEP_0234(object):
# 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 @@ class XEP_0234(object):
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 @@ class XEP_0234(object):
@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 @@ class XEP_0234(object):
# 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 @@ class XEP_0234(object):
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 @@ class XEP_0234(object):
@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 @@ class XEP_0234(object):
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 @@ class XEP_0234(object):
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 @@ class XEP_0234(object):
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 @@ class XEP_0234(object):
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 @@ class XEP_0234(object):
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 @@ class XEP_0234(object):
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"],

View file

@ -196,13 +196,13 @@ class XEP_0300(object):
try:
idx = algos.index(algo)
except ValueError:
log.warning("Proposed {} algorithm is not managed".format(algo))
log.warning(f"Proposed {algo} algorithm is not managed")
algo = None
continue
if best_algo is None or algos.index(best_algo) < idx:
best_algo = algo
best_value = base64.b64decode(str(hash_elt))
best_value = base64.b64decode(str(hash_elt)).decode('utf-8')
if not hash_elt:
raise exceptions.NotFound

View file

@ -595,10 +595,14 @@ class XEP_0329(object):
):
return
for file_data in files_data:
file_elt = self._jf.buildFileElementFromDict(
file_data, modified=file_data.get("modified", file_data["created"])
)
query_elt.addChild(file_elt)
if file_data['type'] == C.FILE_TYPE_DIRECTORY:
directory_elt = query_elt.addElement("directory")
directory_elt['name'] = file_data['name']
else:
file_elt = self._jf.buildFileElementFromDict(
file_data, modified=file_data.get("modified", file_data["created"])
)
query_elt.addChild(file_elt)
client.send(iq_result_elt)
def onComponentRequest(self, iq_elt, client):
@ -624,7 +628,7 @@ class XEP_0329(object):
file_data = {"name": elt["name"], "type": C.FILE_TYPE_DIRECTORY}
else:
log.warning(
_("unexpected element, ignoring: {elt}").format(elt=elt.toXml())
_(f"unexpected element, ignoring: {elt.toXml()}")
)
continue
files.append(file_data)
@ -642,7 +646,7 @@ class XEP_0329(object):
def _listFiles(self, target_jid, path, extra, profile):
client = self.host.getClient(profile)
target_jid = client.jid.userhostJID() if not target_jid else jid.JID(target_jid)
target_jid = client.jid if not target_jid else jid.JID(target_jid)
d = self.listFiles(client, target_jid, path or None)
d.addCallback(self._serializeData)
return d

View file

@ -36,7 +36,6 @@ from twisted.web import http_headers
from twisted.web import iweb
from twisted.python import failure
from collections import namedtuple
from zope.interface import implementer
from OpenSSL import SSL
import os.path
import mimetypes
@ -204,7 +203,7 @@ class XEP_0363(object):
@param ignore_tls_errors(bool): ignore TLS certificate is True
@return (tuple
"""
log.debug("Got upload slot: {}".format(slot))
log.debug(f"Got upload slot: {slot}")
sat_file = self.host.plugins["FILE"].File(
self.host, client, path, size=size, auto_end_signals=False
)
@ -222,7 +221,7 @@ class XEP_0363(object):
headers[name] = value
d = agent.request(
"PUT",
b"PUT",
slot.put.encode("utf-8"),
http_headers.Headers(headers),
file_producer,