XEP-0363: Types, docs, and new-style kwargs

This commit is contained in:
mathieui 2021-02-13 18:18:23 +01:00
parent a7d690813c
commit ea2d851a93
3 changed files with 90 additions and 32 deletions

View file

@ -8,6 +8,12 @@ XEP-0363: HTTP File Upload
:members: :members:
:exclude-members: session_bind, plugin_init, plugin_end :exclude-members: session_bind, plugin_init, plugin_end
.. autoclass:: UploadServiceNotFound
.. autoclass:: FileTooBig
.. autoclass:: HTTPError
Stanza elements Stanza elements
--------------- ---------------

View file

@ -1,4 +1,3 @@
# slixmpp: The Slick XMPP Library # slixmpp: The Slick XMPP Library
# Copyright (C) 2018 Emmanuel Gil Peyrot # Copyright (C) 2018 Emmanuel Gil Peyrot
# This file is part of slixmpp. # This file is part of slixmpp.
@ -6,6 +5,12 @@
from slixmpp.plugins.base import register_plugin from slixmpp.plugins.base import register_plugin
from slixmpp.plugins.xep_0363.stanza import Request, Slot, Put, Get, Header from slixmpp.plugins.xep_0363.stanza import Request, Slot, Put, Get, Header
from slixmpp.plugins.xep_0363.http_upload import XEP_0363 from slixmpp.plugins.xep_0363.http_upload import (
XEP_0363,
UploadServiceNotFound,
FileTooBig,
HTTPError,
FileUploadError,
)
register_plugin(XEP_0363) register_plugin(XEP_0363)

View file

