diff --git a/conf/nginx/yunohost_admin.conf.inc b/conf/nginx/yunohost_admin.conf.inc index b5eff7a5e..84c49d30b 100644 --- a/conf/nginx/yunohost_admin.conf.inc +++ b/conf/nginx/yunohost_admin.conf.inc @@ -19,6 +19,10 @@ location /yunohost/admin/ { more_set_headers "Cache-Control: no-store, no-cache, must-revalidate"; } + location /yunohost/admin/applogos/ { + alias /usr/share/yunohost/applogos/; + } + more_set_headers "Content-Security-Policy: upgrade-insecure-requests; default-src 'self'; connect-src 'self' https://paste.yunohost.org wss://$host; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval'; object-src 'none'; img-src 'self' data:;"; more_set_headers "Content-Security-Policy-Report-Only:"; } diff --git a/src/app.py b/src/app.py index af7822308..842c71b80 100644 --- a/src/app.py +++ b/src/app.py @@ -71,6 +71,7 @@ from yunohost.app_catalog import ( # noqa app_catalog, app_search, _load_apps_catalog, + APPS_CATALOG_LOGOS, ) logger = getActionLogger("yunohost.app") @@ -151,6 +152,9 @@ def app_info(app, full=False, upgradable=False): absolute_app_name, _ = _parse_app_instance_name(app) from_catalog = _load_apps_catalog()["apps"].get(absolute_app_name, {}) + # Check if $app.png exists in the app logo folder, this is a trick to be able to easily customize the logo + # of an app just by creating $app.png (instead of the hash.png) in the corresponding folder + ret["logo"] = app if os.path.exists(f"{APPS_CATALOG_LOGOS}/{app}.png") else from_catalog.get("logo_hash") ret["upgradable"] = _app_upgradable({**ret, "from_catalog": from_catalog}) if ret["upgradable"] == "yes": diff --git a/src/app_catalog.py b/src/app_catalog.py index 22a878579..79d02e8f1 100644 --- a/src/app_catalog.py +++ b/src/app_catalog.py @@ -18,6 +18,7 @@ # import os import re +import hashlib from moulinette import m18n from moulinette.utils.log import getActionLogger @@ -36,6 +37,7 @@ from yunohost.utils.error import YunohostError logger = getActionLogger("yunohost.app_catalog") APPS_CATALOG_CACHE = "/var/cache/yunohost/repo" +APPS_CATALOG_LOGOS = "/usr/share/yunohost/applogos" APPS_CATALOG_CONF = "/etc/yunohost/apps_catalog.yml" APPS_CATALOG_API_VERSION = 3 APPS_CATALOG_DEFAULT_URL = "https://app.yunohost.org/default" @@ -182,6 +184,9 @@ def _update_apps_catalog(): logger.debug("Initialize folder for apps catalog cache") mkdir(APPS_CATALOG_CACHE, mode=0o750, parents=True, uid="root") + if not os.path.exists(APPS_CATALOG_LOGOS): + mkdir(APPS_CATALOG_LOGOS, mode=0o755, parents=True, uid="root") + for apps_catalog in apps_catalog_list: if apps_catalog["url"] is None: continue @@ -212,6 +217,37 @@ def _update_apps_catalog(): raw_msg=True, ) + # Download missing app logos + logos_to_download = [] + for app, infos in apps_catalog_content["apps"].items(): + logo_hash = infos.get("logo_hash") + if not logo_hash or os.path.exists(f"{APPS_CATALOG_LOGOS}/{logo_hash}.png"): + continue + logos_to_download.append(logo_hash) + + if len(logos_to_download) > 20: + logger.info(f"(Will fetch {len(logos_to_download)} logos, this may take a couple minutes)") + + import requests + from multiprocessing.pool import ThreadPool + + def fetch_logo(logo_hash): + try: + r = requests.get(f"{apps_catalog['url']}/v{APPS_CATALOG_API_VERSION}/logos/{logo_hash}.png", timeout=10) + assert r.status_code == 200, f"Got status code {r.status_code}, expected 200" + if hashlib.sha256(r.content).hexdigest() != logo_hash: + raise Exception(f"Found inconsistent hash while downloading logo {logo_hash}") + open(f"{APPS_CATALOG_LOGOS}/{logo_hash}.png", "wb").write(r.content) + return True + except Exception as e: + logger.debug(f"Failed to download logo {logo_hash} : {e}") + return False + + results = ThreadPool(8).imap_unordered(fetch_logo, logos_to_download) + for result in results: + # Is this even needed to iterate on the results ? + pass + logger.success(m18n.n("apps_catalog_update_success"))