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 }}
-{{ group.name }}
-{{ group.name }}
-{{ image.created_at }}
-