diff --git a/CONTROL/preinst b/CONTROL/preinst index 424b5da9..6b86c125 100755 --- a/CONTROL/preinst +++ b/CONTROL/preinst @@ -52,6 +52,13 @@ else echo "⚠ Could not install ${PY}-requests, but continuing installation" fi +echo "Installing ${PY}-pillow..." +if opkg install --force-reinstall "${PY}-pillow" > /dev/null 2>&1; then + echo "✓ ${PY}-pillow installed successfully" +else + echo "⚠ Could not install ${PY}-pillow, but continuing installation" +fi + # Additional dependency checks echo "Verifying system compatibility..." if [ -e "/usr/bin/enigma2" ]; then diff --git a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/city_panel.py b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/city_panel.py index c2dee23f..7b7368d4 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/city_panel.py +++ b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/city_panel.py @@ -5,6 +5,7 @@ # fallback import requests +from urllib.parse import quote_plus from os.path import exists, join from enigma import eListboxPythonMultiContent, gFont, RT_VALIGN_CENTER, eTimer, eListbox @@ -296,7 +297,8 @@ def search_online(self, search_term): """Cerca tramite API Foreca. Ritorna True se ha trovato risultati, False altrimenti.""" current_lang = _get_system_language() try: - url = "%s/locations/search/%s.json" % (BASE_URL, search_term) + url = "%s/locations/search/%s.json" % ( + BASE_URL, quote_plus(search_term)) params = { "limit": 20, "lang": current_lang 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 4fc43d37..ac805daa 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 @@ -100,8 +100,8 @@ def load_config(self): config_data = default_config # Assign values - self.user = config_data.get("API_USER", "ekekaz") - self.password = config_data.get("API_PASSWORD", "im5issEYcMUG") + self.user = config_data.get("API_USER", "your_username_here") + self.password = config_data.get("API_PASSWORD", "your_password_here") self.token_expire_hours = int( config_data.get( "TOKEN_EXPIRE_HOURS", 720)) @@ -150,10 +150,10 @@ def create_example_config(self): # Rename this file to api_config.txt and fill with your credentials # Your Foreca API username - API_USER=ekekaz + API_USER=your_username_here # Your Foreca API password - API_PASSWORD=im5issEYcMUG + API_PASSWORD=your_password_here # Token expiration in hours (max 720 = 30 days) TOKEN_EXPIRE_HOURS=720 diff --git a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/foreca_map_menu.py b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/foreca_map_menu.py index 34e84e6d..83203083 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/foreca_map_menu.py +++ b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/foreca_map_menu.py @@ -63,14 +63,13 @@ def load_layers(self): self.layers = self.api.get_capabilities() if DEBUG: print(f"[DEBUG] Layers ricevuti ({len(self.layers)}):") - for layer in self.layers: - layer_id = layer['id'] - title = layer.get('title', 'N/A') - layer_type = layer.get('type', 'N/A') - colorschemes = layer.get('colorschemes', []) - if DEBUG: + if DEBUG: + for layer in self.layers: + title = layer.get('title', 'N/A') + layer_type = layer.get('type', 'N/A') + colorschemes = layer.get('colorschemes', []) print( - f" ID: {layer_id}, Title: {title}, Type: {layer_type}, Schemes: {colorschemes}") + f" ID: {layer['id']}, Title: {title}, Type: {layer_type}, Schemes: {colorschemes}") if not self.layers: self["info"].setText(_("Error loading maps. Check connection.")) @@ -81,7 +80,7 @@ def load_layers(self): title = layer.get('title', f"Layer {layer['id']}") if 'wind symbol' in title.lower(): continue - if layer_id == 3: + if layer['id'] == 3: continue items.append((trans(title), layer)) diff --git a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/foreca_svg_map_viewer.py b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/foreca_svg_map_viewer.py index 548e40b6..e1b15074 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/foreca_svg_map_viewer.py +++ b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/foreca_svg_map_viewer.py @@ -30,14 +30,40 @@ apply_global_theme, TEMP_DIR ) -from .foreca_map_viewer import REGION_CENTERS, get_background_for_layer - SVG_MAPS_DIR = join(TEMP_DIR, "svgmapviewer") if not exists(SVG_MAPS_DIR): makedirs(SVG_MAPS_DIR) TILE_SIZE = 256 +# Fallback center coordinates per region, used when a layer has no usable extent. +REGION_CENTERS = { + 'eu': (50.0, 10.0), + 'europe': (50.0, 10.0), + 'us': (39.0, -98.0), + 'usa': (39.0, -98.0), + 'africa': (1.0, 20.0), + 'asia': (34.0, 100.0), + 'oceania': (-25.0, 135.0), + 'world': (20.0, 0.0), +} + +# Maps a region to a background PNG that actually exists under thumb/. +_REGION_BACKGROUNDS = { + 'eu': 'europa.png', + 'europe': 'europa.png', + 'us': 'nordamerika.png', + 'usa': 'nordamerika.png', + 'africa': 'africa.png', + 'asia': 'asia_se.png', + 'oceania': 'australia.png', +} + + +def get_background_for_layer(layer_title, region): + """Pick a background PNG for the given region (layer_title currently unused).""" + return _REGION_BACKGROUNDS.get((region or '').lower(), 'world.png') + class ForecaSVGMapViewer(Screen, HelpableScreen): def __init__(self, session, api, layer, unit_system='metric', region='eu'): diff --git a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/meteogram.py b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/meteogram.py index fc47828a..14ece036 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/meteogram.py +++ b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/meteogram.py @@ -8,7 +8,9 @@ from json import loads, JSONDecodeError from os.path import exists, join from os import makedirs, listdir, remove +from threading import Thread import requests +from twisted.internet import reactor from enigma import getDesktop, ePoint @@ -224,11 +226,15 @@ def cleanup_temp_files(self): print(f"[Meteogram] Error cleaning temp files: {e}") def fetch_data(self): - """Download the detailed forecast page and extract JSON data.""" + """Kick off the (blocking) forecast download in a background thread.""" + Thread(target=self._fetch_data_worker).start() + + def _fetch_data_worker(self): + """Download the detailed forecast page and extract JSON data. Runs off the UI thread.""" lang = _get_system_language() place = self.api.get_location_by_id(self.loc_id) if not place: - self.close() + reactor.callFromThread(self.close) return url = f"https://www.foreca.com/{lang}/{self.loc_id}/{place.address}/detailed-forecast" @@ -272,6 +278,10 @@ def fetch_data(self): write_meteogram_debug( f"First element keys: {list(forecast[0].keys())}") + reactor.callFromThread(self._apply_fetched_data, forecast, ranges) + + def _apply_fetched_data(self, forecast, ranges): + """Populate widgets from downloaded forecast data. Runs on the UI thread.""" # Update time (first element's 'updated' field) if forecast and len(forecast) > 1: updated_utc = forecast[1].get('updated', '').replace('Z', '+00:00') diff --git a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/moon_calendar.py b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/moon_calendar.py index 4e189e9b..8e3cd1fa 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/moon_calendar.py +++ b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/moon_calendar.py @@ -6,6 +6,8 @@ from datetime import datetime, timedelta from os.path import exists, join from collections import defaultdict +from threading import Thread +from twisted.internet import reactor from Screens.Screen import Screen from Screens.HelpMenu import HelpableScreen from Screens.MessageBox import MessageBox @@ -156,6 +158,10 @@ def _jd_to_datetime(self, jd): def load_calendar(self): """Generate the list of lunar phases and special events for the next 12 months.""" self["info"].setText(_("Calculating...")) + Thread(target=self._load_calendar_worker).start() + + def _load_calendar_worker(self): + """Heavy lunar-phase computation. Runs off the UI thread.""" self.phases = [] today = datetime.now() # Start from the first day of next month @@ -253,6 +259,10 @@ def load_calendar(self): # Update current moon info info = self.moon.get_phase_info() + reactor.callFromThread(self._apply_calendar_data, info) + + def _apply_calendar_data(self, info): + """Populate widgets with the computed calendar data. Runs on the UI thread.""" if info["icon_path"] and exists(info["icon_path"]): self["current_phase_icon"].instance.setPixmapFromFile( info["icon_path"]) diff --git a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/plugin.py b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/plugin.py index 12730ce3..2a99730a 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/plugin.py +++ b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/plugin.py @@ -344,6 +344,7 @@ def save(self): f"TOKEN_EXPIRE_HOURS={token_expire_hours_int}\n") config_file.write(f"MAP_SERVER={map_server}\n") config_file.write(f"AUTH_SERVER={auth_server}\n") + chmod(CONFIG_FILE, 0o600) except Exception as error: self.session.open( MessageBox, @@ -1201,6 +1202,7 @@ def _load_favorite(self, fav_index, path_loc, forced_name=None): print( f"[DEBUG] sunrise={daily_all[self.tag].sunrise}, sunset={daily_all[self.tag].sunset}") + day_selected = None if daily_all and len(daily_all) > self.tag: day_selected = daily_all[self.tag] @@ -1269,6 +1271,7 @@ def _load_favorite(self, fav_index, path_loc, forced_name=None): # Hourly forecast (try free, fallback to auth) hourly = None + target_date = None # First try free API try: hourly = self.weather_api.get_hourly_forecast( @@ -1395,10 +1398,10 @@ def _save_favorite(self, index, city_id): try: with open(filename, "w") as f: f.write(city_id) - chmod(filename, 0o655) + chmod(filename, 0o644) if DEBUG: print( - f"[Foreca1] Saved {names[index]} = {city_id} (perms 655)") + f"[Foreca1] Saved {names[index]} = {city_id} (perms 644)") except Exception as e: print(f"[Foreca1] Error saving {names[index]}: {e}") @@ -1407,7 +1410,7 @@ def _save_color(self): try: with open(path, "w") as f: f.write(f"{self.rgbmyr} {self.rgbmyg} {self.rgbmyb}") - chmod(path, 0o655) + chmod(path, 0o644) if DEBUG: print( f"[Foreca1] Color saved: {self.rgbmyr} {self.rgbmyg} {self.rgbmyb}") @@ -1419,7 +1422,7 @@ def _save_alpha(self): try: with open(path, "w") as f: f.write(self.alpha) - chmod(path, 0o655) + chmod(path, 0o644) except Exception as e: print("[Foreca1] Error saving alpha:", e) @@ -2212,11 +2215,16 @@ def truncate(text, max_len=25): # Truncate text if too long station_text = truncate(station_text) - # Safely update the widget only if it exists - if "station_name" in self: - self["station_name"].setText(station_text) - if source: - print(f"[Foreca1] Station source: {source}") + from twisted.internet import reactor + + def update_ui(): + # Safely update the widget only if it exists + if "station_name" in self: + self["station_name"].setText(station_text) + if source: + print(f"[Foreca1] Station source: {source}") + + reactor.callFromThread(update_ui) def _update_moon(self, target_date=None): """ diff --git a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/rain_maps.py b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/rain_maps.py index 080f18e0..c022f031 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/rain_maps.py +++ b/usr/lib/enigma2/python/Plugins/Extensions/Foreca1/rain_maps.py @@ -262,7 +262,14 @@ def _fetch_frames(self): _("API error"))) return data = resp.json() - self.host = data['host'] + returned_host = data['host'] + if returned_host.startswith('https://') and returned_host[8:].split( + '/')[0].endswith('.rainviewer.com'): + self.host = returned_host + else: + print( + f"[RainViewer] Unexpected host in API response, ignoring: {returned_host}") + self.host = 'https://tilecache.rainviewer.com' self.frames = [frame['path'] for frame in data['radar']['past']] self.frames.reverse() # oldest to newest self.current_frame = len(self.frames) - 1 # last frame