diff --git a/gallery/db.py b/gallery/db.py index fc68d75..ef00ff3 100644 --- a/gallery/db.py +++ b/gallery/db.py @@ -13,6 +13,7 @@ USER_DIR = platformdirs.user_config_dir('onlylegs') DB_PATH = os.path.join(USER_DIR, 'gallery.sqlite') +# In the future, I want to add support for other databases # engine = create_engine('postgresql://username:password@host:port/database_name', echo=False) # engine = create_engine('mysql://username:password@host:port/database_name', echo=False) engine = create_engine(f'sqlite:///{DB_PATH}', echo=False) @@ -59,6 +60,7 @@ class Posts (base): # pylint: disable=too-few-public-methods, C0103 post_alt = Column(String, nullable=False) junction = relationship('GroupJunction', backref='posts') + thumbnail = relationship('Thumbnails', backref='posts') class Thumbnails (base): # pylint: disable=too-few-public-methods, C0103 @@ -70,7 +72,8 @@ class Thumbnails (base): # pylint: disable=too-few-public-methods, C0103 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) + resolution = Column(PickleType, nullable=False) + post_id = Column(Integer, ForeignKey('posts.id')) class Groups (base): # pylint: disable=too-few-public-methods, C0103 diff --git a/gallery/routes/api.py b/gallery/routes/api.py index e613c27..7677487 100644 --- a/gallery/routes/api.py +++ b/gallery/routes/api.py @@ -1,26 +1,24 @@ """ Onlylegs - API endpoints -Used internally by the frontend and possibly by other applications """ from uuid import uuid4 import os import pathlib -import io +import platformdirs import logging from datetime import datetime as dt -from flask import (Blueprint, send_from_directory, send_file, - abort, flash, jsonify, request, g, current_app) +from flask import Blueprint, send_from_directory, abort, flash, jsonify, request, g, current_app from werkzeug.utils import secure_filename from colorthief import ColorThief -from PIL import Image, ImageOps, ImageFilter from sqlalchemy.orm import sessionmaker from gallery.auth import login_required from gallery import db from gallery.utils import metadata as mt +from gallery.utils.generate_image import ImageGenerator blueprint = Blueprint('api', __name__, url_prefix='/api') @@ -33,14 +31,8 @@ def file(file_name): """ Returns a file from the uploads folder r for resolution, 400x400 or thumb for thumbnail - 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 res = request.args.get('r', default=None, type=str) # Type of file (thumb, etc) - filtered = request.args.get('f', default=False, type=bool) # Whether to apply filters # pylint: disable=W0612 - blur = request.args.get('b', default=False, type=bool) # Whether to force blur - file_name = secure_filename(file_name) # Sanitize file name # if no args are passed, return the raw file @@ -49,65 +41,13 @@ def file(file_name): abort(404) return send_from_directory(current_app.config['UPLOAD_FOLDER'], file_name) - - buff = io.BytesIO() - img = None # Image object to be set - - try: # Open image and set extension - img = Image.open(os.path.join(current_app.config['UPLOAD_FOLDER'], file_name)) - except FileNotFoundError: # FileNotFound is raised if the file doesn't exist - logging.error('File not found: %s', file_name) + + thumb = ImageGenerator.thumbnail(file_name, res) + + if not thumb: abort(404) - except OSError as err: # OSError is raised if the file is broken or corrupted - logging.error('Possibly broken image %s, error: %s', file_name, err) - abort(500) - - 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 - - img = ImageOps.exif_transpose(img) # Rotate image based on EXIF data - - # Todo: If type is thumb(nail), return from database instead of file system pylint: disable=W0511 - # as it's faster than generating a new thumbnail on every request - if res: - if res in ['thumb', 'thumbnail']: - width, height = 400, 400 - elif res in ['prev', 'preview']: - width, height = 1920, 1080 - else: - try: - width, height = res.split('x') - width = int(width) - height = int(height) - except ValueError: - abort(400) - - img.thumbnail((width, height), Image.LANCZOS) - - # Todo: If the image has a NSFW tag, blur image for example pylint: disable=W0511 - # if filtered: - # pass - - # If forced to blur, blur image - if blur: - img = img.filter(ImageFilter.GaussianBlur(20)) - - try: - img.save(buff, img_ext, icc_profile=img_icc) - except OSError: - # This usually happens when saving a JPEG with an ICC profile, - # so we convert to RGB and try again - 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_name, err) - abort(500) - - 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) + + return send_from_directory(os.path.dirname(thumb), os.path.basename(thumb)) @blueprint.route('/upload', methods=['POST']) @@ -171,34 +111,37 @@ def delete_image(image_id): """ img = db_session.query(db.Posts).filter_by(id=image_id).first() + # Check if image exists and if user is allowed to delete it (author) if img is None: abort(404) if img.author_id != g.user.id: abort(403) + # Delete file try: os.remove(os.path.join(current_app.config['UPLOAD_FOLDER'],img.file_name)) except FileNotFoundError: - # File was already deleted or doesn't exist logging.warning('File not found: %s, already deleted or never existed', img.file_name) - except Exception as err: - logging.error('Could not remove file: %s', err) - abort(500) - try: - db_session.query(db.Posts).filter_by(id=image_id).delete() + # Delete cached files + cache_path = os.path.join(platformdirs.user_config_dir('onlylegs'), 'cache') + cache_name = img.file_name.rsplit('.')[0] + for file in pathlib.Path(cache_path).glob(cache_name + '*'): + os.remove(file) - groups = db_session.query(db.GroupJunction).filter_by(post_id=image_id).all() - for group in groups: - db_session.delete(group) + # Delete from database + db_session.query(db.Posts).filter_by(id=image_id).delete() - db_session.commit() - except Exception as err: - logging.error('Could not remove from database: %s', err) - abort(500) + # Remove all entries in junction table + groups = db_session.query(db.GroupJunction).filter_by(post_id=image_id).all() + for group in groups: + db_session.delete(group) + + # Commit all changes + db_session.commit() logging.info('Removed image (%s) %s', image_id, img.file_name) - flash(['Image was all in Le Head!', 1]) + flash(['Image was all in Le Head!', '1']) return 'Gwa Gwa' diff --git a/gallery/templates/groups/group.html b/gallery/templates/groups/group.html index ea7cefe..db54a1d 100644 --- a/gallery/templates/groups/group.html +++ b/gallery/templates/groups/group.html @@ -30,7 +30,7 @@ {% else %} @@ -58,7 +58,7 @@

