diff sat/plugins/plugin_xep_0363.py @ 3922:0ff265725489

plugin XEP-0447: handle attachment and download: - plugin XEP-0447 can now be used in message attachments and to retrieve an attachment - plugin attach: `attachment` being processed is added to `extra` so the handler can inspect it - plugin attach: `size` is added to attachment - plugin download: a whole attachment dict is now used in `download` and `file_download`/`file_download_complete`. `download_uri` can be used as a shortcut when just a URI is used. In addition to URI scheme handler, whole attachment handlers can now be registered with `register_download_handler` - plugin XEP-0363: `file_http_upload` `XEP-0363_upload_size` triggers have been renamed to `XEP-0363_upload_pre_slot` and is now using a dict with arguments, allowing for the size but also the filename to be modified, which is necessary for encryption (filename may be hidden from URL this way). - plugin XEP-0446: fix wrong element name - plugin XEP-0447: source handler can now be registered (`url-data` is registered by default) - plugin XEP-0447: source parsing has been put in a separated `parse_sources_elt` method, as it may be useful to do it independently (notably with XEP-0448) - plugin XEP-0447: parse received message and complete attachments when suitable - plugin XEP-0447: can now be used with message attachments - plugin XEP-0447: can now be used with attachments download - renamed `options` arguments to `extra` for consistency - some style change (progressive move from legacy camelCase to PEP8 snake_case) - some typing rel 379
author Goffi <goffi@goffi.org>
date Thu, 06 Oct 2022 16:02:05 +0200
parents e84ffb48acd4
children 412b99c29d83
line wrap: on
line diff
--- a/sat/plugins/plugin_xep_0363.py	Thu Oct 06 16:02:05 2022 +0200
+++ b/sat/plugins/plugin_xep_0363.py	Thu Oct 06 16:02:05 2022 +0200
@@ -16,25 +16,29 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-import os.path
+from dataclasses import dataclass
 import mimetypes
-from typing import NamedTuple, Callable, Optional
-from dataclasses import dataclass
+import os.path
+from pathlib import Path
+from typing import Callable, NamedTuple, Optional, Tuple
 from urllib import parse
-from wokkel import disco, iwokkel
-from zope.interface import implementer
-from twisted.words.protocols.jabber import jid, xmlstream, error
-from twisted.words.xish import domish
+
 from twisted.internet import reactor
 from twisted.internet import defer
 from twisted.web import client as http_client
 from twisted.web import http_headers
-from sat.core.i18n import _
-from sat.core.xmpp import SatXMPPComponent
+from twisted.words.protocols.jabber import error, jid, xmlstream
+from twisted.words.xish import domish
+from wokkel import disco, iwokkel
+from zope.interface import implementer
+
+from sat.core import exceptions
 from sat.core.constants import Const as C
+from sat.core.core_types import SatXMPPEntity
+from sat.core.i18n import _
 from sat.core.log import getLogger
-from sat.core import exceptions
-from sat.tools import web as sat_web, utils
+from sat.core.xmpp import SatXMPPComponent
+from sat.tools import utils, web as sat_web
 
 
 log = getLogger(__name__)
@@ -87,7 +91,7 @@
             ".plugin",
             in_sign="sssbs",
             out_sign="",
-            method=self._fileHTTPUpload,
+            method=self._file_http_upload,
         )
         host.bridge.addMethod(
             "fileHTTPUploadGetSlot",
@@ -98,7 +102,7 @@
             async_=True,
         )
         host.plugins["UPLOAD"].register(
-            "HTTP Upload", self.getHTTPUploadEntity, self.fileHTTPUpload
+            "HTTP Upload", self.getHTTPUploadEntity, self.file_http_upload
         )
         # list of callbacks used when a request is done to a component
         self.handlers = []
@@ -151,58 +155,67 @@
 
         return entity
 
