changeset 3223:163014f09bf4

plugin attach: handle large images resizing: if `C.MESS_KEY_ATTACHMENTS_RESIZE` flag is set for an attachment, it will be checked, and if it is a large image, a resized copy will be uploaded.
author Goffi <goffi@goffi.org>
date Sun, 22 Mar 2020 14:04:16 +0100
parents 653fa213d2f8
children 0404b306641d
files sat/core/constants.py sat/plugins/plugin_misc_attach.py sat/plugins/plugin_xep_0363.py
diffstat 3 files changed, 52 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/sat/core/constants.py	Sun Mar 22 14:01:47 2020 +0100
+++ b/sat/core/constants.py	Sun Mar 22 14:04:16 2020 +0100
@@ -136,6 +136,7 @@
     MESS_KEY_ATTACHMENTS = "attachments"
     MESS_KEY_ATTACHMENTS_MEDIA_TYPE = "media_type"
     MESS_KEY_ATTACHMENTS_PREVIEW = "preview"
+    MESS_KEY_ATTACHMENTS_RESIZE = "resize"
 
     # File encryption algorithms
     ENC_AES_GCM = "AES-GCM"
--- a/sat/plugins/plugin_misc_attach.py	Sun Mar 22 14:01:47 2020 +0100
+++ b/sat/plugins/plugin_misc_attach.py	Sun Mar 22 14:04:16 2020 +0100
@@ -16,15 +16,18 @@
 # 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/>.
 
-from functools import partial
 from pathlib import Path
 from collections import namedtuple
 from twisted.internet import defer
+import mimetypes
+import tempfile
+import shutil
 from sat.core.i18n import _
 from sat.core import exceptions
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
 from sat.tools import utils
+from sat.tools import image
 
 
 log = getLogger(__name__)
@@ -86,6 +89,46 @@
         log.debug(f"new attachments handler: {handler}")
 
     async def attachFiles(self, client, data):
+        """Main method to attach file
+
+        It will do generic pre-treatment, and call the suitable attachments handler
+        """
+        # we check attachment for pre-treatment like large image resizing
+        # media_type will be added if missing (and if it can be guessed from path)
+        attachments = data["extra"][C.MESS_KEY_ATTACHMENTS]
+        tmp_dirs_to_clean = []
+        for attachment in attachments:
+            if attachment.get(C.MESS_KEY_ATTACHMENTS_RESIZE, False):
+                path = Path(attachment["path"])
+                try:
+                    media_type = attachment[C.MESS_KEY_ATTACHMENTS_MEDIA_TYPE]
+                except KeyError:
+                    media_type = mimetypes.guess_type(path, strict=False)[0]
+                    if media_type is None:
+                        log.warning(
+                            _("Can't resize attachment of unknown type: {attachment}")
+                            .format(attachment=attachment))
+                        continue
+                    attachment[C.MESS_KEY_ATTACHMENTS_MEDIA_TYPE] = media_type
+
+                main_type = media_type.split('/')[0]
+                if main_type == "image":
+                    report = image.check(self.host, path)
+                    if report['too_large']:
+                        tmp_dir = Path(tempfile.mkdtemp())
+                        tmp_dirs_to_clean.append(tmp_dir)
+                        new_path = tmp_dir / path.name
+                        await image.resize(
+                            path, report["recommended_size"], dest=new_path)
+                        attachment["path"] = new_path
+                        log.info(
+                            _("Attachment {path!r} has been resized at {new_path!r}")
+                            .format(path=str(path), new_path=str(new_path)))
+                else:
+                    log.warning(
+                        _("Can't resize attachment of type {main_type!r}: {attachment}")
+                        .format(main_type=main_type, attachment=attachment))
+
         if client.encryption.isEncryptionRequested(data):
             handlers = self._attachments_handlers['encrypted']
         else:
@@ -103,6 +146,10 @@
 
         await utils.asDeferred(handler.attach, client, data)
 
+        for dir_path in tmp_dirs_to_clean:
+            log.debug(f"Cleaning temporary directory at {dir_path}")
+            shutil.rmtree(dir_path)
+
         return data
 
     async def uploadFiles(self, client, data, upload_cb=None):
@@ -186,13 +233,13 @@
 
         return data
 
-    def _attachFiles(self, client, data):
+    def _attachFiles(self, data, client):
         return defer.ensureDeferred(self.attachFiles(client, data))
 
     def _sendMessageTrigger(
         self, client, mess_data, pre_xml_treatments, post_xml_treatments):
         if mess_data['extra'].get(C.MESS_KEY_ATTACHMENTS):
-            post_xml_treatments.addCallback(partial(self._attachFiles, client))
+            post_xml_treatments.addCallback(self._attachFiles, client=client)
         return True
 
     async def defaultCanHandle(self, client, data):
--- a/sat/plugins/plugin_xep_0363.py	Sun Mar 22 14:01:47 2020 +0100
+++ b/sat/plugins/plugin_xep_0363.py	Sun Mar 22 14:04:16 2020 +0100
@@ -205,7 +205,7 @@
             should be closed, but it is needed to send the progressFinished signal
         @param slot(Slot): put/get urls
         """
-        log.info("HTTP upload finished")
+        log.info(f"HTTP upload finished ({slot.get})")
         sat_file.progressFinished({"url": slot.get})
         return slot.get