From 90b1c95b77f143e28c002d97c5b45896bda28b47 Mon Sep 17 00:00:00 2001 From: Lorenzo Paderi Date: Thu, 4 Jan 2024 12:16:11 +0100 Subject: [PATCH] wip --- data/it.mijorus.collector.gschema.xml | 3 + python3-requirements.json | 57 ++++++++++++++--- requirements.txt | 3 +- src/lib/DroppedItem.py | 92 ++++++++++++++++++++------- src/lib/utils.py | 43 ++++++++++++- src/window.py | 13 +++- 6 files changed, 177 insertions(+), 34 deletions(-) diff --git a/data/it.mijorus.collector.gschema.xml b/data/it.mijorus.collector.gschema.xml index 7679b95..5b20406 100644 --- a/data/it.mijorus.collector.gschema.xml +++ b/data/it.mijorus.collector.gschema.xml @@ -3,6 +3,9 @@ true + + + true diff --git a/python3-requirements.json b/python3-requirements.json index c19a1a5..31a6ecd 100644 --- a/python3-requirements.json +++ b/python3-requirements.json @@ -1,14 +1,55 @@ { - "name": "python3-pillow", + "name": "python3-requirements", "buildsystem": "simple", - "build-commands": [ - "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"pillow==10.2.0\" --no-build-isolation" - ], - "sources": [ + "build-commands": [], + "modules": [ { - "type": "file", - "url": "https://files.pythonhosted.org/packages/f8/3e/32cbd0129a28686621434cbf17bb64bf1458bfb838f1f668262fefce145c/pillow-10.2.0.tar.gz", - "sha256": "e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e" + "name": "python3-pillow", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"pillow==10.2.0\" --no-build-isolation" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/f8/3e/32cbd0129a28686621434cbf17bb64bf1458bfb838f1f668262fefce145c/pillow-10.2.0.tar.gz", + "sha256": "e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e" + } + ] + }, + { + "name": "python3-requests", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"requests\" --no-build-isolation" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/64/62/428ef076be88fa93716b576e4a01f919d25968913e817077a386fcbe4f42/certifi-2023.11.17-py3-none-any.whl", + "sha256": "e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", + "sha256": "f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", + "sha256": "c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", + "sha256": "58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/96/94/c31f58c7a7f470d5665935262ebd7455c7e4c7782eb525658d3dbf4b9403/urllib3-2.1.0-py3-none-any.whl", + "sha256": "55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3" + } + ] } ] } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index d1106ae..db930c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -pillow==10.2.0 \ No newline at end of file +pillow==10.2.0 +requests \ No newline at end of file diff --git a/src/lib/DroppedItem.py b/src/lib/DroppedItem.py index adfb60e..290aaed 100644 --- a/src/lib/DroppedItem.py +++ b/src/lib/DroppedItem.py @@ -1,9 +1,11 @@ import os - +import logging from PIL import Image from gi.repository import Gtk, Adw, Gio, GLib, GObject -from .utils import get_giofile_content_type, pillow_crop_center, get_file_hash +from .utils import get_giofile_content_type, \ + pillow_crop_center, get_file_hash, \ + link_is_image, download_image, get_safe_path class DroppedItemNotSupportedException(Exception): def __init__(self, item, *args: object) -> None: @@ -11,12 +13,16 @@ def __init__(self, item, *args: object) -> None: self.item = item class DroppedItem(): + + DROPS_DIR = f'{GLib.get_user_cache_dir()}/drops' + def __init__(self, item) -> None: self.target_path = None self.display_value = '' self.preview_image = 'paper-symbolic' self.gfile = None self.size = 0 + self.async_load = False MAX_PREVIEW_SIZE_MB = 50 @@ -29,36 +35,22 @@ def __init__(self, item) -> None: content_type = get_giofile_content_type(item) if content_type in ['image/png', 'image/jpg', 'image/jpeg'] and self.size < (MAX_PREVIEW_SIZE_MB * (1024 * 1024)): - filehash = get_file_hash(item) - preview_path = f'{ GLib.get_user_cache_dir()}/drops/{filehash}.{content_type.split("/")[1]}' - image = Image.open(self.target_path) - image.thumbnail((200, 200)) - image = pillow_crop_center(image, min(image.size)) - image.save(preview_path) - - self.preview_image = Gtk.Image( - file=preview_path, - overflow=Gtk.Overflow.HIDDEN, - css_classes=['dropped-item-thumb'], - height_request=70, - width_request=70, - ) + self.generate_preview_for_image() else: self.preview_image = info.get_icon() elif isinstance(item, str): base_filename = 'collected_text_' text_string = item - i = 1 - while os.path.exists(GLib.get_user_cache_dir() + f'/drops/{base_filename}{i}.txt'): - i += 1 + if text_string.startswith('http://') or text_string.startswith('https://'): + self.async_load = True - self.target_path = GLib.get_user_cache_dir() + f'/drops/{base_filename}{i}.txt' + self.target_path = get_safe_path(f'{self.DROPS_DIR}/{base_filename}', 'txt') with open(self.target_path, 'w+') as f: f.write(text_string) self.gfile = Gio.File.new_for_path(self.target_path) - self.size = os.stat(self.target_path).st_size + self.size = len(text_string) self.preview_image = 'font-x-generic-symbolic' self.display_value = text_string[:25] @@ -66,4 +58,60 @@ def __init__(self, item) -> None: self.display_value = self.display_value + '...' else: - raise DroppedItemNotSupportedException(item) \ No newline at end of file + raise DroppedItemNotSupportedException(item) + + def complete_load(self): + if not self.async_load: + return + + text_content = '' + with open(self.gfile.get_path(), 'r') as f: + text_content = f.read() + + try: + is_image = link_is_image(text_content) + except Exception as e: + logging.warn(e) + return + + if not is_image: + return + + try: + data, extension, filename = download_image(text_content) + except Exception as e: + logging.warn(e) + return + + self.target_path = get_safe_path(f'{self.DROPS_DIR}/{filename}', extension) + + with open(self.target_path, 'wb') as f: + f.write(data) + + self.gfile = Gio.File.new_for_path(self.target_path) + + self.generate_preview_for_image() + self.async_load = False + + def generate_preview_for_image(self): + logging.debug(f'Generating preview image for: {self.target_path}') + + extension = os.path.splitext(self.target_path)[1] + + filehash = get_file_hash(self.gfile) + preview_path = f'{self.DROPS_DIR}/__{filehash}.{extension}' + + image = self.crop_image(self.target_path) + image.save(preview_path) + + self.preview_image = Gio.File.new_for_path(preview_path) + + + + def crop_image(self, image_path): + logging.debug(f'Cropping image: {image_path}') + + image = Image.open(image_path) + image.thumbnail((200, 200)) + image = pillow_crop_center(image, min(image.size)) + return image \ No newline at end of file diff --git a/src/lib/utils.py b/src/lib/utils.py index 3ae0428..fb83800 100644 --- a/src/lib/utils.py +++ b/src/lib/utils.py @@ -1,4 +1,8 @@ +import os import hashlib +import logging +import requests +import re from gi.repository import Gtk, Adw, Gio, Gdk, GObject, GLib @@ -17,4 +21,41 @@ def get_file_hash(file: Gio.File, alg='md5') -> str: if alg == 'md5': return hashlib.md5(f.read()).hexdigest() elif alg == 'sha1': - return hashlib.sha1(f.read()).hexdigest() \ No newline at end of file + return hashlib.sha1(f.read()).hexdigest() + +def link_is_image(link): + logging.info(f'Testing link headers for: {link}') + + link = link.strip() + image_formats = ["image/png", "image/jpeg", "image/jpg"] + r = requests.head(link) + + is_image = r.headers["content-type"] in image_formats + + if is_image: + logging.info(f'Link appears to be an image') + + return is_image + + +def download_image(link: str): + logging.log(f'Downloading image from url: {link}') + r = requests.get(link.strip()) + + extension = r.headers["content-type"].split('/')[1] + filename = link.split('/')[-1] + + if r.headers.get('content-disposition', None): + d = r.headers['content-disposition'] + filename = re.findall("filename=(.+)", d)[0] + + + return (r.content(), extension, filename) + + +def get_safe_path(p, ext): + i = 1 + while os.path.exists(f'{p}{i}.{ext}'): + i += 1 + + return f'{p}{i}.{ext}' \ No newline at end of file diff --git a/src/window.py b/src/window.py index cd8c49e..6e2c959 100644 --- a/src/window.py +++ b/src/window.py @@ -228,8 +228,14 @@ def on_drop_event(self, widget, value, x, y): new_image = Gtk.Image(icon_name=dropped_item.preview_image, pixel_size=70) elif isinstance(dropped_item.preview_image, Gio.Icon): new_image = Gtk.Image(gicon=dropped_item.preview_image, pixel_size=70) - else: - new_image = dropped_item.preview_image + elif isinstance(dropped_item.preview_image, Gio.File): + new_image = Gtk.Image( + file=dropped_item.preview_image.get_path(), + overflow=Gtk.Overflow.HIDDEN, + css_classes=['dropped-item-thumb'], + height_request=70, + width_request=70, + ) new_image.set_tooltip_text(dropped_item.display_value) @@ -243,6 +249,9 @@ def on_drop_event(self, widget, value, x, y): self.icon_carousel.scroll_to(new_image, True) return True + + def on_drop_event_complete(self): + pass def on_drop_enter(self, widget, x, y): if not self.is_dragging_away: