# HG changeset patch # User Goffi # Date 1520009599 -3600 # Node ID 025afb04c10bc130f0d3d0d80038dae1ff7230d2 # Parent 20a5e7db06092b4703d4eacea46f252cb819e26d plugin XEP-0234: some cleaning + added triggers to allow plugins to change parsing/generation of element diff -r 20a5e7db0609 -r 025afb04c10b src/plugins/plugin_xep_0234.py --- a/src/plugins/plugin_xep_0234.py Fri Mar 02 17:45:23 2018 +0100 +++ b/src/plugins/plugin_xep_0234.py Fri Mar 02 17:53:19 2018 +0100 @@ -36,6 +36,7 @@ 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 @@ 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 @@ # 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 element with available metadata @param file_hash(unicode, None): hash of the file empty string to set element @param hash_algo(unicode, None): hash algorithm used if file_hash is None and hash_algo is set, a element will be generated - @param range_offset(int, None): offset where transfer must start - use -1 to add an empty 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 @@ 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 @@ 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 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 element in parent_elt @raise exceptions.DataError: if file_elt uri is not NS_JINGLE_FT """ @@ -187,12 +184,13 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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'') diff -r 20a5e7db0609 -r 025afb04c10b src/plugins/plugin_xep_0329.py --- a/src/plugins/plugin_xep_0329.py Fri Mar 02 17:45:23 2018 +0100 +++ b/src/plugins/plugin_xep_0329.py Fri Mar 02 17:53:19 2018 +0100 @@ -311,11 +311,11 @@ 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 @@ 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 @@ 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)