changeset 2512:025afb04c10b

plugin XEP-0234: some cleaning + added triggers to allow plugins to change parsing/generation of <file> element
author Goffi <goffi@goffi.org>
date Fri, 02 Mar 2018 17:53:19 +0100
parents 20a5e7db0609
children 2d3c9dcec384
files src/plugins/plugin_xep_0234.py src/plugins/plugin_xep_0329.py
diffstat 2 files changed, 63 insertions(+), 49 deletions(-) [+]
line wrap: on
line diff
--- 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 <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 @@
                 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 <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 @@
         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'')
--- 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)