@ -1,18 +1,21 @@
""" # slixmpp: The Slick XMPP Library
slixmpp: The Slick XMPP Library # Copyright (C) 2018 Emmanuel Gil Peyrot
Copyright (C) 2018 Emmanuel Gil Peyrot # This file is part of slixmpp.
This file is part of slixmpp. # See the file LICENSE for copying permission.
See the file LICENSE for copying permission.
"""
import logging import logging
import os.path import os.path
from aiohttp import ClientSession from aiohttp import ClientSession
from asyncio import Future
from mimetypes import guess_type from mimetypes import guess_type
from typing import (
Optional,
IO,
)
from slixmpp import Iq, __version__ from slixmpp import JID, __version__
from slixmpp.stanza import Iq
from slixmpp.plugins import BasePlugin from slixmpp.plugins import BasePlugin
from slixmpp.xmlstream import register_stanza_plugin from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.handler import Callback
@ -25,19 +28,39 @@ class FileUploadError(Exception):
pass pass
class UploadServiceNotFound(FileUploadError): class UploadServiceNotFound(FileUploadError):
pass """
Raised if no upload service can be found.
"""
class FileTooBig(FileUploadError): class FileTooBig(FileUploadError):
"""
Raised if the file size is above advertised server limits.
args:
- size of the file
- max file size allowed
"""
def __str__(self): def __str__(self):
return 'File size too large: {} (max: {} bytes)' \ return 'File size too large: {} (max: {} bytes)' \
.format(self.args[0], self.args[1]) .format(self.args[0], self.args[1])
class HTTPError(FileUploadError): class HTTPError(FileUploadError):
"""
Raised when we receive an HTTP error response during upload.
args:
- HTTP Error code
- Content of the HTTP response
"""
def __str__(self): def __str__(self):
return 'Could not upload file: %d (%s)' % (self.args[0], self.args[1]) return 'Could not upload file: %d (%s)' % (self.args[0], self.args[1])
class XEP_0363(BasePlugin): class XEP_0363(BasePlugin):
''' This plugin only supports Python 3.5+ ''' """
XEP-0363: HTTP File Upload
"""
name = 'xep_0363' name = 'xep_0363'
description = 'XEP-0363: HTTP File Upload' description = 'XEP-0363: HTTP File Upload'
@ -62,9 +85,7 @@ class XEP_0363(BasePlugin):
self._handle_request)) self._handle_request))
def plugin_end(self): def plugin_end(self):
self._http_session.close()
self.xmpp.remove_handler('HTTP Upload Request') self.xmpp.remove_handler('HTTP Upload Request')
self.xmpp.remove_handler('HTTP Upload Slot')
self.xmpp['xep_0030'].del_feature(feature=Request.namespace) self.xmpp['xep_0030'].del_feature(feature=Request.namespace)
def session_bind(self, jid): def session_bind(self, jid):
@ -73,9 +94,14 @@ class XEP_0363(BasePlugin):
def _handle_request(self, iq): def _handle_request(self, iq):
self.xmpp.event('http_upload_request', iq) self.xmpp.event('http_upload_request', iq)
async def find_upload_service(self, domain=None, timeout=None): async def find_upload_service(self, domain: Optional[JID] = None, **iqkwargs) -> Optional[Iq]:
"""Find an upload service on a domain (our own by default).
:param domain: Domain to disco to find a service.
"""
results = await self.xmpp['xep_0030'].get_info_from_domain( results = await self.xmpp['xep_0030'].get_info_from_domain(
domain=domain, timeout=timeout) domain=domain, **iqkwargs
)
candidates = [] candidates = []
for info in results: for info in results:
@ -87,26 +113,49 @@ class XEP_0363(BasePlugin):
if feature == Request.namespace: if feature == Request.namespace:
return info return info
def request_slot(self, jid, filename, size, content_type=None, ifrom=None, def request_slot(self, jid: JID, filename: str, size: int,
timeout=None, callback=None, timeout_callback=None): content_type: Optional[str] = None, *,
iq = self.xmpp.Iq() ifrom: Optional[JID] = None, **iqkwargs) -> Future:
iq['to'] = jid """Request an HTTP upload slot from a service.
iq['from'] = ifrom
iq['type'] = 'get' :param jid: Service to request the slot from.
:param filename: Name of the file that will be uploaded.
:param size: size of the file in bytes.
:param content_type: Type of the file that will be uploaded.
"""
iq = self.xmpp.make_iq_get(ito=jid, ifrom=ifrom)
request = iq['http_upload_request'] request = iq['http_upload_request']
request['filename'] = filename request['filename'] = filename
request['size'] = str(size) request['size'] = str(size)
request['content-type'] = content_type or self.default_content_type request['content-type'] = content_type or self.default_content_type
return iq.send(timeout=timeout, callback=callback, return iq.send(**iqkwargs)
timeout_callback=timeout_callback)
async def upload_file(self, filename, size=None, content_type=None, *, async def upload_file(self, filename: str, size: Optional[int] = None,
input_file=None, ifrom=None, domain=None, timeout=None, content_type: Optional[str] = None, *,
callback=None, timeout_callback=None): input_file: Optional[IO[bytes]]=None,
''' Helper function which does all of the uploading process. ''' domain: Optional[JID] = None,
**iqkwargs) -> str:
'''Helper function which does all of the uploading discovery and
process.
:param filename: Path to the file to upload (or only the name if
``input_file`` is provided.
:param size: size of the file in bytes.
:param content_type: Type of the file that will be uploaded.
:param input_file: Binary file stream on the file.
:param domain: Domain to query to find an HTTP upload service.
:raises .UploadServiceNotFound: If slixmpp is unable to find an
an available upload service.
:raises .FileTooBig: If the filesize is above what is accepted by
the service.
:raises .HTTPError: If there is an error in the HTTP operation.
:returns: The URL of the uploaded file.
'''
timeout = iqkwargs.get('timeout', None)
if self.upload_service is None: if self.upload_service is None:
info_iq = await self.find_upload_service( info_iq = await self.find_upload_service(
domain=domain, timeout=timeout) domain=domain, **iqkwargs
)
if info_iq is None: if info_iq is None:
raise UploadServiceNotFound() raise UploadServiceNotFound()
self.upload_service = info_iq['from'] self.upload_service = info_iq['from']
@ -137,9 +186,7 @@ class XEP_0363(BasePlugin):
basename = os.path.basename(filename) basename = os.path.basename(filename)
slot_iq = await self.request_slot(self.upload_service, basename, size, slot_iq = await self.request_slot(self.upload_service, basename, size,
content_type, ifrom, timeout, content_type, **iqkwargs)
callback=callback,
timeout_callback=timeout_callback)
slot = slot_iq['http_upload_slot'] slot = slot_iq['http_upload_slot']
headers = { headers = {