From 3008a55899973f6294c1087a792a7da7f9d51b49 Mon Sep 17 00:00:00 2001 From: Fluffy-Bean Date: Tue, 14 Mar 2023 22:07:17 +0000 Subject: [PATCH] Moved some scripts to a utils folder Renamed upload route to file as its more approprete Fixed random CSS issues that occur on older browsers or Safari --- README.md | 2 +- gallery/__init__.py | 3 +- gallery/db.py | 12 ++ gallery/routes/api.py | 120 ++++++++++-------- gallery/static/js/login.js | 12 +- gallery/static/js/main.js | 2 +- gallery/templates/groups/group.html | 2 +- gallery/templates/image.html | 10 +- .../default/components/elements/pop-up.sass | 8 +- .../components/image-view/fullscreen.sass | 1 + .../themes/default/components/navigation.sass | 1 + gallery/utils/__init__.py | 0 gallery/{ => utils}/metadata/__init__.py | 0 gallery/{ => utils}/metadata/helpers.py | 0 gallery/{ => utils}/metadata/mapping.py | 0 gallery/{ => utils}/theme_manager.py | 0 pyproject.toml | 2 +- run.py | 2 +- setup/configuration.py | 1 + 19 files changed, 102 insertions(+), 76 deletions(-) create mode 100644 gallery/utils/__init__.py rename gallery/{ => utils}/metadata/__init__.py (100%) rename gallery/{ => utils}/metadata/helpers.py (100%) rename gallery/{ => utils}/metadata/mapping.py (100%) rename gallery/{ => utils}/theme_manager.py (100%) diff --git a/README.md b/README.md index 9d66729..7602331 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Try checking if you have `XDG_CONFIG_HOME` setup. If you don't, you can set that export XDG_CONFIG_HOME="$HOME/.config" -## Finally notes +## Final notes Thank you to everyone who helped me test the previous and current versions of the gallery, especially critters: diff --git a/gallery/__init__.py b/gallery/__init__.py index 20636c5..fcf4b29 100644 --- a/gallery/__init__.py +++ b/gallery/__init__.py @@ -13,6 +13,8 @@ from flask_caching import Cache from flask_assets import Environment, Bundle from flask import Flask, render_template +from gallery.utils import theme_manager + # Configuration from dotenv import load_dotenv import platformdirs @@ -61,7 +63,6 @@ def create_app(test_config=None): pass # Load theme - from . import theme_manager theme_manager.CompileTheme('default', app.root_path) # Bundle JS files diff --git a/gallery/db.py b/gallery/db.py index 8a3a124..f3d5e77 100644 --- a/gallery/db.py +++ b/gallery/db.py @@ -58,6 +58,18 @@ class Posts (base): # pylint: disable=too-few-public-methods, C0103 post_alt = Column(String, nullable=False) junction = relationship('GroupJunction', backref='posts') + + +class Thumbnails (base): # pylint: disable=too-few-public-methods, C0103 + """ + Thumbnail table + """ + __tablename__ = 'thumbnails' + + id = Column(Integer, primary_key=True) + file_name = Column(String, unique=True, nullable=False) + file_ext = Column(String, nullable=False) + data = Column(PickleType, nullable=False) class Groups (base): # pylint: disable=too-few-public-methods, C0103 diff --git a/gallery/routes/api.py b/gallery/routes/api.py index 2e55154..3583f1c 100644 --- a/gallery/routes/api.py +++ b/gallery/routes/api.py @@ -4,6 +4,7 @@ Used intermally by the frontend and possibly by other applications """ from uuid import uuid4 import os +import pathlib import io import logging from datetime import datetime as dt @@ -19,7 +20,7 @@ from sqlalchemy.orm import sessionmaker from gallery.auth import login_required from gallery import db -from gallery import metadata as mt +from gallery.utils import metadata as mt blueprint = Blueprint('api', __name__, url_prefix='/api') @@ -27,58 +28,77 @@ db_session = sessionmaker(bind=db.engine) db_session = db_session() -@blueprint.route('/uploads/', methods=['GET']) -def uploads(file): +@blueprint.route('/file/', methods=['GET']) +def get_file(file_name): """ Returns a file from the uploads folder + t is the type of file (thumb, etc) w and h are the width and height of the image for resizing f is whether to apply filters to the image, such as blurring NSFW images b is whether to force blur the image, even if it's not NSFW """ # Get args + type = request.args.get('t', default=None, type=str) # Type of file (thumb, etc) width = request.args.get('w', default=0, type=int) # Width of image height = request.args.get('h', default=0, type=int) # Height of image filtered = request.args.get('f', default=False, type=bool) # Whether to apply filters blur = request.args.get('b', default=False, type=bool) # Whether to force blur - # if no args are passed, return the raw file - if width == 0 and height == 0 and not filtered: - if not os.path.exists(os.path.join(current_app.config['UPLOAD_FOLDER'], - secure_filename(file))): + file_name = secure_filename(file_name) # Sanitize file name + + # If type is thumb(nail), return from database instead of file system + # as it's faster than generating a new thumbnail on every request + if type == 'thumb': + thumb = db_session.query(db.Thumbnails).filter_by(file_name=file_name).first() + + # If no thumbnail exists, return 404 + if not thumb: abort(404) - return send_from_directory(current_app.config['UPLOAD_FOLDER'], file, as_attachment=True) - # Of either width or height is 0, set it to the other value to keep aspect ratio - if width > 0 and height == 0: + return send_file(thumb.data, mimetype='image/' + thumb.file_ext) + + # if no args are passed, return the raw file + if not request.args: + if not os.path.exists(os.path.join(current_app.config['UPLOAD_FOLDER'], file_name)): + abort(404) + + return send_from_directory(current_app.config['UPLOAD_FOLDER'], file_name) + + # If only width is passed, set height to width + if width and not height: height = width - elif width == 0 and height > 0: + # If only height is passed, set width to height + elif not width and height: width = height + # If neither are passed, return 400 as one is required for resizing + elif not width and not height: + abort(400) - buff = io.BytesIO() + buff = io.BytesIO() # Image Buffer # Open image and set extension try: - img = Image.open(os.path.join(current_app.config['UPLOAD_FOLDER'], file)) + img = Image.open(os.path.join(current_app.config['UPLOAD_FOLDER'], file_name)) + # FileNotFound is raised if the file doesn't exist except FileNotFoundError: - logging.error('File not found: %s, possibly broken upload', file) + logging.error('File not found: %s', file_name) abort(404) - except Exception as err: - logging.error('Error opening image: %s', err) + # OSError is raised if the file is broken or corrupted + except OSError as err: + logging.error('Possibly broken image %s, error: %s', file_name, err) abort(500) - img_ext = os.path.splitext(file)[-1].lower().replace('.', '') - img_ext = current_app.config['ALLOWED_EXTENSIONS'][img_ext] - img_icc = img.info.get("icc_profile") # Get ICC profile as it alters colours when saving + img_ext = pathlib.Path(file_name).suffix.replace('.', '').lower() # Get file extension + img_ext = current_app.config['ALLOWED_EXTENSIONS'][img_ext] # Convert to MIME type + img_icc = img.info.get("icc_profile") # Get ICC profile - # Resize image and orientate correctly - img.thumbnail((width, height), Image.LANCZOS) - img = ImageOps.exif_transpose(img) + img.thumbnail((width, height), Image.LANCZOS) # Resize image + img = ImageOps.exif_transpose(img) # Rotate image based on EXIF data # If has NSFW tag, blur image, etc. if filtered: - # img = img.filter(ImageFilter.GaussianBlur(20)) pass - + # If forced to blur, blur image if blur: img = img.filter(ImageFilter.GaussianBlur(20)) @@ -91,12 +111,12 @@ def uploads(file): img = img.convert('RGB') img.save(buff, img_ext, icc_profile=img_icc) except Exception as err: - logging.error('Could not resize image %s, error: %s', file, err) + logging.error('Could not resize image %s, error: %s', file_name, err) abort(500) - img.close() + img.close() # Close image to free memory, learned the hard way buff.seek(0) # Reset buffer to start - + return send_file(buff, mimetype='image/' + img_ext) @@ -108,23 +128,21 @@ def upload(): """ form_file = request.files['file'] form = request.form - form_description = form['description'] - form_alt = form['alt'] + # If no image is uploaded, return 404 error if not form_file: return abort(404) - img_ext = os.path.splitext(form_file.filename)[-1].replace('.', '').lower() + # Get file extension, generate random name and set file path + img_ext = pathlib.Path(form_file.filename).suffix.replace('.', '').lower() img_name = "GWAGWA_"+str(uuid4()) img_path = os.path.join(current_app.config['UPLOAD_FOLDER'], img_name+'.'+img_ext) + # Check if file extension is allowed if img_ext not in current_app.config['ALLOWED_EXTENSIONS'].keys(): logging.info('File extension not allowed: %s', img_ext) abort(403) - if os.path.isdir(current_app.config['UPLOAD_FOLDER']) is False: - os.mkdir(current_app.config['UPLOAD_FOLDER']) - # Save file try: form_file.save(img_path) @@ -132,28 +150,27 @@ def upload(): logging.error('Could not save file: %s', err) abort(500) - # Get metadata and colors - img_exif = mt.Metadata(img_path).yoink() - img_colors = ColorThief(img_path).get_palette(color_count=3) - + img_exif = mt.Metadata(img_path).yoink() # Get EXIF data + img_colors = ColorThief(img_path).get_palette(color_count=3) # Get color palette + # Save to database - try: + try: query = db.Posts(author_id=g.user.id, created_at=dt.utcnow(), file_name=img_name+'.'+img_ext, file_type=img_ext, image_exif=img_exif, image_colours=img_colors, - post_description=form_description, - post_alt=form_alt) - + post_description=form['description'], + post_alt=form['alt']) + db_session.add(query) db_session.commit() except Exception as err: logging.error('Could not save to database: %s', err) abort(500) - return 'Gwa Gwa' + return 'Gwa Gwa' # Return something so the browser doesn't show an error @blueprint.route('/delete/', methods=['POST']) @@ -180,11 +197,11 @@ def delete_image(image_id): try: db_session.query(db.Posts).filter_by(id=image_id).delete() - + groups = db_session.query(db.GroupJunction).filter_by(post_id=image_id).all() for group in groups: db_session.delete(group) - + db_session.commit() except Exception as err: logging.error('Could not remove from database: %s', err) @@ -205,10 +222,10 @@ def create_group(): description=request.form['description'], author_id=g.user.id, created_at=dt.utcnow()) - + db_session.add(new_group) db_session.commit() - + return ':3' @@ -220,7 +237,7 @@ def modify_group(): """ group_id = request.form['group'] image_id = request.form['image'] - + group = db_session.query(db.Groups).filter_by(id=group_id).first() if group is None: @@ -233,9 +250,9 @@ def modify_group(): db_session.add(db.GroupJunction(group_id=group_id, post_id=image_id, date_added=dt.utcnow())) elif request.form['action'] == 'remove': db_session.query(db.GroupJunction).filter_by(group_id=group_id, post_id=image_id).delete() - + db_session.commit() - + return ':3' @@ -262,10 +279,9 @@ def logfile(): Gets the log file and returns it as a JSON object """ log_dict = {} - i = 0 with open('only.log', encoding='utf-8') as file: - for line in file: + for i, line in enumerate(file): line = line.split(' : ') event = line[0].strip().split(' ') @@ -290,6 +306,4 @@ def logfile(): log_dict[i] = {'event': event_data, 'message': message_data} - i += 1 # Line number, starts at 0 - return jsonify(log_dict) diff --git a/gallery/static/js/login.js b/gallery/static/js/login.js index 0f1b4d6..f3ea168 100644 --- a/gallery/static/js/login.js +++ b/gallery/static/js/login.js @@ -5,8 +5,8 @@ function showLogin() { 'Need an account? Register!', '', '
\ - \ - \ + \ + \
' ); }; @@ -59,10 +59,10 @@ function showRegister() { 'Already have an account? Login!', '', '
\ - \ - \ - \ - \ + \ + \ + \ + \
' ); }; diff --git a/gallery/static/js/main.js b/gallery/static/js/main.js index 9a041f2..cde60fe 100644 --- a/gallery/static/js/main.js +++ b/gallery/static/js/main.js @@ -35,7 +35,7 @@ function loadOnView() { let image = lazyLoad[i]; if (image.getBoundingClientRect().top < window.innerHeight && image.getBoundingClientRect().bottom > 0) { if (!image.src) { - image.src = `/api/uploads/${image.getAttribute('data-src')}?w=400&h=400` + image.src = `/api/file/${image.getAttribute('data-src')}?w=400&h=400` } } } diff --git a/gallery/templates/groups/group.html b/gallery/templates/groups/group.html index 3e216f9..c34ed53 100644 --- a/gallery/templates/groups/group.html +++ b/gallery/templates/groups/group.html @@ -6,7 +6,7 @@