diff --git a/ycast/generic.py b/ycast/generic.py index 11c1f2d..e3e3776 100644 --- a/ycast/generic.py +++ b/ycast/generic.py @@ -1,6 +1,9 @@ import logging +import os USER_AGENT = 'YCast' +VAR_PATH = os.path.expanduser("~") + '/.ycast' +CACHE_PATH = VAR_PATH + '/cache' class Directory: @@ -31,3 +34,15 @@ def get_stationid_without_prefix(uid): logging.error("Could not extract stationid (Invalid station id length)") return None return uid[3:] + + +def get_cache_path(cache_name): + cache_path = CACHE_PATH + '/' + cache_name + try: + os.makedirs(cache_path) + except FileExistsError: + pass + except PermissionError: + logging.error("Could not create cache folders (%s) because of access permissions", cache_path) + return None + return cache_path diff --git a/ycast/server.py b/ycast/server.py index a244a2a..93acb1d 100644 --- a/ycast/server.py +++ b/ycast/server.py @@ -262,9 +262,9 @@ def get_station_icon(): if not hasattr(station, 'icon') or not station.icon: logging.warning("No icon information found for station with id '%s'", stationid) abort(404) - station_icon = station_icons.get_icon_from_url(station.icon) + station_icon = station_icons.get_icon(station) if not station_icon: - logging.error("Could not convert station icon for station with id '%s'", stationid) + logging.error("Could not get station icon for station with id '%s'", stationid) abort(404) response = make_response(station_icon) response.headers.set('Content-Type', 'image/jpeg') diff --git a/ycast/station_icons.py b/ycast/station_icons.py index 39c5aba..3c79ce7 100644 --- a/ycast/station_icons.py +++ b/ycast/station_icons.py @@ -1,6 +1,7 @@ import logging import requests import io +import os from PIL import Image @@ -8,31 +9,42 @@ import ycast.generic as generic from ycast import __version__ MAX_SIZE = 290 +CACHE_NAME = 'icons' -def get_icon_from_url(iconurl): - # TODO cache icons on disk - headers = {'User-Agent': generic.USER_AGENT + '/' + __version__} - try: - response = requests.get(iconurl, headers=headers) - except requests.exceptions.ConnectionError as err: - logging.error("Connection to station icon URL failed (%s)", err) - return None - if response.status_code != 200: - logging.error("Could not get station icon data from %s (HTML status %s)", iconurl, response.status_code) +def get_icon(station): + cache_path = generic.get_cache_path(CACHE_NAME) + if not cache_path: return None + station_icon_file = cache_path + '/' + station.id + if not os.path.exists(station_icon_file): + logging.debug("Station icon cache miss. Fetching and converting station icon for station id '%s'", station.id) + headers = {'User-Agent': generic.USER_AGENT + '/' + __version__} + try: + response = requests.get(station.icon, headers=headers) + except requests.exceptions.ConnectionError as err: + logging.error("Connection to station icon URL failed (%s)", err) + return None + if response.status_code != 200: + logging.error("Could not get station icon data from %s (HTML status %s)", station.icon, response.status_code) + return None + try: + image = Image.open(io.BytesIO(response.content)) + image = image.convert("RGB") + if image.size[0] > image.size[1]: + ratio = MAX_SIZE / image.size[0] + else: + ratio = MAX_SIZE / image.size[1] + image = image.resize((int(image.size[0] * ratio), int(image.size[1] * ratio)), Image.ANTIALIAS) + image.save(station_icon_file, format="JPEG") + except Exception as e: + logging.error("Station icon conversion error (%s)", e) + return None try: - image = Image.open(io.BytesIO(response.content)) - image = image.convert("RGB") - if image.size[0] > image.size[1]: - ratio = MAX_SIZE / image.size[0] - else: - ratio = MAX_SIZE / image.size[1] - image = image.resize((int(image.size[0] * ratio), int(image.size[1] * ratio)), Image.ANTIALIAS) - with io.BytesIO() as output_img: - image.save(output_img, format="JPEG") - image_conv = output_img.getvalue() - except Exception as e: - logging.error("Station icon conversion error (%s)", e) + with open(station_icon_file, 'rb') as file: + image_conv = file.read() + except PermissionError: + logging.error("Could not access station icon file in cache (%s) because of access permissions", + station_icon_file) return None return image_conv