{{ image.created_at }}

- {{ image.post_alt }} + {{ image.post_alt }} {% endfor %} diff --git a/gallery/templates/groups/list.html b/gallery/templates/groups/list.html index 3e721d9..0903893 100644 --- a/gallery/templates/groups/list.html +++ b/gallery/templates/groups/list.html @@ -29,7 +29,7 @@

{{ group.name }}

- + {% else %} @@ -37,7 +37,7 @@

{{ group.name }}

- +
{% endif %} {% endfor %} diff --git a/gallery/templates/image.html b/gallery/templates/image.html index 27a7a92..c1a7ef6 100644 --- a/gallery/templates/image.html +++ b/gallery/templates/image.html @@ -91,6 +91,10 @@ alt="{{ image.post_alt }}" onload="imgFade(this)" style="opacity:0;" onerror="this.src='{{ url_for('static', filename='error.png')}}'" + {% if "File" in image.image_exif %} + width="{{ image.image_exif.File.Width.raw }}" + height="{{ image.image_exif.File.Height.raw }}" + {% endif %} /> diff --git a/gallery/templates/index.html b/gallery/templates/index.html index b3670ec..bb4a89d 100644 --- a/gallery/templates/index.html +++ b/gallery/templates/index.html @@ -41,7 +41,7 @@

{{ image.created_at }}

- {{ image.post_alt }} + {{ image.post_alt }} {% endfor %} diff --git a/gallery/templates/layout.html b/gallery/templates/layout.html index 85fe4f2..85aa587 100644 --- a/gallery/templates/layout.html +++ b/gallery/templates/layout.html @@ -61,7 +61,7 @@