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:
parent
5cee4fbab7
commit
76aadbb3fb
18 changed files with 3299 additions and 3300 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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"):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 "")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue