Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTROL/control
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Package: enigma2-plugin-extensions-foreca-one
Version: 1.3.1
Version: 1.3.2
Section: base
Priority: optional
Architecture: all
Expand Down
7 changes: 4 additions & 3 deletions CONTROL/preinst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion CONTROL/prerm
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/bash
echo 'Removing package : Foreca v.1.0.0'
echo 'Removing package : Foreca v.1.3.2'
exit 0
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<img src="https://img.shields.io/badge/Enigma2-Plugin-orange.svg" alt="Enigma2 Plugin">
</a>
<a href="https://github.com/Belfagor2005/ForecaOne">
<img src="https://img.shields.io/badge/Version-1.3.1-blue.svg" alt="Version">
<img src="https://img.shields.io/badge/Version-1.3.2-blue.svg" alt="Version">
</a>
<a href="https://www.gnu.org/licenses/gpl-3.0.html">
<img src="https://img.shields.io/badge/License-GPLv3-blue.svg" alt="License">
Expand Down Expand Up @@ -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/
Expand All @@ -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

Expand Down
31 changes: 11 additions & 20 deletions installer.sh
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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..."
Expand Down Expand Up @@ -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 <<EOF
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
42 changes: 40 additions & 2 deletions usr/lib/enigma2/python/Plugins/Extensions/Foreca1/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1009,7 +1009,13 @@ def Fav2(self):
self._load_favorite(2, self.path_loc2)

def _update_fav_button_names(self):
"""Update the buttons with city names (truncated to 11 characters)"""
"""Update the buttons with city names (truncated to 11 characters).
Fetches names (network calls) in a background thread; only the
final widget update runs on the UI thread.
"""
Thread(target=self._update_fav_button_names_worker).start()

def _update_fav_button_names_worker(self):
fav_paths = [self.path_loc0, self.path_loc1, self.path_loc2]
button_names = []

Expand Down Expand Up @@ -1056,6 +1062,11 @@ def _update_fav_button_names(self):
print(f"[DEBUG] Error Fav {i}: {e}")
button_names.append(f"Fav{i}")

from twisted.internet import reactor
reactor.callFromThread(self._apply_fav_button_names, button_names)

def _apply_fav_button_names(self, button_names):
"""Set the favorite button labels. Runs on the UI thread."""
# Assign: 0=home(blue), 1=fav1(green), 2=fav2(yellow)
self["key_blue"].setText(_(button_names[0]))
self["key_green"].setText(_(button_names[1]))
Expand All @@ -1071,6 +1082,8 @@ def _load_favorite(self, fav_index, path_loc, forced_name=None):
"""Load data for the specified favorite.
path_loc can be just the ID or "ID/Name_with_underscores".
forced_name can be used to override the name.
Kicks off the (blocking) fetch in a background thread so the UI
doesn't freeze while waiting on the network.
"""
if self.weather_anim_running:
self.weather_anim_timer.stop()
Expand All @@ -1084,6 +1097,20 @@ def _load_favorite(self, fav_index, path_loc, forced_name=None):
self.windspeed_anim_timer.stop()
self.windspeed_anim_running = False

self.myloc = fav_index

# Sequence guard: if a newer _load_favorite call comes in before this
# one finishes (e.g. rapid arrow-key presses), the older worker's
# results are discarded instead of overwriting the newer ones.
self._load_favorite_seq = getattr(self, '_load_favorite_seq', 0) + 1
seq = self._load_favorite_seq
Thread(
target=self._load_favorite_worker,
args=(fav_index, path_loc, forced_name, seq)
).start()

def _load_favorite_worker(self, fav_index, path_loc, forced_name, seq):
"""Fetch weather/location data for a favorite. Runs off the UI thread."""
# Extract ID and stored name (if present)
if '/' in path_loc:
location_id, stored_name = path_loc.split('/', 1)
Expand All @@ -1092,7 +1119,6 @@ def _load_favorite(self, fav_index, path_loc, forced_name=None):
location_id = path_loc
stored_name = None

self.myloc = fav_index
day_index = self.tag

# Fetch location data from API (for country, lat, lon, timezone)
Expand Down Expand Up @@ -1346,6 +1372,18 @@ def _load_favorite(self, fav_index, path_loc, forced_name=None):
print(
f"[DEBUG] Daily forecast for day {self.tag}: {day_selected.__dict__ if day_selected else 'None'}")

if seq != getattr(self, '_load_favorite_seq', seq):
# A newer _load_favorite call superseded this one; drop stale results.
return

from twisted.internet import reactor
reactor.callFromThread(self._apply_favorite_data, target_date, seq)

def _apply_favorite_data(self, target_date, seq):
"""Populate widgets once _load_favorite_worker finishes. Runs on the UI thread."""
if seq != getattr(self, '_load_favorite_seq', seq):
return

# Update UI
self._update_moon(target_date=target_date)
self.my_cur_weather()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def download_all_images(self):
"""Download all 6 images for the region"""

for i in range(self.total_images):
url = f"http://img.wetterkontor.de/karten/{self.region_code}{i}.jpg"
url = f"https://img.wetterkontor.de/karten/{self.region_code}{i}.jpg"
cache_file = join(
WETTERKONTOR_CACHE,
f"{self.region_code}_{i}.jpg")
Expand Down
Loading
Loading