-    def _fileHTTPUpload(self, filepath, filename="", upload_jid="",
+    def _file_http_upload(self, filepath, filename="", upload_jid="",
                         ignore_tls_errors=False, profile=C.PROF_KEY_NONE):
         assert os.path.isabs(filepath) and os.path.isfile(filepath)
         client = self.host.getClient(profile)
-        progress_id_d, __ = defer.ensureDeferred(self.fileHTTPUpload(
+        return defer.ensureDeferred(self.file_http_upload(
             client,
             filepath,
             filename or None,
             jid.JID(upload_jid) if upload_jid else None,
             {"ignore_tls_errors": ignore_tls_errors},
         ))
-        return progress_id_d
 
-    async def fileHTTPUpload(
-        self, client, filepath, filename=None, upload_jid=None, options=None):
+    async def file_http_upload(
+        self,
+        client: SatXMPPEntity,
+        filepath: Path,
+        filename: Optional[str] = None,
+        upload_jid: Optional[jid.JID] = None,
+        extra: Optional[dict] = None
+    ) -> Tuple[str, defer.Deferred]:
         """Upload a file through HTTP
 
-        @param filepath(str): absolute path of the file
-        @param filename(None, unicode): name to use for the upload
+        @param filepath: absolute path of the file
+        @param filename: name to use for the upload
             None to use basename of the path
-        @param upload_jid(jid.JID, None): upload capable entity jid,
+        @param upload_jid: upload capable entity jid,
             or None to use autodetected, if possible
-        @param options(dict): options where key can be:
+        @param extra: options where key can be:
             - ignore_tls_errors(bool): if True, SSL certificate will not be checked
+            - attachment(dict): file attachment data
         @param profile: %(doc_profile)s
-        @return (D(tuple[D(unicode), D(unicode)])): progress id and Deferred which fire
-            download URL
+        @return: progress id and Deferred which fire download URL
         """
-        if options is None:
-            options = {}
-        ignore_tls_errors = options.get("ignore_tls_errors", False)
-        filename = filename or os.path.basename(filepath)
-        size = os.path.getsize(filepath)
+        if extra is None:
+            extra = {}
+        ignore_tls_errors = extra.get("ignore_tls_errors", False)
+        file_metadata = {
+            "filename": filename or os.path.basename(filepath),
+            "filepath": filepath,
+            "size": os.path.getsize(filepath),
+        }
 
-        size_adjust = []
-        #: this trigger can be used to modify the requested size, it is notably useful
-        #: with encryption. The size_adjust is a list which can be filled by int to add
-        #: to the initial size
+        #: this trigger can be used to modify the filename or size requested when geting
+        #: the slot, it is notably useful with encryption.
         self.host.trigger.point(
-            "XEP-0363_upload_size", client, options, filepath, size, size_adjust,
-            triggers_no_cancel=True)
-        if size_adjust:
-            size = sum([size, *size_adjust])
+            "XEP-0363_upload_pre_slot", client, extra, file_metadata,
+            triggers_no_cancel=True
+        )
         try:
-            slot = await self.getSlot(client, filename, size, upload_jid=upload_jid)
+            slot = await self.getSlot(
+                client, file_metadata["filename"], file_metadata["size"],
+                upload_jid=upload_jid
+            )
         except Exception as e:
             log.warning(_("Can't get upload slot: {reason}").format(reason=e))
             raise e
         else:
             log.debug(f"Got upload slot: {slot}")
             sat_file = self.host.plugins["FILE"].File(
-                self.host, client, filepath, uid=options.get("progress_id"), size=size,
+                self.host, client, filepath, uid=extra.get("progress_id"),
+                size=file_metadata["size"],
                 auto_end_signals=False
             )
             progress_id = sat_file.uid
@@ -223,7 +236,7 @@
 
 
             await self.host.trigger.asyncPoint(
-                "XEP-0363_upload", client, options, sat_file, file_producer, slot,
+                "XEP-0363_upload", client, extra, sat_file, file_producer, slot,
                 triggers_no_cancel=True)
 
             download_d = agent.request(
@@ -233,8 +246,8 @@
                 file_producer,
             )
             download_d.addCallbacks(
-                self._uploadCb,
-                self._uploadEb,
+                self._upload_cb,
+                self._upload_eb,
                 (sat_file, slot),
                 None,
                 (sat_file,),
@@ -242,7 +255,7 @@
 
             return progress_id, download_d
 
-    def _uploadCb(self, __, sat_file, slot):
+    def _upload_cb(self, __, sat_file, slot):
         """Called once file is successfully uploaded
 
         @param sat_file(SatFile): file used for the upload
@@ -253,7 +266,7 @@
         sat_file.progressFinished({"url": slot.get})
         return slot.get
 
-    def _uploadEb(self, failure_, sat_file):
+    def _upload_eb(self, failure_, sat_file):
         """Called on unsuccessful upload
 
         @param sat_file(SatFile): file used for the upload