plugin XEP-0234: some cleaning + added triggers to allow plugins to change parsing/generation of <file> element
This commit is contained in:
parent
7faccbd03b
commit
29d9713462
2 changed files with 63 additions and 49 deletions
|
|
@ -36,6 +36,7 @@ from twisted.internet import reactor
|
|||
from twisted.internet import error as internet_error
|
||||
from collections import namedtuple
|
||||
import regex
|
||||
import mimetypes
|
||||
|
||||
|
||||
NS_JINGLE_FT = 'urn:xmpp:jingle:apps:file-transfer:5'
|
||||
|
|
@ -59,6 +60,7 @@ Range = namedtuple('Range', ('offset', 'length'))
|
|||
class XEP_0234(object):
|
||||
# TODO: assure everything is closed when file is sent or session terminate is received
|
||||
# TODO: call self._f.unregister when unloading order will be managing (i.e. when dependencies will be unloaded at the end)
|
||||
Range = Range # we copy the class here, so it can be used by other plugins
|
||||
|
||||
def __init__(self, host):
|
||||
log.info(_("plugin Jingle File Transfer initialization"))
|
||||
|
|
@ -86,41 +88,46 @@ class XEP_0234(object):
|
|||
|
||||
# generic methods
|
||||
|
||||
def buildFileElement(self, name, file_hash=None, hash_algo=None, size=None, media_type=None, desc=None,
|
||||
date=None, range_offset=None, range_length=None, path=None, namespace=None, file_elt=None):
|
||||
def buildFileElement(self, name, 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
|
||||
|
||||
@param file_hash(unicode, None): hash of the file
|
||||
empty string to set <hash-used/> element
|
||||
@param hash_algo(unicode, None): hash algorithm used
|
||||
if file_hash is None and hash_algo is set, a <hash-used/> element will be generated
|
||||
@param range_offset(int, None): offset where transfer must start
|
||||
use -1 to add an empty <range/> element
|
||||
@param date(int, unicode, None): date of last modification
|
||||
@param transfer_range(Range, None): where transfer must start/stop
|
||||
@param modified(int, unicode, None): date of last modification
|
||||
0 to use current date
|
||||
int to use an unix timestamp
|
||||
else must be an unicode string which will be used as it (it must be an XMPP time)
|
||||
@param file_elt(domish.Element, None): element to use
|
||||
None to create a new one
|
||||
@param **kwargs: data for plugin extension (ignored by default)
|
||||
@return (domish.Element): generated element
|
||||
@trigger XEP-0234_buildFileElement(file_elt, extra_args): can be used to extend elements to add
|
||||
"""
|
||||
if file_elt is None:
|
||||
file_elt = domish.Element((NS_JINGLE_FT, u'file'))
|
||||
for name, value in ((u'name', name), (u'size', size), ('media-type', media_type),
|
||||
for name, value in ((u'name', name), (u'size', size), ('media-type', mime_type),
|
||||
(u'desc', desc), (u'path', path), (u'namespace', namespace)):
|
||||
if value is not None:
|
||||
file_elt.addElement(name, content=unicode(value))
|
||||
if date is not None:
|
||||
if isinstance(date, int):
|
||||
file_elt.addElement(u'date', utils.xmpp_date(date or None))
|
||||
|
||||
if modified is not None:
|
||||
if isinstance(modified, int):
|
||||
file_elt.addElement(u'date', utils.xmpp_date(modified or None))
|
||||
else:
|
||||
file_elt.addElement(u'date', date)
|
||||
if range_offset or range_length:
|
||||
range_elt = file_elt.addElement(u'range')
|
||||
if range_offset is not None and range_offset != -1:
|
||||
range_elt[u'offset'] = range_offset
|
||||
if range_length is not None:
|
||||
range_elt[u'length'] = range_length
|
||||
file_elt.addElement(u'date', modified)
|
||||
elif 'created' in kwargs:
|
||||
file_elt.addElement(u'date', utils.xmpp_date(kwargs.pop('created')))
|
||||
|
||||
range_elt = file_elt.addElement(u'range')
|
||||
if transfer_range is not None:
|
||||
if transfer_range.offset is not None:
|
||||
range_elt[u'offset'] = transfer_range.offset
|
||||
if transfer_range.length is not None:
|
||||
range_elt[u'length'] = transfer_range.length
|
||||
if file_hash is not None:
|
||||
if not file_hash:
|
||||
file_elt.addChild(self._hash.buildHashUsedElt())
|
||||
|
|
@ -128,9 +135,13 @@ class XEP_0234(object):
|
|||
file_elt.addChild(self._hash.buildHashElt(file_hash, hash_algo))
|
||||
elif hash_algo is not None:
|
||||
file_elt.addChild(self._hash.buildHashUsedElt(hash_algo))
|
||||
self.host.trigger.point(u'XEP-0234_buildFileElement', file_elt, extra_args=kwargs)
|
||||
if kwargs:
|
||||
for kw in kwargs:
|
||||
log.debug('ignored keyword: {}'.format(kw))
|
||||
return file_elt
|
||||
|
||||
def buildFileElementFromDict(self, file_data, file_elt = None, **kwargs):
|
||||
def buildFileElementFromDict(self, file_data, **kwargs):
|
||||
"""like buildFileElement but get values from a file_data dict
|
||||
|
||||
@param file_data(dict): metadata to use
|
||||
|
|
@ -139,37 +150,23 @@ class XEP_0234(object):
|
|||
if kwargs:
|
||||
file_data = file_data.copy()
|
||||
file_data.update(kwargs)
|
||||
(name, file_hash, hash_algo, size, media_type,
|
||||
desc, date, path, namespace) = (file_data.get(u'name'),
|
||||
file_data.get(u'file_hash'),
|
||||
file_data.get(u'hash_algo'),
|
||||
file_data.get(u'size'),
|
||||
file_data.get(u'media-type'),
|
||||
file_data.get(u'desc'),
|
||||
file_data.get(u'date'),
|
||||
file_data.get(u'path'),
|
||||
file_data.get(u'namespace'))
|
||||
try:
|
||||
range_offset, range_length = file_data[u'range']
|
||||
except KeyError:
|
||||
range_offset = range_length = None
|
||||
return self. buildFileElement(name = name, file_hash = file_hash, hash_algo = hash_algo, size = size,
|
||||
media_type = media_type, desc = desc, date = date,
|
||||
range_offset = range_offset, range_length = range_length, path = path,
|
||||
namespace = namespace, file_elt = file_elt)
|
||||
return self. buildFileElement(**file_data)
|
||||
|
||||
|
||||
def parseFileElement(self, file_elt, file_data=None, given=False, parent_elt=None):
|
||||
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
|
||||
following keys will be set (and overwritten if they already exist):
|
||||
name, file_hash, hash_algo, size, media-type, desc, path, namespace, range
|
||||
name, file_hash, hash_algo, size, mime_type, desc, path, namespace, range
|
||||
if None, a new dict is created
|
||||
@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
|
||||
@return (dict): file_data
|
||||
@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
|
||||
"""
|
||||
|
|
@ -187,12 +184,13 @@ class XEP_0234(object):
|
|||
if file_data is None:
|
||||
file_data = {}
|
||||
|
||||
for name in (u'name', u'media-type', u'desc', u'path', u'namespace'):
|
||||
for name in (u'name', u'desc', u'path', u'namespace'):
|
||||
try:
|
||||
file_data[name] = unicode(file_elt.elements(NS_JINGLE_FT, name).next())
|
||||
file_data[name] = unicode(next(file_elt.elements(NS_JINGLE_FT, name)))
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
|
||||
name = file_data.get(u'name')
|
||||
if name == u'..':
|
||||
# we don't want to go to parent dir when joining to a path
|
||||
|
|
@ -202,7 +200,17 @@ class XEP_0234(object):
|
|||
file_data[u'name'] = regex.pathEscape(name)
|
||||
|
||||
try:
|
||||
file_data[u'size'] = int(unicode(file_elt.elements(NS_JINGLE_FT, u'size').next()))
|
||||
file_data[u'mime_type'] = unicode(next(file_elt.elements(NS_JINGLE_FT, u'media-type')))
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
try:
|
||||
file_data[u'size'] = int(unicode(next(file_elt.elements(NS_JINGLE_FT, u'size'))))
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
try:
|
||||
file_data[u'modified'] = utils.date_parse(next(file_elt.elements(NS_JINGLE_FT, u'date')))
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
|
|
@ -213,7 +221,8 @@ class XEP_0234(object):
|
|||
else:
|
||||
offset = range_elt.getAttribute('offset')
|
||||
length = range_elt.getAttribute('length')
|
||||
file_data[u'range'] = Range(offset=offset, length=length)
|
||||
if offset or length or keep_empty_range:
|
||||
file_data[u'transfer_range'] = Range(offset=offset, length=length)
|
||||
|
||||
prefix = u'given_' if given else u''
|
||||
hash_algo_key, hash_key = u'hash_algo', prefix + u'file_hash'
|
||||
|
|
@ -222,6 +231,8 @@ class XEP_0234(object):
|
|||
except exceptions.NotFound:
|
||||
pass
|
||||
|
||||
self.host.trigger.point(u'XEP-0234_parseFileElement', file_elt, file_data)
|
||||
|
||||
return file_data
|
||||
|
||||
# bridge methods
|
||||
|
|
@ -314,17 +325,20 @@ class XEP_0234(object):
|
|||
|
||||
if content_data[u'senders'] == self._j.ROLE_INITIATOR:
|
||||
# we send a file
|
||||
if name is None:
|
||||
name = os.path.basename(filepath)
|
||||
file_data[u'date'] = utils.xmpp_date()
|
||||
file_data[u'desc'] = extra.pop(u'file_desc', u'')
|
||||
file_data[u'media-type'] = "application/octet-stream" # TODO
|
||||
file_data[u'name'] = os.path.basename(filepath) if name is None else name
|
||||
file_data[u'name'] = name
|
||||
mime_type = mimetypes.guess_type(name, strict=False)[0]
|
||||
if mime_type is not None:
|
||||
file_data[u'mime_type'] = mime_type
|
||||
file_data[u'size'] = os.path.getsize(filepath)
|
||||
if u'namespace' in extra:
|
||||
file_data[u'namespace'] = extra[u'namespace']
|
||||
if u'path' in extra:
|
||||
file_data[u'path'] = extra[u'path']
|
||||
self.buildFileElementFromDict(file_data, file_elt=file_elt, file_hash=u'')
|
||||
file_elt.addElement("range") # TODO
|
||||
else:
|
||||
# we request a file
|
||||
file_hash = extra.pop(u'file_hash', u'')
|
||||
|
|
|
|||
|
|
@ -311,11 +311,11 @@ class XEP_0329(object):
|
|||
name = os.path.basename(path)
|
||||
if os.path.isfile(path):
|
||||
size = os.path.getsize(path)
|
||||
media_type = mimetypes.guess_type(path, strict=False)[0]
|
||||
mime_type = mimetypes.guess_type(path, strict=False)[0]
|
||||
file_elt = self._jf.buildFileElement(name = name,
|
||||
size = size,
|
||||
media_type = media_type,
|
||||
date = os.path.getmtime(path))
|
||||
mime_type = mime_type,
|
||||
modified = os.path.getmtime(path))
|
||||
|
||||
query_elt.addChild(file_elt)
|
||||
# we don't specify hash as it would be too resource intensive to calculate it for all files
|
||||
|
|
@ -323,7 +323,7 @@ class XEP_0329(object):
|
|||
name_data = client._XEP_0329_names_data.setdefault(name, {})
|
||||
if path not in name_data:
|
||||
name_data[path] = {'size': size,
|
||||
'media_type': media_type,
|
||||
'mime_type': mime_type,
|
||||
'parent': parent_node}
|
||||
else:
|
||||
# we have a directory
|
||||
|
|
@ -435,7 +435,7 @@ class XEP_0329(object):
|
|||
query_elt[u'node'] = node_path
|
||||
for file_data in files_data:
|
||||
file_elt = self._jf.buildFileElementFromDict(file_data,
|
||||
date=file_data.get(u'modified', file_data[u'created']))
|
||||
modified=file_data.get(u'modified', file_data[u'created']))
|
||||
query_elt.addChild(file_elt)
|
||||
client.send(iq_result_elt)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue