diff options
| author | Jan Tuomi <jan@jantuomi.fi> | 2026-07-12 20:50:56 +0300 |
|---|---|---|
| committer | Jan Tuomi <jan@jantuomi.fi> | 2026-07-12 20:50:56 +0300 |
| commit | d080e8a1e2241540f1dae78806b687413783e61d (patch) | |
| tree | fcd8e3fb6b99931d6be4255b0ad8f621b844669e | |
| parent | b0204b30ea5bf54010d351f314ca123514021930 (diff) | |
| -rw-r--r-- | app.py | 101 |
1 files changed, 72 insertions, 29 deletions
@@ -1,34 +1,39 @@ import os -import threading -import time -from flask.helpers import url_for -import requests import re import sqlite3 +import threading +import time +import requests from dotenv import load_dotenv +from flask.helpers import url_for + load_dotenv() -from flask import Flask, render_template, make_response, send_file -from flask_compress import Compress from collections import defaultdict -from PIL import Image from io import BytesIO +from flask import Flask, make_response, render_template, send_file +from flask_compress import Compress +from PIL import Image + # Environment config GDRIVE_FOLDER = os.environ["GDRIVE_FOLDER"] GAPI_KEY = os.environ["GAPI_KEY"] -PORT = int(os.environ.get("PORT", 5000)) -THUMBNAIL_DIR = "thumbnails" -DB_FILE = "data/sqlite.db" -ISO_DATE_REGEX = re.compile(r"^\d{4}-\d{2}-\d{2}") +DATA_PREFIX = os.environ.get("DATA_PREFIX", ".") AUTHOR = os.environ["AUTHOR"] IG_LINK = os.environ.get("IG_LINK", None) BASE_URL = os.environ["BASE_URL"] +THUMBNAIL_DIR = os.path.join(DATA_PREFIX, "thumbnails") +DB_FILE = os.path.join(DATA_PREFIX, "sqlite.db") + +ISO_DATE_REGEX = re.compile(r"^\d{4}-\d{2}-\d{2}") + # SQLite connection configuration per kerkour.com/sqlite-for-servers _write_lock = threading.Lock() + def _configure_connection(conn): conn.execute("PRAGMA journal_mode = WAL") conn.execute("PRAGMA busy_timeout = 5000") @@ -37,16 +42,19 @@ def _configure_connection(conn): conn.execute("PRAGMA foreign_keys = true") conn.execute("PRAGMA temp_store = memory") + def get_read_db(): conn = sqlite3.connect(DB_FILE) _configure_connection(conn) return conn + def get_write_db(): conn = sqlite3.connect(DB_FILE, isolation_level=None) _configure_connection(conn) return conn + # Initialize DB def init_db(): os.makedirs(os.path.dirname(DB_FILE), exist_ok=True) @@ -65,12 +73,18 @@ def init_db(): finally: conn.close() + # Get image files from the given Google Drive folder (non-recursive) def list_drive_files(folder_id): url = "https://www.googleapis.com/drive/v3/files" image_mime_types = { - "image/jpeg", "image/png", "image/webp", "image/gif", - "image/bmp", "image/tiff", "image/jpg" + "image/jpeg", + "image/png", + "image/webp", + "image/gif", + "image/bmp", + "image/tiff", + "image/jpg", } params = { "q": f"'{folder_id}' in parents and trashed = false", @@ -82,6 +96,7 @@ def list_drive_files(folder_id): files = resp.json().get("files", []) return [f for f in files if f.get("mimeType") in image_mime_types] + # Download image and generate thumbnail def download_and_create_thumbnail(file_id): url = f"https://drive.google.com/uc?id={file_id}" @@ -98,6 +113,7 @@ def download_and_create_thumbnail(file_id): img.save(path, format="JPEG") return path + # Background thread to sync Drive files def save_files_periodically(): while True: @@ -114,26 +130,42 @@ def save_files_periodically(): for f in drive_files: current_ids.add(f["id"]) date_match = ISO_DATE_REGEX.match(f["name"]) - date = date_match.group(0) if date_match else f["createdTime"][:10] + date = ( + date_match.group(0) if date_match else f["createdTime"][:10] + ) - cur.execute("SELECT description FROM files WHERE id = ?", (f["id"],)) + cur.execute( + "SELECT description FROM files WHERE id = ?", (f["id"],) + ) row = cur.fetchone() if row is None: thumb_path = download_and_create_thumbnail(f["id"]) - cur.execute(""" + cur.execute( + """ INSERT OR IGNORE INTO files (id, name, description, date, thumbnail) VALUES (?, ?, ?, ?, ?) - """, (f["id"], f["name"], f.get("description", ""), date, thumb_path)) + """, + ( + f["id"], + f["name"], + f.get("description", ""), + date, + thumb_path, + ), + ) else: existing_description = row[0] or "" new_description = f.get("description", "") if existing_description != new_description: - cur.execute(""" + cur.execute( + """ UPDATE files SET name = ?, description = ?, date = ? WHERE id = ? - """, (f["name"], new_description, date, f["id"])) + """, + (f["name"], new_description, date, f["id"]), + ) # Delete removed files cur.execute("SELECT id, thumbnail FROM files") @@ -155,6 +187,7 @@ def save_files_periodically(): print("Error updating file list:", e) time.sleep(60) + def create_app() -> Flask: init_db() @@ -166,38 +199,45 @@ def create_app() -> Flask: Compress(app) return app + # Setup app = create_app() + # Route: Gallery @app.route("/") def gallery(): conn = get_read_db() try: cur = conn.cursor() - cur.execute("SELECT id, name, description, date, thumbnail FROM files ORDER BY date DESC") + cur.execute( + "SELECT id, name, description, date, thumbnail FROM files ORDER BY date DESC" + ) files = cur.fetchall() finally: conn.close() grouped = defaultdict(list) for file in files: - grouped[file[3]].append({ - "id": file[0], - "name": file[1], - "description": file[2], - "thumbnail": file[4], - }) + grouped[file[3]].append( + { + "id": file[0], + "name": file[1], + "description": file[2], + "thumbnail": file[4], + } + ) sorted_dates = sorted(grouped.keys(), reverse=True) if len(files) > 0: - cover_img_path = url_for("serve_thumbnail", filename=files[0][4].split('/')[-1]) + cover_img_path = url_for("serve_thumbnail", filename=files[0][4].split("/")[-1]) cover_img_url = BASE_URL + cover_img_path else: cover_img_url = None - return render_template("gallery.html", + return render_template( + "gallery.html", grouped=grouped, dates=sorted_dates, author=AUTHOR, @@ -206,6 +246,7 @@ def gallery(): base_url=BASE_URL, ) + @app.route("/thumbnails/<filename>") def serve_thumbnail(filename): path = os.path.join(THUMBNAIL_DIR, filename) @@ -213,5 +254,7 @@ def serve_thumbnail(filename): return "Not Found", 404 response = make_response(send_file(path)) - response.headers["Cache-Control"] = "public, max-age=86400, stale-while-revalidate=604800" + response.headers["Cache-Control"] = ( + "public, max-age=86400, stale-while-revalidate=604800" + ) return response |
