From 17edacd3ac08ec9f4fa161e3fb0afb6032d17e71 Mon Sep 17 00:00:00 2001 From: Burer Date: Thu, 25 Dec 2025 10:32:22 +0200 Subject: [PATCH] feat/flask: split app into modules feat/flask: !fix strange code to previous --- app.py | 209 +++++++------------------------------------ modules/__init__.py | 1 + modules/autobuild.py | 89 ++++++++++++++++++ modules/helpers.py | 23 +++++ modules/locales.py | 51 +++++++++++ 5 files changed, 194 insertions(+), 179 deletions(-) create mode 100644 modules/__init__.py create mode 100644 modules/autobuild.py create mode 100644 modules/helpers.py create mode 100644 modules/locales.py diff --git a/app.py b/app.py index 0d79cff..be59d0f 100644 --- a/app.py +++ b/app.py @@ -1,22 +1,10 @@ import re -import threading -import time - -from os import path, listdir -from datetime import date -from configparser import ConfigParser +import datetime from sass import compile as compile_sass -from htmlmin import minify as minify_html +from flask import Flask, redirect, request, url_for, g, Response -from flask import Flask, redirect, render_template, request, url_for, g, Response - - -# ---------- ENV VARS -------------------------------------------------------- - - -STATUS_URL = "https://builds.kolibrios.org/status.html" -STATUS_FETCH_DELAY_SEC = 300 # 5 minutes +from modules import autobuild, locales, helpers # ---------- APP CONFIG ------------------------------------------------------ @@ -24,6 +12,8 @@ STATUS_FETCH_DELAY_SEC = 300 # 5 minutes app = Flask(__name__) +locales.ensure_loaded() + if app.debug: # CSS Compilation and minification css = compile_sass(filename="static/style.scss", output_style="compressed") @@ -40,163 +30,27 @@ if app.debug: f.write(js) -# ---------- LATEST COMMIT DATE (MINIMAL ADD-ON) ----------------------------- - - -autobuild_date = "DD.MM.YYYY" -autobuild_vers = "0.0.0.0+0000-0000000" - - -def _refresh_build_date_once(): - global autobuild_date, autobuild_vers - try: - from urllib.request import Request, urlopen - import re - - req = Request( - STATUS_URL, - headers={ - "User-Agent": "Mozilla/5.0", - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", - }, - ) - with urlopen(req, timeout=10) as r: - html = r.read().decode( - r.headers.get_content_charset() or "utf-8", "replace" - ) - - rows = re.findall(r"(]*>.*?)", html, flags=re.I | re.S) - if not rows: - return - - last_commit_ver = None - - for row in rows: - cls = re.search(r'class\s*=\s*"([^"]*)"', row, flags=re.I) - classes = cls.group(1).lower() if cls else "" - - text = re.sub(r"<[^>]+>", " ", row) - - if "commit" in classes: - mver = re.search( - r"\b(\d+\.\d+\.\d+\.\d+\+\d{3,8}-[0-9a-fA-F]{7,40})\b", row - ) - if mver: - last_commit_ver = mver.group(1) - - elif "success" in classes: - mts = re.search( - r"\b(\d{4})\.(\d{2})\.(\d{2})\s+\d{2}:\d{2}:\d{2}\b", text - ) - if not mts: - mds = re.search(r"\b(\d{2})\.(\d{2})\.(\d{4})\b", text) - if mds: - autobuild_date = f"{mds.group(1)}.{mds.group(2)}.{mds.group(3)}" - else: - return - else: - y, mo, d = mts.groups() - autobuild_date = f"{d}.{mo}.{y}" - - if last_commit_ver: - autobuild_vers = last_commit_ver - return - - except Exception: - pass - - -def _updater_loop(): - while True: - _refresh_build_date_once() - time.sleep(STATUS_FETCH_DELAY_SEC) - - -_started = False -_refresh_build_date_once() -_updater_lock = threading.Lock() - - @app.before_request def _ensure_updater_started(): - global _started - if not _started: - with _updater_lock: - if not _started: - _started = True - threading.Thread(target=_updater_loop, daemon=True).start() - - -@app.context_processor -def _inject_autobuild_vers(): - return {'autobuild_vers': autobuild_vers} - - -@app.context_processor -def _inject_autobuild_date(): - return {'autobuild_date': autobuild_date} - - -# ---------- LOCALES FUNCTIONS ----------------------------------------------- - - -def load_all_locales(): - translations = {} - locales_dir = "locales" - - locales_code_default = ('en', 'ru', 'es') - locales_code_extra = [] - locales_code = () - - for filename in listdir(locales_dir): - if filename.endswith(".ini"): - cp = ConfigParser() - lang = path.splitext(filename)[0] - with open(path.join(locales_dir, filename), encoding="utf-8") as f: - cp.read_file(f) - - if lang not in locales_code_default: - locales_code_extra.append(lang) - - translations[lang] = { - section: dict(cp[section]) for section in cp.sections() - } - - locales_code = locales_code_default + tuple(sorted(locales_code_extra)) - locales_name = {l: translations[l]['title']['language'] for l in locales_code} - - return translations, locales_name, locales_code - - -translations, locales_name, locales_code = load_all_locales() - - -# ---------- HELPER FUNCTIONS ------------------------------------------------ - - -def get_best_lang(): - return request.accept_languages.best_match(locales_code) or "en" - - -def render_localized_template(lang, template_name): - if lang not in locales_code: - return redirect(url_for("index", lang=get_best_lang())) - - return minify_html( - render_template( - template_name, - year=date.today().year, - ), - remove_empty_space=True, - ) + autobuild.ensure_started() @app.before_request def before_request(): if args := request.view_args: g.locale = args.get("lang", "en") - g.translations = translations.get(g.locale, get_best_lang()) - g.locales_name = locales_name + g.translations = locales.translations.get(g.locale, helpers.get_best_lang()) + g.locales_name = locales.locales_name + + +@app.context_processor +def _inject_autobuild_vers(): + return {'autobuild_vers': autobuild.autobuild_vers} + + +@app.context_processor +def _inject_autobuild_date(): + return {'autobuild_date': autobuild.autobuild_date} @app.context_processor @@ -216,25 +70,22 @@ def inject_translations(): return {'_': translate} -# ---------- MAIN PAGES ------------------------------------------------------ +# ---------- ROUTES ------------------------------------------------------- @app.route("/") def home(): - return redirect(url_for("index", lang=get_best_lang())) + return redirect(url_for("index", lang=helpers.get_best_lang())) @app.route("/") def index(lang): - return render_localized_template(lang, "index.html") + return helpers.render_localized_template(lang, "index.html") @app.route("//download") def download(lang): - return render_localized_template(lang, "download.html") - - -# ---------- ROBOTS.TXT + SITEMAP.XML ---------------------------------------- + return helpers.render_localized_template(lang, "download.html") @app.route("/robots.txt") @@ -251,10 +102,10 @@ def robots_txt(): @app.route("/sitemap.xml") def sitemap_xml(): base_url = request.url_root.rstrip("/") - today = date.today().isoformat() + today = datetime.date.today().isoformat() urls = [] - for lang in locales_code: + for lang in locales.locales_code: urls.append(f"{base_url}/{lang}") urls.append(f"{base_url}/{lang}/download") @@ -265,12 +116,12 @@ def sitemap_xml(): for loc in urls: xml_lines.extend( [ - f" ", - f" {loc}", - f" {today}", - f" monthly", - f" 0.8", - f" ", + f" ", + f" {loc}", + f" {today}", + f" monthly", + f" 0.8", + f" ", ] ) xml_lines.append("") diff --git a/modules/__init__.py b/modules/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/modules/__init__.py @@ -0,0 +1 @@ + diff --git a/modules/autobuild.py b/modules/autobuild.py new file mode 100644 index 0000000..56df4cf --- /dev/null +++ b/modules/autobuild.py @@ -0,0 +1,89 @@ +import re +import threading +import time + + +STATUS_URL = "https://builds.kolibrios.org/status.html" +STATUS_SEC = 300 # refetch each 5 minutes + +autobuild_date = "DD.MM.YYYY" +autobuild_vers = "0.0.0.0+0000-0000000" + +_started = False +_updater_lock = threading.Lock() + + +def _refresh_build_date_once(): + global autobuild_date, autobuild_vers + try: + from urllib.request import Request, urlopen + + req = Request( + STATUS_URL, + headers={ + "User-Agent": "Mozilla/5.0", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + }, + ) + with urlopen(req, timeout=10) as r: + html = r.read().decode( + r.headers.get_content_charset() or "utf-8", "replace" + ) + + rows = re.findall(r"(]*>.*?)", html, flags=re.I | re.S) + if not rows: + return + + last_commit_ver = None + + for row in rows: + cls = re.search(r'class\s*=\s*"([^"]*)"', row, flags=re.I) + classes = cls.group(1).lower() if cls else "" + + text = re.sub(r"<[^>]+>", " ", row) + + if "commit" in classes: + mver = re.search( + r"\b(\d+\.\d+\.\d+\.\d+\+\d{3,8}-[0-9a-fA-F]{7,40})\b", row + ) + if mver: + last_commit_ver = mver.group(1) + + elif "success" in classes: + mts = re.search( + r"\b(\d{4})\.(\d{2})\.(\d{2})\s+\d{2}:\d{2}:\d{2}\b", text + ) + if not mts: + mds = re.search(r"\b(\d{2})\.(\d{2})\.(\d{4})\b", text) + if mds: + autobuild_date = f"{mds.group(1)}.{mds.group(2)}.{mds.group(3)}" + else: + return + else: + y, mo, d = mts.groups() + autobuild_date = f"{d}.{mo}.{y}" + + if last_commit_ver: + autobuild_vers = last_commit_ver + return + + except Exception: + pass + + +def _updater_loop(): + while True: + _refresh_build_date_once() + time.sleep(STATUS_SEC) + + +def ensure_started(): + global _started + with _updater_lock: + if _started: + return + threading.Thread(target=_updater_loop, daemon=True).start() + _started = True + + +_refresh_build_date_once() diff --git a/modules/helpers.py b/modules/helpers.py new file mode 100644 index 0000000..d6fa2b4 --- /dev/null +++ b/modules/helpers.py @@ -0,0 +1,23 @@ +from datetime import date + +from flask import redirect, render_template, request, url_for +from htmlmin import minify as minify_html + +from modules import locales + + +def get_best_lang(): + return request.accept_languages.best_match(locales.locales_code) or "en" + + +def render_localized_template(lang, template_name): + if lang not in locales.locales_code: + return redirect(url_for("index", lang=get_best_lang())) + + return minify_html( + render_template( + template_name, + year=date.today().year, + ), + remove_empty_space=True, + ) diff --git a/modules/locales.py b/modules/locales.py new file mode 100644 index 0000000..c9f698f --- /dev/null +++ b/modules/locales.py @@ -0,0 +1,51 @@ +from os import path, listdir +from configparser import ConfigParser +import threading + + +translations = {} +locales_name = {} +locales_code = () + +_loaded = False +_load_lock = threading.Lock() + + +def load_all_locales(): + new_translations = {} + locales_dir = "locales" + + locales_code_default = ("en", "ru", "es") + locales_code_extra = [] + new_locales_code = () + + for filename in listdir(locales_dir): + if filename.endswith(".ini"): + cp = ConfigParser() + lang = path.splitext(filename)[0] + with open(path.join(locales_dir, filename), encoding="utf-8") as f: + cp.read_file(f) + + if lang not in locales_code_default: + locales_code_extra.append(lang) + + new_translations[lang] = { + section: dict(cp[section]) for section in cp.sections() + } + + new_locales_code = locales_code_default + tuple(sorted(locales_code_extra)) + new_locales_name = { + locale_code: new_translations[locale_code]["title"]["language"] + for locale_code in new_locales_code + } + + return new_translations, new_locales_name, new_locales_code + + +def ensure_loaded(): + global translations, locales_name, locales_code, _loaded + with _load_lock: + if _loaded: + return + translations, locales_name, locales_code = load_all_locales() + _loaded = True