From f4c778af7abbde3f70bf23060593bebc10f5039e Mon Sep 17 00:00:00 2001 From: pQu4k3r Date: Mon, 7 Sep 2026 21:07:41 +0000 Subject: [PATCH 1/4] Fix remaining low-severity cleanup from full repository audit - unit_manager.py: UnitSettingsSimple's "left" action called self.down instead of self.up, so the metric preset (labeled "Prev") could never be reached with the left key. - foreca_map_api.py: removed a dead, broken download_tile_grid_async method referencing attributes that don't exist on this class (self.api, self.center_lat, self.merge_tile_grid, etc.) - a copy-paste leftover with zero callers; the real, working version lives on the screen class in foreca_map_viewer.py. Also dropped the now-unused Thread/PIL.Image imports. - CONTROL/prerm: updated a hardcoded stale version string ("v.1.0.0") to match the current plugin version (1.3.1). - README.md: documented installer.sh as the recommended automatic install method (handles dependencies), alongside the existing manual copy instructions. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01UPpumFb2PP21ATpDwJBYBB --- CONTROL/prerm | 2 +- README.md | 12 ++++- .../Extensions/Foreca1/foreca_map_api.py | 53 ------------------- .../Extensions/Foreca1/unit_manager.py | 2 +- 4 files changed, 13 insertions(+), 56 deletions(-) diff --git a/CONTROL/prerm b/CONTROL/prerm index cdbecfbd..42df9acd 100755 --- a/CONTROL/prerm +++ b/CONTROL/prerm @@ -1,3 +1,3 @@ #!/bin/bash -echo 'Removing package : Foreca v.1.0.0' +echo 'Removing package : Foreca v.1.3.1' exit 0 diff --git a/README.md b/README.md index 261310bb..fc3c549e 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,15 @@ ## Installation +### Automatic (recommended) +Download and run `installer.sh` directly on your Enigma2 box. It detects your image/OS, installs the required dependencies (requests, Pillow, etc.), and copies the plugin files for you: +``` +wget --no-check-certificate 'https://github.com/Belfagor2005/ForecaOne/raw/main/installer.sh' -O installer.sh +chmod +x installer.sh +./installer.sh +``` + +### Manual 1. Copy the `Foreca1` folder to your Enigma2 plugins directory: ``` /usr/lib/enigma2/python/Plugins/Extensions/ @@ -213,7 +222,8 @@ ``` chmod -R 755 /usr/lib/enigma2/python/Plugins/Extensions/Foreca1 ``` -3. Restart Enigma2 or the plugin menu to make the plugin visible. +3. Install the required dependencies yourself (`requests`, `Pillow`) using your image's package manager (`opkg`/`apt-get`). +4. Restart Enigma2 or the plugin menu to make the plugin visible. ## Initial Configuration diff --git a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/foreca_map_api.py b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/foreca_map_api.py index ac805daa..e0aadf5d 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/foreca_map_api.py +++ b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/foreca_map_api.py @@ -10,10 +10,8 @@ from json import load, dump from os import remove, listdir, unlink from os.path import exists, join, isfile, getmtime -from threading import Thread import requests -from PIL import Image from . import ( DEBUG, @@ -371,57 +369,6 @@ def get_tile(self, layer_id, timestamp, zoom, x, y, unit_system='metric'): print(f"[Foreca1MapAPI] Tile download exception: {e}") return None - def download_tile_grid_async(self, timestamp, callback): - def download_thread(): - cx, cy = self.latlon_to_tile( - self.center_lat, self.center_lon, self.zoom_level) - offset_cols = self.grid_cols // 2 - offset_rows = self.grid_rows // 2 - - tile_paths = [] - for dx in range(-offset_cols, offset_cols + 1): - for dy in range(-offset_rows, offset_rows + 1): - tx = cx + dx - ty = cy + dy - path = self.api.get_tile( - self.layer_id, - timestamp, - self.zoom_level, - tx, ty, - self.unit_system - ) - - if path and exists(path): - try: - if DEBUG: - with Image.open(path) as img: - # Debug only, if it fails skip - print( - f"[DEBUG] Tile zoom={self.zoom_level} ({tx},{ty}) size: {img.size}") - tile_paths.append( - (dx + offset_cols, dy + offset_rows, path)) - except Exception as e: - print( - f"[Foreca1] Corrupted tile, skipped: {path} - {e}") - # Remove corrupted file to avoid future reuse - try: - remove(path) - except BaseException: - pass - - if len(tile_paths) > 0: - merged = self.merge_tile_grid(tile_paths) - if merged and callback: - callback(merged) - else: - if DEBUG: - print("[Foreca1] No valid tiles downloaded") - from twisted.internet import reactor - reactor.callFromThread(self._show_no_tiles_error) - callback(None) - - Thread(target=download_thread).start() - def check_credentials(self): """Check if credentials are configured""" return bool(self.user and self.password) diff --git a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/unit_manager.py b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/unit_manager.py index e932caf3..b5818e7d 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/unit_manager.py +++ b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/unit_manager.py @@ -341,7 +341,7 @@ def __init__(self, session, unit_manager): "red": (self.exit, _("Exit")), "green": (self.save, _("Save")), "blue": (self.open_advanced, _("Advanced")), - "left": (self.down, _("Prev")), + "left": (self.up, _("Prev")), "right": (self.down, _("Next")), "up": (self.up, _("Prev")), "down": (self.down, _("Next")) From 2eddea039da09093cc1f948523ba8b6999eaaa07 Mon Sep 17 00:00:00 2001 From: pQu4k3r Date: Mon, 7 Sep 2026 21:16:51 +0000 Subject: [PATCH 2/4] Fix remaining excluded audit items: UI freeze, Python 3 requirement, HTTPS - plugin.py: fully backgrounded _load_favorite (the core weather-refresh path, run on every arrow-key press and favorite selection), which previously ran all network calls synchronously on the UI thread and froze the whole Enigma2 UI. Network fetching now runs in a background thread; only the final widget updates run via reactor.callFromThread. Added a sequence guard so a superseded request (from rapid key presses) can't overwrite a newer one with stale data. _update_fav_button_names had the same blocking-network-calls-on-UI- thread problem (3x get_location_by_id) and got the same treatment. - installer.sh: removed the dead Python 2 code path. README.md and the Yocto recipe (enigma2-plugin-extensions-foreca-one.bb) already declare Python 3 only, so branching for "Python2 image detected" just installed the wrong package names and gave a false success message on Python-2-only systems where the plugin could never actually load (f-strings are used throughout). Now requires python3 explicitly and fails with a clear error otherwise. Also fixed the Python-version check itself: it ran `python --version`, which fails outright on images that only ship a `python3` binary (no unversioned `python`), misdetecting them as Python 2. Also fixed a separate pre-existing bug: Packagepillow was computed but install_pkg was never actually called on it, so installer.sh never really installed Pillow despite several modules importing it. - CONTROL/preinst: same Python 3 requirement fix as installer.sh. - slideshow.py: switched the wetterkontor map image fetch from HTTP to HTTPS; the existing try/except already handles connection failures gracefully, so a TLS-incompatible host would fail the same way any other network hiccup already does. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01UPpumFb2PP21ATpDwJBYBB --- CONTROL/preinst | 7 ++-- installer.sh | 27 ++++-------- .../Plugins/Extensions/Foreca1/plugin.py | 42 ++++++++++++++++++- .../Plugins/Extensions/Foreca1/slideshow.py | 2 +- 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/CONTROL/preinst b/CONTROL/preinst index 6b86c125..e2e81129 100755 --- a/CONTROL/preinst +++ b/CONTROL/preinst @@ -31,14 +31,15 @@ else echo "✓ No previous installation found" fi -# Determine python command and install dependencies +# ForecaOne requires Python 3; install dependencies accordingly echo "Installing required dependencies..." if [ -e "/usr/bin/python3" ]; then PY="python3" echo "✓ Using Python 3" else - PY="python" - echo "✓ Using Python 2.7" + echo "ERROR: Python 3 is required but was not found (/usr/bin/python3 missing)." + echo "ForecaOne only supports Python 3." + exit 1 fi # Update package list and install dependencies diff --git a/installer.sh b/installer.sh index d044313b..cb3b044e 100755 --- a/installer.sh +++ b/installer.sh @@ -92,23 +92,14 @@ if ! command -v wget >/dev/null 2>&1; then esac fi -if python --version 2>&1 | grep -q '^Python 3\.'; then - echo "Python3 image detected" - PYTHON="PY3" - Packagesix="python3-six" - Packagerequests="python3-requests" - Packagepillow="python3-pillow" -else - echo "Python2 image detected" - PYTHON="PY2" - Packagerequests="python-requests" - Packagepillow="python-pillow" - if [ "$OSTYPE" = "DreamOs" ] || [ "$OSTYPE" = "Debian" ]; then - Packagesix="python-six" - else - Packagesix="python-six" - fi +if ! command -v python3 >/dev/null 2>&1 || ! python3 --version 2>&1 | grep -q '^Python 3\.'; then + echo "ERROR: Python 3 is required but was not found on this system." + echo "ForecaOne only supports Python 3 (see README.md)." + exit 1 fi +echo "Python3 image detected: $(python3 --version 2>&1)" +Packagerequests="python3-requests" +Packagepillow="python3-pillow" install_pkg() { local pkg=$1 @@ -130,8 +121,8 @@ install_pkg() { fi } -[ "$PYTHON" = "PY3" ] && install_pkg "$Packagesix" install_pkg "$Packagerequests" +install_pkg "$Packagepillow" if [ "$OSTYPE" = "OE" ]; then echo "Installing additional dependencies for OpenEmbedded..." @@ -222,7 +213,7 @@ fi [ -z "$distro_value" ] && distro_value="Unknown" [ -z "$distro_version" ] && distro_version="Unknown" -python_vers=$(python --version 2>&1) +python_vers=$(python3 --version 2>&1) cat < Date: Mon, 7 Sep 2026 21:17:42 +0000 Subject: [PATCH 3/4] Remove dead translate_utils.py module Confirmed zero references anywhere in the codebase (verified via grep across all plugin files). It duplicated google_translate.py's translation-cache logic under a different, unused entry point. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01UPpumFb2PP21ATpDwJBYBB --- .../Extensions/Foreca1/translate_utils.py | 621 ------------------ 1 file changed, 621 deletions(-) delete mode 100644 usr/lib/enigma2/python/Plugins/Extensions/Foreca1/translate_utils.py diff --git a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/translate_utils.py b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/translate_utils.py deleted file mode 100644 index 06b9bc1c..00000000 --- a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/translate_utils.py +++ /dev/null @@ -1,621 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# Copyright (c) @Lululla 2026 -# Google Translate API for Foreca One Weather Plugin - -import hashlib -import json -import socket -import time -from json import JSONDecodeError, loads -from os import makedirs, remove -from os.path import dirname, exists, join - -from urllib.error import HTTPError, URLError -from urllib.parse import urlencode -from urllib.request import Request, urlopen - -from Components.config import config - -from . import DEBUG, HEADERS, SYSTEM_DIR - -# ============================================================ -# CUSTOM CONFIGURATION -# ============================================================ - -# Translation API URL (can be changed if needed) -TRANSLATE_API_URL = "https://translate.googleapis.com/translate_a/single" - -# Timeout for HTTP requests (in seconds) -REQUEST_TIMEOUT = 8 - -# Character limit for batch translation (to avoid errors) -MAX_CHARS_PER_REQUEST = 2000 - -# Local cache to avoid repetitive requests -CACHE_FILE = join(SYSTEM_DIR, "translation_cache.json") -_translation_cache = {} -_cache_hits = 0 -_cache_misses = 0 -_cache_dirty = False # flag to know if there are changes to save - -# Enable logging -ENABLE_LOGGING = True - - -# ============================================================ -# CACHE PERSISTENCE -# ============================================================ - - -def _ensure_cache_dir(): - """Create the directory for the cache file if it does not exist.""" - cache_dir = dirname(CACHE_FILE) - if not exists(cache_dir): - try: - makedirs(cache_dir) - except Exception as e: - _log(f"Error creating cache directory: {e}") - - -def load_cache_from_disk(): - """Load the cache from the JSON file at startup.""" - global _translation_cache - _ensure_cache_dir() - if exists(CACHE_FILE): - try: - with open(CACHE_FILE, 'r', encoding='utf-8') as f: - _translation_cache = json.load(f) - _log(f"Cache loaded from disk ({len(_translation_cache)} entries)") - except Exception as e: - _log(f"Error loading cache: {e}") - _translation_cache = {} - else: - _translation_cache = {} - - -def save_cache_to_disk(): - """Save the cache to disk if there are changes.""" - global _cache_dirty - if not _cache_dirty: - return - _ensure_cache_dir() - try: - with open(CACHE_FILE, 'w', encoding='utf-8') as f: - json.dump(_translation_cache, f, ensure_ascii=False, indent=2) - _log(f"Cache saved to disk ({len(_translation_cache)} entries)") - _cache_dirty = False - except Exception as e: - _log(f"Error saving cache: {e}") - - -# ============================================================ -# UTILITY FUNCTIONS -# ============================================================ - - -def _log(message): - """Custom logging""" - if ENABLE_LOGGING and DEBUG: - timestamp = time.time() - print(f"[Foreca-1-Translate][{timestamp:.2f}] {message}") - - -def _get_system_language(): - """Get system language in short format""" - try: - lang = config.misc.language.value - return lang.split('_')[0].lower() - except Exception: - lang = config.osd.language.value - return lang.split('_')[0].lower() - -# print("System Language:", _get_system_language()) - - -def _to_unicode(text): - """Convert any input into a Unicode string.""" - if text is None: - return "" - - if isinstance(text, str): - return text - - if isinstance(text, bytes): - try: - return text.decode("utf-8", errors="ignore") - except Exception: - return str(text, errors="ignore") - - try: - return str(text) - except Exception: - return "" - - -def _clean_whitespace(text): - text_unicode = _to_unicode(text) - while " " in text_unicode: - text_unicode = text_unicode.replace(" ", " ") - return text_unicode.strip() - - -# ============================================================ -# ARABIC LANGUAGE DETECTION -# ============================================================ - - -def _is_arabic_char(char): - """Check if a character is Arabic""" - try: - code = ord(char) - # Unicode ranges for Arabic characters - return ( - 0x0600 <= code <= 0x06FF or - 0x0750 <= code <= 0x077F or - 0x08A0 <= code <= 0x08FF or - 0xFB50 <= code <= 0xFDFF or - 0xFE70 <= code <= 0xFEFF - ) - except Exception: - return False - - -def _is_text_arabic(text): - """ - Determines whether a text is predominantly Arabic. - Returns True if more than 60% of alphabetic characters are Arabic. - """ - text_unicode = _to_unicode(text) - if not text_unicode: - return False - - total_letters = 0 - arabic_letters = 0 - - for char in text_unicode: - # Consider only alphabetic characters (exclude spaces, numbers, - # punctuation) - if char.isalpha(): - total_letters += 1 - if _is_arabic_char(char): - arabic_letters += 1 - - # If there are no letters, it's not Arabic - if total_letters == 0: - return False - - # Calculate percentage - arabic_ratio = float(arabic_letters) / float(total_letters) - - # Threshold to consider the text Arabic (60%) - return arabic_ratio >= 0.6 - - -# ============================================================ -# CACHE AND PERFORMANCE -# ============================================================ - - -def _get_cache_key(text, target_lang): - """Generate a unique cache key using MD5 (stable across runs)""" - # Use MD5 because it is fast and deterministic - key_string = f"{target_lang}:{text}".encode('utf-8') - return hashlib.md5(key_string).hexdigest() - - -def _cache_translation(text, target_lang, translated): - """Store a translation in the cache and save immediately to disk.""" - global _cache_dirty - cache_key = _get_cache_key(text, target_lang) - _translation_cache[cache_key] = translated - _cache_dirty = True - save_cache_to_disk() - return translated - - -def _get_cached_translation(text, target_lang): - """Retrieve a translation from the cache""" - global _cache_hits, _cache_misses - cache_key = _get_cache_key(text, target_lang) - - if cache_key in _translation_cache: - _cache_hits += 1 - return _translation_cache[cache_key] - - _cache_misses += 1 - return None - - -def get_cache_stats(): - """Return cache statistics""" - return { - 'hits': _cache_hits, - 'misses': _cache_misses, - 'size': len(_translation_cache), - 'hit_rate': _cache_hits / max(1, _cache_hits + _cache_misses) - } - - -def clear_cache(): - """Clear the translation cache and delete the file""" - global _cache_hits, _cache_misses, _cache_dirty - _translation_cache.clear() - _cache_hits = 0 - _cache_misses = 0 - _cache_dirty = False - if exists(CACHE_FILE): - try: - remove(CACHE_FILE) - except Exception as e: - _log(f"Error deleting cache file: {e}") - _log("Cache cleared") - - -# ============================================================ -# MAIN TRANSLATION FUNCTION -# ============================================================ - -def translate_text(text, target_lang=None, use_cache=True): - """ - Translates text using the Google Translate API. - - Args: - text (str): Text to translate - target_lang (str): Target language (e.g. 'it', 'en', 'de') - If None, uses the system language - use_cache (bool): Whether to use the local cache - - Returns: - str: Translated text or original text in case of error - """ - start_time = time.time() - _log(f"Target language: '{target_lang}'") - # Input validation - if not text: - return "" - - # Convert to Unicode - text_unicode = _to_unicode(text) - - # Use system language if not specified - if target_lang is None: - target_lang = _get_system_language() - - # Normalize language (ensure lowercase) - target_lang = target_lang.lower() - - # If the text is already Arabic, do not translate it - if _is_text_arabic(text_unicode): - _log(f"Arabic text detected, not translated: '{text_unicode[:50]}...'") - return text_unicode - - # Check cache if enabled - if use_cache: - cached = _get_cached_translation(text_unicode, target_lang) - if cached is not None: - _log(f"Cache HIT: '{text_unicode[:30]}...' -> '{cached[:30]}...'") - return cached - - # Error handling for overly long texts - if len(text_unicode) > MAX_CHARS_PER_REQUEST: - _log("Text too long (" + - str(len(text_unicode)) + - " chars), truncated to " + - str(MAX_CHARS_PER_REQUEST)) - text_unicode = text_unicode[:MAX_CHARS_PER_REQUEST] - - # Prepare the request - params = { - "client": "gtx", # Fake client to bypass restrictions - "sl": "auto", # Automatic source language - "tl": target_lang, # Target language - "dt": "t", # Response type: translation only - "q": text_unicode, # Text to translate - } - - try: - # Build the URL - query_string = urlencode(params) - url = f"{TRANSLATE_API_URL}?{query_string}" - - _log(f"Translating: '{text_unicode[:40]}...' -> {target_lang}") - - # Set timeout to avoid blocking - socket.setdefaulttimeout(REQUEST_TIMEOUT) - - # Perform the request - req = Request(url) - for key, value in HEADERS.items(): - req.add_header(key, value) - response = urlopen(req, timeout=REQUEST_TIMEOUT) - raw_data = response.read() - - # Decode the response - if isinstance(raw_data, bytes): - raw_data = raw_data.decode('utf-8') - - # Parse JSON response - data = loads(raw_data) - - # Extract the translation from the JSON structure - translated_text = "" - if isinstance(data, list) and data: - # Typical structure: [[[translation, original], ...], ...] - for item in data[0]: - if item and isinstance(item, list) and item[0]: - translated_text += item[0] - - # Clean the result - if translated_text: - translated_text = _clean_whitespace(translated_text) - - # Save to cache - if use_cache: - _cache_translation(text_unicode, target_lang, translated_text) - - elapsed = time.time() - start_time - _log(( - f"Translation completed in {elapsed:.2f}s: '{text_unicode[:30]}...' -> " - f"'{translated_text[:30]}...'" - )) - - return translated_text - else: - _log(f"Empty API response for: '{text_unicode[:30]}...'") - return text_unicode - - except socket.timeout: - _log(f"TIMEOUT during translation: '{text_unicode[:30]}...'") - return text_unicode - - except (URLError, HTTPError) as e: - _log(f"HTTP error {getattr(e, 'code', 'N/A')}: {str(e)}") - return text_unicode - - except JSONDecodeError as e: - _log(f"JSON error: {str(e)}") - return text_unicode - - except Exception as e: - error_type = type(e).__name__ - _log(f"Error {error_type}: {str(e)}") - return text_unicode - - finally: - # Restore default timeout - socket.setdefaulttimeout(None) - - -# ============================================================ -# AUXILIARY FUNCTIONS FOR SPECIAL CASES -# ============================================================ - - -def translate_batch(texts, target_lang=None, use_cache=True): - """ - Translates a list of texts in batch. - Optimized to reduce the number of HTTP requests. - - Args: - texts (list): List of texts to translate - target_lang (str): Target language - use_cache (bool): Use cache - - Returns: - list: List of translated texts - """ - if not texts: - return [] - - # Use system language if not specified - if target_lang is None: - target_lang = _get_system_language() - results = [] - batch_text = [] - batch_indices = [] - - for i, text in enumerate(texts): - text_unicode = _to_unicode(text) - - # Check cache - if use_cache: - cached = _get_cached_translation(text_unicode, target_lang) - if cached is not None: - results.append(cached) - continue - - # If the text is Arabic, do not translate it - if _is_text_arabic(text_unicode): - results.append(text_unicode) - continue - - # Add to batch - batch_text.append(text_unicode) - batch_indices.append(i) - results.append(None) # Placeholder - - # If there are texts to translate in batch - if batch_text: - try: - # Join texts with a special separator - separator = u" ||| " - combined_text = separator.join(batch_text) - - # Translate the batch - combined_translated = translate_text( - combined_text, - target_lang, - use_cache=False # Do not use cache for batch - ) - - # Split results - if separator in combined_translated: - translated_parts = combined_translated.split(separator) - else: - # Fallback: split by approximate number - translated_parts = [combined_translated] * len(batch_text) - - # Update results - for idx, translated in zip(batch_indices, translated_parts): - results[idx] = translated - - # Save to cache - if use_cache and idx < len(texts): - text_unicode = _to_unicode(texts[idx]) - _cache_translation(text_unicode, target_lang, translated) - - except Exception as e: - _log(f"Batch translation error: {str(e)}") - # Fallback: translate individually - for idx in batch_indices: - if results[idx] is None and idx < len(texts): - results[idx] = translate_text( - texts[idx], target_lang, use_cache) - - # Replace None with original text - for i in range(len(results)): - if results[i] is None: - results[i] = _to_unicode(texts[i]) - - return results - - -def safe_translate(text, fallback=None, **kwargs): - """ - Safe version of translate_text that always returns a valid string. - Args: - text (str): Text to translate - fallback (str): Fallback text if translation fails - **kwargs: Additional arguments for translate_text - Returns: - str: Translated text, fallback or original - """ - try: - translated = translate_text(text, **kwargs) - if translated and translated.strip(): - return translated - - # If translation is empty, use fallback - if fallback is not None: - return _to_unicode(fallback) - - return _to_unicode(text) - - except Exception as e: - _log(f"Error in safe_translate: {str(e)}") - if fallback is not None: - return _to_unicode(fallback) - return _to_unicode(text) - - -def trans(text, target_lang=None): - """ - Simplified translation function for single strings. - Uses cache and translate_text. - """ - if target_lang is None: - target_lang = _get_system_language() - target_lang = target_lang.lower() - - if not text or not isinstance(text, str): - return text or "" - - text = text.strip() - if not text: - return "" - - # Do not translate Arabic text - if _is_text_arabic(text): - return text - - # Check cache using full key (language + hash) - cached = _get_cached_translation(text, target_lang) - if cached is not None: - return cached - - # Translate (translate_text already handles internal caching if - # use_cache=True) - translated = translate_text(text, target_lang, use_cache=True) - if translated and translated != text: - return translated - return text - - -def translate_batch_strings(texts, target_lang=None): - """ - High-level batch translation for a list of strings. - """ - if not texts: - return [] - valid_texts = [str(t).strip() for t in texts if t and str(t).strip()] - if not valid_texts: - return [] - - # Use the existing cache via translate_batch - return translate_batch(valid_texts, target_lang, use_cache=True) - - -# ============================================================ -# TEST FUNCTION (for debugging) -# ============================================================ - - -def test_translation(): - """Test function to verify functionality""" - test_cases = [ - ("Hello world", "it", "Ciao mondo"), - ("Weather forecast", "es", "Pronóstico del tiempo"), - ("Temperature", "fr", "Température"), - ] - if DEBUG: - print("=" * 60) - print("Foreca One TRANSLATION TEST") - print("=" * 60) - - all_passed = True - - for original, lang, expected in test_cases: - result = translate_text(original, lang) - - if result and result.lower() == expected.lower(): - status = "✓ PASS" - else: - status = "✗ FAIL" - all_passed = False - if DEBUG: - print( - f"{status}: '{original}' -> '{result}' (expected: '{expected}')") - if DEBUG: - print("=" * 60) - stats = get_cache_stats() - print(( - f"Cache statistics: {stats['hits']} hits, {stats['misses']} misses, " - f"rate: {stats['hit_rate']:.1%}" - )) - print("=" * 60) - - return all_passed - - -# ============================================================ -# INITIALIZATION -# ============================================================ - -# Load cache at module startup -load_cache_from_disk() - -if __name__ == "__main__": - # Test mode when run directly - if DEBUG: - print("Google Translate API for Foreca") - print("Enhanced custom version") - - if test_translation(): - print("✓ All tests passed!") - else: - print("✗ Some tests failed") -else: - # Imported as a module - _log("Foreca One translation module loaded") - _log(f"System language: {_get_system_language()}") From d94c0249947af73444ce1d523ec35da45a2ca01c Mon Sep 17 00:00:00 2001 From: pQu4k3r Date: Mon, 7 Sep 2026 21:24:54 +0000 Subject: [PATCH 4/4] Bump version to 1.3.2 Patch release covering the bug fixes from the full repository audit (crash fixes, security fixes, UI-freeze fixes, Python 3 enforcement, dead code removal). Updated the version string everywhere it's declared: __init__.py, installer.sh (also updated its changelog shown by the in-plugin update checker), CONTROL/control, CONTROL/prerm, and the README badge. The Yocto recipe (enigma2-plugin-extensions-foreca-one.bb) uses its own git-hash-based PV scheme and is intentionally left untouched. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01UPpumFb2PP21ATpDwJBYBB --- CONTROL/control | 2 +- CONTROL/prerm | 2 +- README.md | 2 +- installer.sh | 4 ++-- usr/lib/enigma2/python/Plugins/Extensions/Foreca1/__init__.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CONTROL/control b/CONTROL/control index 4e162da9..5661e5e3 100644 --- a/CONTROL/control +++ b/CONTROL/control @@ -1,5 +1,5 @@ Package: enigma2-plugin-extensions-foreca-one -Version: 1.3.1 +Version: 1.3.2 Section: base Priority: optional Architecture: all diff --git a/CONTROL/prerm b/CONTROL/prerm index 42df9acd..8b9dc47a 100755 --- a/CONTROL/prerm +++ b/CONTROL/prerm @@ -1,3 +1,3 @@ #!/bin/bash -echo 'Removing package : Foreca v.1.3.1' +echo 'Removing package : Foreca v.1.3.2' exit 0 diff --git a/README.md b/README.md index fc3c549e..f40558e2 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Enigma2 Plugin - Version + Version License diff --git a/installer.sh b/installer.sh index cb3b044e..8ad6d3b2 100755 --- a/installer.sh +++ b/installer.sh @@ -1,7 +1,7 @@ #!/bin/bash -version='1.3.1' -changelog='Introduce configurable translation engine allowing users to choose between gettext (local .po files) and Google Translate for UI translations. Features include:\n- New translation engine config option in plugin settings\n- Support for 100+ languages via Google Translate with automatic system language detection\n- Placeholder preservation logic to handle format strings in translated text\n- New "Translation Settings" menu item in main interface\n- Improved translation function with fallback mechanisms and error handling\n- Version bump to 1.3.0\n- Code formatting improvements (f-string to % formatting for Python 2 compatibility)\n- Enhanced documentation with section headers and docstrings' +version='1.3.2' +changelog='Bug fix release following a full repository audit:\n- Fixed crash bugs in day navigation, wind-symbol maps, and the map layer menu\n- Removed hardcoded API credentials and tightened saved config file permissions\n- Validated third-party API responses before use and URL-encoded search input\n- Fixed UI freezes on weather refresh, meteogram, and lunar calendar screens by backgrounding network/CPU-heavy work\n- Enforced the Python 3 requirement in install scripts (fixes silent failures on Python-2-only systems)\n- Removed dead code and fixed missing Pillow dependency in the opkg install path' TMPPATH=/tmp/ForecaOne-install FILEPATH=/tmp/ForecaOne-main.tar.gz diff --git a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/__init__.py b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/__init__.py index 0b1d49f1..a225a1b1 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/__init__.py +++ b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/__init__.py @@ -142,7 +142,7 @@ config.plugins.foreca.target_language = ConfigSelection( choices=LANGUAGE_CHOICES, default='auto') -__version__ = "1.3.1" +__version__ = "1.3.2" VERSION = __version__ _AUTHOR_ = "by Lululla - 2026" IDEAS = "@Bauernbub"