XEP-0363: Types, docs, and new-style kwargs
This commit is contained in:
parent
a7d690813c
commit
ea2d851a93
3 changed files with 90 additions and 32 deletions
|
@ -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
|
||||||
---------------
|
---------------
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
Loading…
Reference in a new issue