From 474f8640fa9b2b50609d1aaba3322483a805f941 Mon Sep 17 00:00:00 2001 From: Honey Date: Sun, 26 Jul 2026 22:28:37 +0200 Subject: [PATCH] Picture: surface the real reason a URL download fails Couldn't reproduce the reported URL failing -- downloads fine from here (200, valid 2.5MB PNG, verified through the actual running app too) -- but found the error handling was swallowing the real exception either way: network failure and "not a valid image" both landed on the same generic message, and nothing was logged, so there was no way to tell which one it actually was, from the Pi or from here. Now: timeouts, connection/HTTP errors, and decode failures each get their own specific error message, and failures get logged via the standard logger (visible in journalctl -u paperwall.service). Also bumped the download timeout 15s -> 30s -- it's a one-time save-time download now, not a recurring poll, so there's no real cost to being more patient with a slow host or a large image. Verified against the real app: the exact reported URL, a non-image URL, and an unresolvable domain each now produce distinct, logged error messages instead of one generic one. --- web/app.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/web/app.py b/web/app.py index 4abec5b..65a8839 100644 --- a/web/app.py +++ b/web/app.py @@ -1,5 +1,6 @@ """Flask app: status/preview page + one settings form per screen + rotation settings.""" +import logging from io import BytesIO import requests @@ -9,9 +10,11 @@ from werkzeug.utils import secure_filename from screens.picture import UPLOAD_DIR +logger = logging.getLogger(__name__) + ALLOWED_IMAGE_EXTENSIONS = {"png", "jpg", "jpeg", "gif", "bmp", "webp"} PIL_FORMAT_TO_EXT = {"JPEG": "jpg", "PNG": "png", "GIF": "gif", "BMP": "bmp", "WEBP": "webp"} -PICTURE_URL_TIMEOUT = 15 +PICTURE_URL_TIMEOUT = 30 # a one-time download at save-time, not a recurring poll -- fine to be generous MAX_SCHEDULE_RULES = 4 PICTURE_URL_HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", @@ -244,12 +247,25 @@ def create_app(config, screens: dict, scheduler) -> Flask: response = requests.get(url, timeout=PICTURE_URL_TIMEOUT, headers=PICTURE_URL_HEADERS) response.raise_for_status() content = response.content - fmt = Image.open(BytesIO(content)).format - except (requests.exceptions.RequestException, OSError): fmt = None + except requests.exceptions.Timeout: + content = fmt = None + error = f"Timed out downloading that URL after {PICTURE_URL_TIMEOUT}s -- the host might be slow or blocking this server." + except requests.exceptions.RequestException as exc: + content = fmt = None + logger.warning("Picture URL download failed for %s: %s", url, exc) + error = f"Could not download that URL -- {exc}" + else: + try: + fmt = Image.open(BytesIO(content)).format + except OSError as exc: + logger.warning("Picture URL %s downloaded but didn't decode as an image: %s", url, exc) + fmt = None + ext = PIL_FORMAT_TO_EXT.get(fmt or "") if not ext: - error = "Could not download a valid image from that URL -- try a different link, or upload a file instead." + if not error: + error = "That URL doesn't look like a valid image -- try a different link, or upload a file instead." else: UPLOAD_DIR.mkdir(parents=True, exist_ok=True) # Only one picture is ever active -- clear any previous