diff --git a/.github/workflows/flake8.yml b/.github/workflows/flake8.yml index a9da50c..806942d 100644 --- a/.github/workflows/flake8.yml +++ b/.github/workflows/flake8.yml @@ -5,9 +5,11 @@ on: push: branches: - main + - develop pull_request: branches: - main + - develop jobs: pep8-check: @@ -41,11 +43,13 @@ jobs: git config --global user.name "GitHub Actions" - name: Pull latest changes + if: github.event_name == 'push' run: | - git pull origin main --no-rebase || echo "No remote changes to pull" + git pull origin ${{ github.ref_name }} --no-rebase || echo "No remote changes to pull" - name: Commit and push if changes exist + if: github.event_name == 'push' run: | git add . git diff --cached --quiet || git commit -m "Apply auto PEP8 aggressive fixes" - git push origin main || echo "Push failed (e.g. due to race condition)" + git push origin ${{ github.ref_name }} || echo "Push failed (e.g. due to race condition)" diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 125d38b..7cba48e 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -5,9 +5,9 @@ name: Python package on: push: - branches: [ "main" ] + branches: [ "main", "develop" ] pull_request: - branches: [ "main" ] + branches: [ "main", "develop" ] jobs: build: diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index a6fd0a1..b04a70c 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - develop pull_request: jobs: diff --git a/.github/workflows/update_translations.yml b/.github/workflows/update_translations.yml index 7b16060..730a260 100644 --- a/.github/workflows/update_translations.yml +++ b/.github/workflows/update_translations.yml @@ -2,7 +2,7 @@ name: Update Translations on: push: - branches: [ main ] + branches: [ main, develop ] workflow_dispatch: jobs: @@ -49,8 +49,8 @@ jobs: if ! git diff --cached --quiet; then git commit -m "Update translations via workflow" - git pull --rebase origin main - git push origin main + git pull --rebase origin ${{ github.ref_name }} + git push origin ${{ github.ref_name }} else echo "No changes to commit" fi diff --git a/.gitignore b/.gitignore index 3c03de0..10bd174 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/icons/link-.png usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/icons2/link-.png usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/plugin~.py +__pycache__/ +*.pyc +*.pyo diff --git a/CONTROL/control b/CONTROL/control index 29f9517..e4a3375 100644 --- a/CONTROL/control +++ b/CONTROL/control @@ -1,5 +1,5 @@ Package: enigma2-plugin-extensions-linuxsat-panel -Version: 2.9.2 +Version: 3.0.0 Description: addons panel Section: extra Priority: optional diff --git a/README.md b/README.md index 13e76e0..7262b3e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
-
+
diff --git a/installer.sh b/installer.sh
index 9dd3f88..dfa8f13 100644
--- a/installer.sh
+++ b/installer.sh
@@ -1,7 +1,7 @@
#!/bin/bash
-version='2.9.2'
-changelog="\n--Add WQHD PANEL"
+version='3.0.0'
+changelog="\n--Search all addons with GREEN button\n--Installed addons shown in green\n--No more GUI freezes (async downloads, catalog cache)\n--Safe settings install with automatic backup\n--Update check on panel start\n--Big cleanup and many bugfixes"
TMPPATH=/tmp/LinuxsatPanel-install
FILEPATH=/tmp/LinuxsatPanel-main.tar.gz
diff --git a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/LCNScanner/Lcn.py b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/LCNScanner/Lcn.py
index 1b0dbfe..dddc9e9 100644
--- a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/LCNScanner/Lcn.py
+++ b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/LCNScanner/Lcn.py
@@ -293,14 +293,17 @@ def addInTVBouquets(self):
try:
with open('/etc/enigma2/bouquets.tv', 'r') as f:
ret = f.read().splitlines()
- dttbouquet_str = Bouquet() # "FROM BOUQUET \"userbouquet.terrestrial_lcn.tv\""
+ dttbouquet = Bouquet()
+ # bouquets.tv references the bouquet by file name, not full path
+ name = os.path.basename(
+ dttbouquet) if dttbouquet else "userbouquet.terrestrial_lcn.tv"
for line in ret:
- if dttbouquet_str in line:
+ if name in line:
return
with open('/etc/enigma2/bouquets.tv', 'w') as f:
f.write(ret[0] + "\n")
f.write(
- '#SERVICE 1:7:1:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.terrestrial_lcn.tv" ORDER BY bouquet\n')
+ '#SERVICE 1:7:1:0:0:0:0:0:0:0:FROM BOUQUET "%s" ORDER BY bouquet\n' % name)
for line in ret[1:]:
f.write(line + "\n")
except Exception as e:
@@ -821,7 +824,7 @@ def find_terrestrial_bouquet():
if terrestrial_bouquet_path:
with open(terrestrial_bouquet_path, 'w') as bouquet_file:
for line in terrestrial_channel_list:
- if '#NAME' in line.lower():
+ if '#name' in line.lower():
bouquet_file.write('#NAME Digitale Terrestre\n')
else:
bouquet_file.write(line)
@@ -830,5 +833,7 @@ def find_terrestrial_bouquet():
print("Errore durante il ripristino del bouquet:", e)
return False
+ return RestoreTerrestrial(TerChArch)
+
# ===== by lululla
diff --git a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/LCNScanner/Terrestrial.py b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/LCNScanner/Terrestrial.py
index 9172a1d..bd59d2f 100644
--- a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/LCNScanner/Terrestrial.py
+++ b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/LCNScanner/Terrestrial.py
@@ -336,7 +336,6 @@ def Plugins(**kwargs):
def __newfunc(self, *args, **kwargs):
if self["scan"].isDone() and "Terrestrial" in str(self.scanList):
- from .LCNScanner.Terrestrial import TerrestrialBouquet
print(
"[TerrestrialBouquet] rebuilding terrestrial bouquet -",
TerrestrialBouquet().rebuild() or "was successful")
diff --git a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/__init__.py b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/__init__.py
index 0914e2d..517ac42 100644
--- a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/__init__.py
+++ b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/__init__.py
@@ -35,12 +35,12 @@
__email__ = "ekekaz@gmail.com"
__copyright__ = 'Copyright (c) 2024 Lululla'
__license__ = "GPL-v2"
-__version__ = "2.9.2"
+__version__ = "3.0.0"
def check_and_install_requests():
try:
- import requests
+ import requests # noqa: F401 (availability probe)
return
except ImportError:
pass
@@ -117,6 +117,17 @@ def setup_timer(callback_method):
return timer
+def version_tuple(version):
+ """'2.10.1' -> (2, 10, 1) for a numeric comparison"""
+ parts = []
+ for part in str(version).strip().split("."):
+ try:
+ parts.append(int(part))
+ except (TypeError, ValueError):
+ parts.append(0)
+ return tuple(parts) or (0,)
+
+
def check_version(currversion, installer_url, AgentRequest):
"""Controllo versione con gestione avanzata formato numerico"""
print("[Version Check] Starting...")
@@ -148,24 +159,26 @@ def check_version(currversion, installer_url, AgentRequest):
remote_changelog = "No changelog available"
for line in lines:
+ line = line.strip()
if line.startswith("version"):
parts = line.split("=")
if len(parts) > 1:
- remote_version = parts[1].strip().strip("'")
- if line.startswith("changelog"):
- parts = line.split("=")
- if len(parts) > 1:
- try:
- remote_changelog = parts[1].strip().strip(
- "'")
- except BaseException:
- remote_changelog = "No changelog available"
- break
+ remote_version = parts[1].strip().strip(
+ "'").strip('"')
+ elif line.startswith("changelog"):
+ parts = line.split("=")
+ if len(parts) > 1:
+ remote_changelog = parts[1].strip().strip(
+ "'").strip('"')
+ break
new_version = remote_version or "Unknown"
new_changelog = remote_changelog or "No changelog available"
- return new_version, new_changelog, currversion < remote_version
+ return new_version, new_changelog, version_tuple(
+ currversion) < version_tuple(remote_version)
+
+ return None, None, False
except Exception as e:
print("Error while checking version:", e)
@@ -294,6 +307,7 @@ def fetch_url(url, retries=3, initial_timeout=5):
else:
from urllib2 import (urlopen)
from urllib2 import URLError
+ PY3 = False
timeout = initial_timeout
for i in range(retries):
try:
@@ -322,7 +336,7 @@ def fetch_url(url, retries=3, initial_timeout=5):
def checkGZIP(url):
url = url
- from io import StringIO
+ from io import BytesIO
import gzip
import requests
import sys
@@ -337,7 +351,7 @@ def checkGZIP(url):
try:
response = urlopen(request, timeout=10)
if response.info().get('Content-Encoding') == 'gzip':
- buffer = StringIO(response.read())
+ buffer = BytesIO(response.read())
deflatedContent = gzip.GzipFile(fileobj=buffer)
if sys.version_info[0] == 3:
return deflatedContent.read().decode('utf-8')
@@ -371,9 +385,9 @@ def b64decoder(s):
print('Invalid base64 string: {}'.format(s))
return ""
elif padding == 2:
- s += b'=='
+ s += '=='
elif padding == 3:
- s += b'='
+ s += '='
else:
return ""
output = base64.b64decode(s)
diff --git a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/addons/stbinfo.py b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/addons/stbinfo.py
index 4fc7a3e..c41f08d 100644
--- a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/addons/stbinfo.py
+++ b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/addons/stbinfo.py
@@ -65,15 +65,20 @@ def __init__(self):
self.is_vti_image = self._is_vti_image()
self.is_dmm_image = self._is_dmm_image()
+ self.has_internet = False
self.internetline = self.get_internet_status()
self.mountid = self.get_mount_info()
self.storhdd = self.get_storage_info()
self.memin = self.get_memory_info()
self.ipub = self.get_ip()
self.current_format = self.getResolution()
- self.pip = self.get_public_ip()
+ # Skip the slow public IP lookups when there is no connection
+ self.pip = self.get_public_ip() if self.has_internet else None
def to_string(self):
+ def fmt(label, value):
+ return '%s: %s' % (label, value if value else 'Unknown')
+
lines = []
try:
lines.append(
@@ -81,52 +86,28 @@ def to_string(self):
('OpenWebif' if self.boxinfo else 'proc'))
lines.append('\n')
lines.append('HW Info:')
- lines.append('Vendor: %s' %
- str(self.hw_vendor) if self.hw_vendor else 'Unknown')
- lines.append('Model: %s' %
- str(self.hw_model) if self.hw_model else 'Unknown')
- lines.append('Chipset: %s' % str(self.hw_chipset)
- if self.hw_chipset else 'Unknown')
- lines.append('Architecture: %s' %
- str(self.hw_arch) if self.hw_arch else 'Unknown')
- lines.append('Local Ip: %s' %
- str(self.ipub) if self.ipub else 'Unknown')
- lines.append('Public IP: %s' %
- str(self.pip) if self.pip else 'Unknown')
- lines.append('Internet: %s' % str(self.internetline)
- if self.internetline else 'Unknown')
+ lines.append(fmt('Vendor', self.hw_vendor))
+ lines.append(fmt('Model', self.hw_model))
+ lines.append(fmt('Chipset', self.hw_chipset))
+ lines.append(fmt('Architecture', self.hw_arch))
+ lines.append(fmt('Local Ip', self.ipub))
+ lines.append(fmt('Public IP', self.pip))
+ lines.append(fmt('Internet', self.internetline))
lines.append('\n')
lines.append('SW Info:')
- lines.append(
- 'Installation ID: %s' % str(
- self.installation_id) if self.installation_id else 'Unknown')
- lines.append(
- 'Python version: %s' % str(
- self.python_version) if self.python_version else 'Unknown')
- lines.append('Distro: %s' %
- str(self.sw_distro) if self.sw_distro else 'Unknown')
- lines.append(
- 'Distro version: %s' % str(
- self.sw_distro_ver) if self.sw_distro_ver else 'Unknown')
- lines.append(
- 'Enigma version: %s' % str(
- self.sw_enigma_ver) if self.sw_enigma_ver else 'Unknown')
- lines.append('OE version: %s' %
- str(self.sw_oe_ver) if self.sw_oe_ver else 'Unknown')
+ lines.append(fmt('Installation ID', self.installation_id))
+ lines.append(fmt('Python version', self.python_version))
+ lines.append(fmt('Distro', self.sw_distro))
+ lines.append(fmt('Distro version', self.sw_distro_ver))
+ lines.append(fmt('Enigma version', self.sw_enigma_ver))
+ lines.append(fmt('OE version', self.sw_oe_ver))
lines.append('\n')
- lines.append(
- 'Video Format: %s' % str(
- self.current_format) if self.current_format else 'Unknown')
- lines.append('Mount Info: %s' %
- str(self.mountid) if self.mountid else 'Unknown')
- lines.append('Storage Info: %s' %
- str(self.storhdd) if self.storhdd else 'Unknown')
- lines.append('Memory Info: %s' %
- str(self.memin) if self.memin else 'Unknown')
- lines.append('Is VTi image: %s' % str(self.is_vti_image)
- if self.is_vti_image else 'Unknown')
- lines.append('Is DMM image: %s' % str(self.is_dmm_image)
- if self.is_dmm_image else 'Unknown')
+ lines.append(fmt('Video Format', self.current_format))
+ lines.append(fmt('Mount Info', self.mountid))
+ lines.append(fmt('Storage Info', self.storhdd))
+ lines.append(fmt('Memory Info', self.memin))
+ lines.append('Is VTi image: %s' % self.is_vti_image)
+ lines.append('Is DMM image: %s' % self.is_dmm_image)
except Exception as e:
print("Error formatting info:", e)
return '\n'.join(lines)
@@ -164,6 +145,7 @@ def get_internet_status(self):
# Test 1: Ping Google DNS
if system("ping -c 1 -W 2 8.8.8.8 >/dev/null 2>&1") == 0:
+ self.has_internet = True
return _("Internet: Connected")
# Test 2: TCP connection to port 80 (HTTP) of a reliable server
@@ -173,6 +155,7 @@ def get_internet_status(self):
sock.settimeout(3)
sock.connect(("www.google.com", 80))
sock.close()
+ self.has_internet = True
return _("Internet: Connected")
except BaseException:
pass
@@ -365,7 +348,7 @@ def get_public_ip(self):
for service in services:
try:
- response = requests.get(service, timeout=5)
+ response = requests.get(service, timeout=3)
if response.status_code == 200:
ip = response.text.strip()
if ip and '.' in ip: # Validazione base IP
diff --git a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/plugin.py b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/plugin.py
index 3755f1c..3d1eeb9 100644
--- a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/plugin.py
+++ b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/plugin.py
@@ -232,6 +232,7 @@
RequestUrl,
make_request,
refreshPlugins,
+ version_tuple,
xmlurl,
HALIGN,
__version__,
@@ -349,7 +350,28 @@ def __init__(self, list):
self.l.setFont(0, gFont("lsat", textfont))
-def LPListEntry(name, item):
+INSTALLED_COLOR = 0x39B54A
+
+
+def get_installed_packages():
+ """Lowercased names of every installed package (opkg or dpkg)."""
+ pkgs = set()
+ dpkg = exists("/var/lib/dpkg/info")
+ path = "/var/lib/dpkg/info" if dpkg else "/var/lib/opkg/info"
+ for root, dirs, files in walk(path):
+ for name in files:
+ name = name.lower()
+ if dpkg:
+ if name.endswith(".list"):
+ pkgs.add(name[:-5])
+ else:
+ if name.endswith(".control"):
+ pkgs.add(name[:-8])
+ break
+ return pkgs
+
+
+def LPListEntry(name, item, installed=False):
res = [(name, item)]
if not fileExists(pngx):
@@ -376,6 +398,11 @@ def LPListEntry(name, item):
icon_x_left = 5
text_x_left = 45
+ # Installed addons are shown in green
+ colors = {}
+ if installed:
+ colors = {"color": INSTALLED_COLOR, "color_sel": INSTALLED_COLOR}
+
if HALIGN == RT_HALIGN_RIGHT:
res.append(
MultiContentEntryPixmapAlphaTest(
@@ -392,7 +419,8 @@ def LPListEntry(name, item):
size=text_size,
font=0,
text=name,
- flags=HALIGN | RT_VALIGN_CENTER))
+ flags=HALIGN | RT_VALIGN_CENTER,
+ **colors))
else:
res.append(
MultiContentEntryPixmapAlphaTest(
@@ -409,13 +437,17 @@ def LPListEntry(name, item):
size=text_size,
font=0,
text=name,
- flags=HALIGN | RT_VALIGN_CENTER))
+ flags=HALIGN | RT_VALIGN_CENTER,
+ **colors))
return res
-def LPshowlist(data, list):
- plist = [LPListEntry(name, index) for index, name in enumerate(data)]
+def LPshowlist(data, list, installed=None):
+ plist = []
+ for index, name in enumerate(data):
+ flag = bool(installed and index < len(installed) and installed[index])
+ plist.append(LPListEntry(name, index, flag))
list.setList(plist)
@@ -465,10 +497,95 @@ def add_menu_item(menu_list, titles, pics, urls, title, pic_name, url=""):
urls.append(url) # add missing string for URL
-class LinuxsatPanel(Screen):
+class AsyncCall:
+ """Run a blocking function in a thread and deliver its result to a
+ callback on the enigma main thread (polled via eTimer)."""
- def __init__(self, session):
+ def __init__(self, fn, callback, poll_ms=100):
+ import threading
+ self._fn_result = None
+ self._done = False
+ self._cancelled = False
+ self._callback = callback
+ self._timer = eTimer()
+ try:
+ self._timer_conn = self._timer.timeout.connect(self._poll)
+ except BaseException:
+ self._timer.callback.append(self._poll)
+ thread = threading.Thread(target=self._run, args=(fn,))
+ thread.daemon = True
+ thread.start()
+ self._timer.start(poll_ms, False)
+
+ def _run(self, fn):
+ try:
+ self._fn_result = fn()
+ except Exception as e:
+ print("[AsyncCall] error:", e)
+ self._fn_result = None
+ self._done = True
+
+ def cancel(self):
+ self._cancelled = True
+ try:
+ self._timer.stop()
+ except BaseException:
+ pass
+ def _poll(self):
+ if self._cancelled or getattr(self, "_delivered", False):
+ return
+ if self._done:
+ self._delivered = True
+ self._timer.stop()
+ self._callback(self._fn_result)
+
+
+class AsyncMixin:
+ """Lets a screen start AsyncCalls that are cancelled automatically
+ when the screen closes, so a late result never touches a dead
+ widget."""
+
+ def _startAsync(self, fn, callback):
+ if not hasattr(self, "_async_calls"):
+ self._async_calls = []
+ self.onClose.append(self._cancelAsync)
+ call = AsyncCall(fn, callback)
+ self._async_calls.append(call)
+ return call
+
+ def _cancelAsync(self):
+ for call in getattr(self, "_async_calls", []):
+ call.cancel()
+
+
+# The addon catalog is fetched once and shared for the whole session;
+# a category click hits the cache and is instant
+_catalog_cache = {"data": None, "time": 0}
+CATALOG_TTL = 300
+
+
+def get_catalog(force=False):
+ """Blocking fetch with a session cache - call it from a thread."""
+ import time
+ now = time.time()
+ if not force and _catalog_cache["data"] is not None and \
+ now - _catalog_cache["time"] < CATALOG_TTL:
+ return _catalog_cache["data"]
+ data = checkGZIP(xmlurl)
+ if data:
+ _catalog_cache["data"] = data
+ _catalog_cache["time"] = now
+ return _catalog_cache["data"]
+
+
+class LPGridScreen(AsyncMixin, Screen):
+ """Shared 20-tile grid engine used by all category screens."""
+
+ PIXMAPS_PER_PAGE = 20
+ GRID_COLS = 5
+
+ def __init__(self, session):
Screen.__init__(self, session)
try:
Screen.setTitle(self, _("%s") % descplug + " V." + __version__)
@@ -480,7 +597,6 @@ def __init__(self, session):
skin = join(skin_path, "LinuxsatPanel.xml")
with codecs.open(skin, "r", encoding="utf-8") as f:
self.skin = f.read()
-
if isWQHD():
self.pos = get_positions("WQHD")
elif isFHD():
@@ -488,7 +604,255 @@ def __init__(self, session):
elif isHD():
self.pos = get_positions("HD")
- self.data = checkGZIP(xmlurl)
+ def initGrid(self, menu_list):
+ """Wire widgets, actions and paging once the menu lists are filled."""
+ self.names = menu_list
+ self.sorted = False
+ self["frame"] = MovingPixmap()
+ self["info"] = Label()
+ self["info"].setText(_("Please Wait..."))
+ self["sort"] = Label(_("Sort A-Z"))
+ self["key_red"] = Label(_("Exit"))
+ self["key_green"] = Label(_("Search"))
+ self["pixmap"] = Pixmap()
+ self["actions"] = ActionMap(
+ [
+ "OkCancelActions",
+ "MenuActions",
+ "DirectionActions",
+ "NumberActions",
+ "ColorActions",
+ "EPGSelectActions",
+ "InfoActions"
+ ],
+ {
+ "ok": self.okbuttonClick,
+ "cancel": self.closeNonRecursive,
+ "exit": self.closeRecursive,
+ "back": self.closeNonRecursive,
+ "red": self.closeNonRecursive,
+ "green": self.key_search,
+ "0": self.list_sort,
+ "left": self.key_left,
+ "right": self.key_right,
+ "up": self.key_up,
+ "down": self.key_down,
+ "info": self.key_info,
+ "menu": self.closeRecursive
+ },
+ -1
+ )
+ i = 0
+ while i < self.PIXMAPS_PER_PAGE:
+ self["label" + str(i + 1)] = StaticText()
+ self["pixmap" + str(i + 1)] = Pixmap()
+ i += 1
+ self.npics = len(self.names)
+ self.npage = max(1, (self.npics + self.PIXMAPS_PER_PAGE - 1) // self.PIXMAPS_PER_PAGE)
+ self.index = 0
+ self.maxentry = len(menu_list) - 1
+ self.ipage = 1
+ self.onLayoutFinish.append(self.openTest)
+
+ def okbuttonClick(self):
+ pass
+
+ def key_search(self):
+ from Screens.VirtualKeyBoard import VirtualKeyBoard
+ self.session.openWithCallback(
+ self.searchCallback,
+ VirtualKeyBoard,
+ title=_("Search addon..."),
+ text="")
+
+ def searchCallback(self, text=None):
+ # Default behavior: jump to the first matching tile of this grid
+ if not text:
+ return
+ text = text.lower()
+ for idx, name in enumerate(self.names):
+ if text in str(name).lower():
+ self.ipage = idx // self.PIXMAPS_PER_PAGE + 1
+ self.openTest()
+ self.index = idx
+ self.paintFrame()
+ return
+ self.session.open(
+ MessageBox,
+ _("Nothing found for '%s'") % text,
+ MessageBox.TYPE_INFO,
+ timeout=5)
+
+ def _catalogReady(self, data):
+ self.data = data
+
+ def _openCategory(self, title, name):
+ self["info"].setText(_("Loading..."))
+
+ def catalog_ready(data):
+ self.data = data
+ self["info"].setText(str(name))
+ if data is not None:
+ n1 = data.find(title, 0)
+ n2 = data.find("", n1)
+ url = data[n1:n2]
+ self.session.open(addInstall, url, name, None)
+ else:
+ self.session.open(
+ MessageBox, _("Error: No Data Find."),
+ MessageBox.TYPE_ERROR
+ )
+
+ self._startAsync(get_catalog, catalog_ready)
+
+ def paintFrame(self):
+ try:
+ # If the index exceeds the maximum number of items, it returns to
+ # the first item
+ if self.index > self.maxentry:
+ self.index = self.minentry
+ self.idx = self.index
+ name = self.names[self.idx]
+ self["info"].setText(str(name))
+ ifr = self.index - (self.PIXMAPS_PER_PAGE * (self.ipage - 1))
+ ipos = self.pos[ifr]
+ self["frame"].moveTo(ipos[0], ipos[1], 1)
+ self["frame"].startMoving()
+ except Exception as e:
+ print("Error in paintFrame: ", e)
+
+ def openTest(self):
+ if self.ipage == self.npage:
+ self.maxentry = len(self.pics) - 1
+ else:
+ self.maxentry = (self.PIXMAPS_PER_PAGE * self.ipage) - 1
+ self.minentry = (self.ipage - 1) * self.PIXMAPS_PER_PAGE
+ self.npics = len(self.pics)
+ ln = self.maxentry - self.minentry + 1
+ for i in range(self.PIXMAPS_PER_PAGE):
+ pixmap = self["pixmap" + str(i + 1)]
+ if i < ln:
+ idx = self.minentry + i
+ pic = self.pics[idx]
+ if not exists(pic):
+ pic = nss_pic
+ pixmap.instance.setPixmapFromFile(pic)
+ pixmap.show()
+ else:
+ # a partial last page hides the unused tiles instead of
+ # filling them with the placeholder picture
+ self["label" + str(i + 1)].setText(" ")
+ pixmap.hide()
+ self.index = self.minentry
+ self.paintFrame()
+
+ def key_left(self):
+ # One step back; from the first tile of a page wrap to the last
+ # tile of the previous page (or the last page from page 1)
+ if self.index > self.minentry:
+ self.index -= 1
+ else:
+ self.ipage = self.npage if self.ipage == 1 else self.ipage - 1
+ self.openTest()
+ self.index = self.maxentry
+ self.paintFrame()
+
+ def key_right(self):
+ # One step forward; from the last tile of a page wrap to the
+ # first tile of the next page (or page 1 from the last page)
+ if self.index < self.maxentry:
+ self.index += 1
+ else:
+ self.ipage = 1 if self.ipage == self.npage else self.ipage + 1
+ self.openTest()
+ self.index = self.minentry
+ self.paintFrame()
+
+ def key_up(self):
+ # One row up; from the top row go to the bottom row of the
+ # previous page (or the last page), keeping the column
+ if self.index - self.GRID_COLS >= self.minentry:
+ self.index -= self.GRID_COLS
+ else:
+ col = (self.index - self.minentry) % self.GRID_COLS
+ self.ipage = self.npage if self.ipage == 1 else self.ipage - 1
+ self.openTest()
+ rows = (self.maxentry - self.minentry) // self.GRID_COLS
+ self.index = min(
+ self.minentry + rows * self.GRID_COLS + col, self.maxentry)
+ self.paintFrame()
+
+ def key_down(self):
+ # One row down; from the bottom row go to the top row of the
+ # next page (or page 1), keeping the column
+ if self.index + self.GRID_COLS <= self.maxentry:
+ self.index += self.GRID_COLS
+ else:
+ col = (self.index - self.minentry) % self.GRID_COLS
+ self.ipage = 1 if self.ipage == self.npage else self.ipage + 1
+ self.openTest()
+ self.index = min(self.minentry + col, self.maxentry)
+
+ self.paintFrame()
+
+ def list_sort(self):
+ if not hasattr(self, "original_data"):
+ self.original_data = (
+ self.names[:],
+ self.titles[:],
+ self.pics[:],
+ self.urls[:])
+ self.sorted = False
+
+ if self.sorted:
+ self.names, self.titles, self.pics, self.urls = self.original_data
+ self.sorted = False
+ self["sort"].setText(_("Sort A-Z"))
+ else:
+ self.names, self.titles, self.pics, self.urls = ListSortUtility.list_sort(
+ self.names, self.titles, self.pics, self.urls)
+ self.sorted = True
+ self["sort"].setText(_("Sort Default"))
+
+ self.openTest()
+
+ def closeNonRecursive(self):
+ self.close(False)
+
+ def closeRecursive(self):
+ self.close(True)
+
+ def createSummary(self):
+ return
+
+ def key_info(self):
+ self.session.open(LSinfo, " Information ")
+
+ def _view_log(self, answer):
+ if answer:
+ from enigma import eTimer
+
+ def open_fc():
+ from .addons.File_Commander import File_Commander
+ if fileExists(file_log):
+ self.session.open(File_Commander, file_log)
+ # keep a reference or the timer is garbage collected
+ # before it fires
+ self._fc_timer = eTimer()
+ self._fc_timer.callback.append(open_fc)
+ self._fc_timer.start(0, True)
+
+
+class LinuxsatPanel(LPGridScreen):
+
+ def __init__(self, session):
+
+ LPGridScreen.__init__(self, session)
+
+ # Warm the catalog cache in the background so the first
+ # category click is instant
+ self.data = None
+ self._startAsync(get_catalog, self._catalogReady)
menu_list = []
self.titles = []
self.pics = []
@@ -835,252 +1199,65 @@ def __init__(self, session):
" About ",
"about.png")
- self.names = menu_list
- self.sorted = False
- # self.combined_data = list(zip(self.names, self.titles, self.pics, self.urls))
- self["frame"] = MovingPixmap()
- self["info"] = Label()
- self["info"].setText(_("Please Wait..."))
- self["sort"] = Label(_("Sort A-Z"))
- self["key_red"] = Label(_("Exit"))
- self["pixmap"] = Pixmap()
- self["actions"] = ActionMap(
- [
- "OkCancelActions",
- "MenuActions",
- "DirectionActions",
- "NumberActions",
- "ColorActions",
- "EPGSelectActions",
- "InfoActions"
- ],
- {
- "ok": self.okbuttonClick,
- "cancel": self.closeNonRecursive,
- "exit": self.closeRecursive,
- "back": self.closeNonRecursive,
- "red": self.closeNonRecursive,
- "0": self.list_sort,
- "left": self.key_left,
- "right": self.key_right,
- "up": self.key_up,
- "down": self.key_down,
- "info": self.key_info,
- "menu": self.closeRecursive
- },
- -1
- )
-
- self.PIXMAPS_PER_PAGE = 20
- i = 0
- while i < self.PIXMAPS_PER_PAGE:
- self["label" + str(i + 1)] = StaticText()
- self["pixmap" + str(i + 1)] = Pixmap()
- i += 1
+ self.initGrid(menu_list)
+ self.start_check_version()
- self.npics = len(self.names)
- self.npage = int(round(self.npics // self.PIXMAPS_PER_PAGE)) + 1
- # self.npage = int(float(self.npics // self.PIXMAPS_PER_PAGE)) + 1
- self.index = 0
- self.maxentry = len(menu_list) - 1
- self.ipage = 1
- self.onLayoutFinish.append(self.openTest)
- # self.onLayoutFinish.append(self.start_check_version)
-
- def start_check_version(self):
- self.Update = False
- self.new_version, self.new_changelog, update_available = check_version(
- __version__, installer_url, AgentRequest
- )
- if update_available:
- self.Update = True
- print("A new version is available:", self.new_version)
-
- # Check if current screen is modal before opening the MessageBox
- if self.session.current_dialog and getattr(
- self.session.current_dialog, "isModal", lambda: False)():
- msg = _(
- "New version available\n\nChangelog:\n\nPress the green button to start the update.")
- msg = msg.replace(
- "available",
- "available %s" %
- self.new_version)
- msg = msg.replace(
- "Changelog:",
- "Changelog: %s" %
- self.new_changelog)
+ def searchCallback(self, text=None):
+ # Search the whole catalog across every category and show the
+ # matches as a normal installable list
+ if not text:
+ return
+ text = text.lower()
+ self["info"].setText(_("Searching..."))
+
+ def do_search():
+ data = get_catalog()
+ if data is None:
+ return None
+ regex = compile(r'
C:\s+([\w.-]+)\s+(\d+)\s+(\w+)\s+([\w.-]+)\s*',
r'"C: (.*?) (.*?) (.*?) (.*?)"',
r'"c: (.*?) (.*?) (.*?) (.*?)"',
@@ -3833,6 +2814,8 @@ def getcl(self, config_type):
timeout=6
)
+ break
+
except Exception as e:
# Error handling
self.session.open(
@@ -3840,7 +2823,7 @@ def getcl(self, config_type):
str(e), type=MessageBox.TYPE_INFO, timeout=8)
-class addInstall(Screen):
+class addInstall(AsyncMixin, Screen):
def __init__(self, session, data, name, dest):
Screen.__init__(self, session)
@@ -3960,9 +2943,25 @@ def openTest(self):
self.names.append(name)
self.urls.append(url)
- LPshowlist(self.names, self["list"])
+ LPshowlist(self.names, self["list"], self.installedFlags())
# self.buttons()
+ def installedFlags(self):
+ """One flag per url: is that package already installed?"""
+ pkgs = get_installed_packages()
+ flags = []
+ for url in self.urls:
+ fname = url[url.rfind("/") + 1:].lower()
+ flag = False
+ if ".ipk" in fname or ".deb" in fname:
+ plug = fname.split("_")[0]
+ if plug.endswith((".ipk", ".deb")):
+ plug = plug.rsplit(".", 1)[0]
+ if plug:
+ flag = any(p.startswith(plug) for p in pkgs)
+ flags.append(flag)
+ return flags
+
def buttons(self):
"""
if HALIGN == RT_HALIGN_RIGHT:
@@ -4076,7 +3075,17 @@ def okClicked(self, choice, name, url):
if choice == "install":
folddest = "/tmp/" + self.plug
- if self.retfile(folddest):
+ self["info"].setText(_("Downloading %s ...") % self.plug)
+
+ def downloaded(ok):
+ self["info"].setText(_("Category: ") + self.name)
+ if not ok:
+ self.session.open(
+ MessageBox,
+ _("Download failed!"),
+ MessageBox.TYPE_ERROR,
+ timeout=5)
+ return
command = ""
if ".deb" in self.plug:
command = "dpkg -i '/tmp/" + self.plug + "'"
@@ -4099,6 +3108,8 @@ def okClicked(self, choice, name, url):
[command],
closeOnSuccess=False)
+ self._startAsync(lambda: self.retfile(folddest), downloaded)
+
elif choice == "uninstall":
if ".deb" in self.plug:
if not has_dpkg:
@@ -4138,7 +3149,7 @@ def okClicked(self, choice, name, url):
def retfile(self, dest):
import requests
- response = requests.get(self.url)
+ response = requests.get(self.url, timeout=30)
if response.status_code == 200:
with open(dest, "wb") as f:
f.write(response.content)
@@ -4149,10 +3160,17 @@ def retfile(self, dest):
return False
def downxmlpage(self):
+ # The provider page is fetched in the background; parsing happens
+ # on the main thread once the data is here
self.downloading = False
- r = make_request(self.fxml)
+ self["info"].setText(_("Loading list..."))
+ self._startAsync(lambda: make_request(self.fxml), self._xmlPageReady)
+
+ def _xmlPageReady(self, r):
+ self["info"].setText(_("Category: ") + self.name)
if r is None:
print("Error: No data received from make_request")
+ self["info"].setText(_("Download page get failed ..."))
return
self.names = []
self.urls = []
@@ -4282,7 +3300,7 @@ def Lcn(self, answer=None):
_("Do you want to Order LCN Bouquet?"),
MessageBox.TYPE_YESNO
)
- else:
+ elif answer:
print("Starting LCN scan...")
try:
from .LCNScanner.Terrestrial import PluginSetup
@@ -4293,11 +3311,11 @@ def Lcn(self, answer=None):
def LcnXX(self, answer=None):
if answer is None:
self.session.openWithCallback(
- self.Lcn,
+ self.LcnXX,
MessageBox,
_("Do you want to Order LCN Bouquet?"),
MessageBox.TYPE_YESNO)
- else:
+ elif answer:
print("Starting LCN scan...")
try:
lcn_scanner_instance = LCNScanner()
@@ -4334,48 +3352,104 @@ def okRun(self):
def okRun1(self, answer=False):
dest = "/tmp/settings.zip"
if answer:
- global setx
if self.downloading is True:
idx = self["list"].getSelectionIndex()
url = self.urls[idx]
self.namel = ""
- if "dtt" not in url.lower():
- setx = 1
- terrestrial()
- if keepiptv():
- print("-----save iptv channels-----")
-
- from six.moves.urllib.request import urlretrieve
- urlretrieve(url, dest)
- if exists(dest) and ".zip" in dest:
- fdest1 = "/tmp/unzipped"
- fdest2 = "/etc/enigma2"
- if exists("/tmp/unzipped"):
- system("rm -rf /tmp/unzipped")
- makedirs("/tmp/unzipped")
- cmd2 = "unzip -o -q '/tmp/settings.zip' -d " + fdest1
- system(cmd2)
- for root, dirs, files in walk(fdest1):
- for name in dirs:
- self.namel = name
- system("rm -rf /etc/enigma2/lamedb")
- system("rm -rf /etc/enigma2/*.radio")
- system("rm -rf /etc/enigma2/*.tv")
- system("rm -rf /etc/enigma2/*.del")
- system("cp -rf '/tmp/unzipped/" +
- str(self.namel) + "/'* " + fdest2)
- system("rm -rf /tmp/unzipped")
- system("rm -rf /tmp/settings.zip")
- title = (_("Installing %s\nPlease Wait...") % self.name)
- self.session.openWithCallback(
- self.yes,
- lsConsole,
- title=_(title),
- cmdlist=["wget -qO - http://127.0.0.1/web/servicelistreload?mode=0 > /tmp/inst.txt 2>&1 &"],
- closeOnSuccess=False)
+ self["info"].setText(_("Installing settings..."))
+ self._startAsync(
+ lambda: self._installSettings(url, dest),
+ self._settingsDone)
else:
self["info"].setText(_("Settings Not Installed ..."))
+ def _installSettings(self, url, dest):
+ """Download, verify, backup, wipe, install. Runs in a background
+ thread; returns an error message, or "" on success."""
+ global setx
+ if "dtt" not in url.lower():
+ setx = 1
+ terrestrial()
+ if keepiptv():
+ print("-----save iptv channels-----")
+
+ fdest1 = "/tmp/unzipped"
+ fdest2 = "/etc/enigma2"
+ backup = "/tmp/settings_backup.tar.gz"
+
+ def cleanup_tmp():
+ system("rm -rf " + fdest1)
+ system("rm -f " + dest)
+
+ # Download and verify BEFORE touching /etc/enigma2, so a
+ # failed transfer can never leave the box without channels
+ try:
+ response = requests.get(url, timeout=30)
+ response.raise_for_status()
+ with open(dest, "wb") as f:
+ f.write(response.content)
+ except Exception as e:
+ print("[Settings] download failed:", e)
+ return _("Download failed! Settings NOT installed.")
+
+ if exists(fdest1):
+ system("rm -rf " + fdest1)
+ makedirs(fdest1)
+ if system("unzip -o -q '%s' -d %s" % (dest, fdest1)) != 0:
+ print("[Settings] corrupted archive:", url)
+ cleanup_tmp()
+ return _("Corrupted archive! Settings NOT installed.")
+
+ # The channel list may live at the root of the zip or in a
+ # single top-level folder
+ srcdir = fdest1
+ for root, dirs, files in walk(fdest1):
+ if dirs and not files:
+ self.namel = dirs[0]
+ srcdir = join(fdest1, self.namel)
+ break
+ payload = []
+ for root, dirs, files in walk(srcdir):
+ payload.extend(files)
+ break
+ if "lamedb" not in payload and not any(
+ name.endswith(".tv") for name in payload):
+ print("[Settings] no channel list in archive:", url)
+ cleanup_tmp()
+ return _("No channel list in archive! Settings NOT installed.")
+
+ # Safety net: keep the current configuration until reboot
+ system("tar -czf %s -C / etc/enigma2 2>/dev/null" % backup)
+
+ system("rm -rf /etc/enigma2/lamedb")
+ system("rm -rf /etc/enigma2/*.radio")
+ system("rm -rf /etc/enigma2/*.tv")
+ system("rm -rf /etc/enigma2/*.del")
+ if system("cp -rf '%s/'* %s" % (srcdir, fdest2)) != 0:
+ system("tar -xzf %s -C / 2>/dev/null" % backup)
+ print("[Settings] install failed, backup restored")
+ cleanup_tmp()
+ return _("Install failed! Previous settings restored.")
+ cleanup_tmp()
+ return ""
+
+ def _settingsDone(self, error):
+ if error is None:
+ # the worker raised unexpectedly
+ self["info"].setText(_("Install failed! Settings NOT installed."))
+ return
+ if error:
+ self["info"].setText(error)
+ return
+ self["info"].setText(_("Category: ") + self.name)
+ title = (_("Installing %s\nPlease Wait...") % self.name)
+ self.session.openWithCallback(
+ self.yes,
+ lsConsole,
+ title=_(title),
+ cmdlist=["wget -qO - http://127.0.0.1/web/servicelistreload?mode=0 > /tmp/inst.txt 2>&1 &"],
+ closeOnSuccess=False)
+
def pas(self, call=None):
pass
@@ -4401,7 +3475,7 @@ def removenow(self, answer=False):
if ".zip" in ipk:
return
n2 = ipk.find("_", 0)
- self.iname = ipk[:n2]
+ self.iname = ipk[:n2] if n2 != -1 else ipk.rsplit(".", 1)[0]
cmd = "opkg remove '" + self.iname + "'"
title = (_("Removing %s") % self.iname)
self.session.open(lsConsole, _(title), cmdlist=[cmd])
@@ -4540,8 +3614,7 @@ def check_vers(self):
self.new_changelog = str(self.new_changelog)
if not isinstance(self.new_version, str):
self.new_version = str(self.new_version)
- # if float(__version__) < float(remote_version):
- if __version__ < remote_version:
+ if version_tuple(__version__) < version_tuple(remote_version):
self.Update = True
self.show_update_message()
@@ -4739,6 +3812,49 @@ def startRun(self):
self["list"].setText(_("Unable to download updates!"))
def openinfo(self):
+ # Collect in a background thread: the first stbinfo import runs
+ # network probes and must not freeze the GUI. The screen updates
+ # from an eTimer on the main thread when the data is ready.
+ import threading
+ self["list"].setText(_("Collecting system information..."))
+ self._info_content = None
+ self._info_closed = False
+ self.onClose.append(self._stopInfoPoll)
+
+ def collect():
+ try:
+ self._info_content = self._collectInfo()
+ except Exception as e:
+ print("Error in openinfo collect:", e)
+ self._info_content = "Error loading information"
+
+ info_thread = threading.Thread(target=collect)
+ info_thread.daemon = True
+ info_thread.start()
+
+ self.info_poll = eTimer()
+ try:
+ self.info_poll_conn = self.info_poll.timeout.connect(
+ self._checkInfoReady)
+ except BaseException:
+ self.info_poll.callback.append(self._checkInfoReady)
+ self.info_poll.start(250, False)
+
+ def _stopInfoPoll(self):
+ self._info_closed = True
+ try:
+ self.info_poll.stop()
+ except BaseException:
+ pass
+
+ def _checkInfoReady(self):
+ if self._info_closed:
+ return
+ if self._info_content is not None:
+ self.info_poll.stop()
+ self["list"].setText(str(self._info_content))
+
+ def _collectInfo(self):
from .addons.stbinfo import stbinfo
try:
header = "Suggested by: @masterG - @oktus - @pcd\n"
@@ -4802,14 +3918,14 @@ def openinfo(self):
with open("/tmp/output.txt", "r") as filer:
content = filer.read().decode("utf-8")
- self["list"].setText(str(content))
+ return str(content)
except Exception as e:
print("Final read/display error: {0}".format(str(e)))
- self["list"].setText("Error loading system information")
+ return "Error loading system information"
except Exception as e:
print("Error in openinfo:", e)
- self["list"].setText("Error loading information")
+ return "Error loading information"
def cancel(self):
self.close()
diff --git a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/sh/bissfeedautokey.sh b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/sh/bissfeedautokey.sh
deleted file mode 100644
index c95491e..0000000
--- a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/sh/bissfeedautokey.sh
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/bin/bash
-#DESCRIPTION=BissFeedAutoKey
-if [ -d /usr/lib/enigma2/python/Plugins/Extensions/BissFeedAutoKey ]; then
-echo "> removing package please wait..."
-sleep 3s
-rm -rf /usr/lib/enigma2/python/Plugins/Extensions/BissFeedAutoKey > /dev/null 2>&1
-rm -rf /usr/lib/enigma2/python/Components/Renderer/ProgressPixmap.py > /dev/null 2>&1
-rm -rf /usr/lib/enigma2/python/Components/Converter/ServiceName2.py > /dev/null 2>&1
-rm -rf /usr/lib/enigma2/python/Components/Converter/ServiceInfo2.py > /dev/null 2>&1
-
-status='/var/lib/opkg/status'
-package='enigma2-plugin-extensions-bissfeed-autokey'
-package1='enigma2-plugin-extensions-bissfeedautokey'
-
-if grep -q $package $status; then
-opkg remove $package
-fi
-if grep -q $package1 $status; then
-opkg remove $package1
-fi
-
-echo "*******************************************"
-echo "* Removed Finished *"
-echo "* Uploaded By Eliesat *"
-echo "*******************************************"
-sleep 3s
-
-else
-
-##remove unnecessary files and folders
-if [ -d "/CONTROL" ]; then
-rm -r /CONTROL >/dev/null 2>&1
-fi
-rm -rf /control >/dev/null 2>&1
-rm -rf /postinst >/dev/null 2>&1
-rm -rf /preinst >/dev/null 2>&1
-rm -rf /prerm >/dev/null 2>&1
-rm -rf /postrm >/dev/null 2>&1
-rm -rf /tmp/*.ipk >/dev/null 2>&1
-rm -rf /tmp/*.tar.gz >/dev/null 2>&1
-
-##check install deps
-## Check python
-pyVersion=$(python -c"from sys import version_info; print(version_info[0])")
-
-##config
-plugin=bissfeedautokey
-version=2.8
-
-if [ "$pyVersion" = 3 ]; then
-url=https://gitlab.com/eliesat/extensions/-/raw/main/bissfeedautokey/bissfeedautokey-py3-2.8.tar.gz
-package=/var/volatile/tmp/$plugin-py3-$version.tar.gz
-else
-url=https://gitlab.com/eliesat/extensions/-/raw/main/bissfeedautokey/bissfeedautokey-py2-2.8.tar.gz
-package=/var/volatile/tmp/$plugin-py2-$version.tar.gz
-fi
-
-##download & install
-echo "> Downloading $plugin-$version package please wait ..."
-sleep 3s
-
-wget -O $package --no-check-certificate $url
-tar -xzf $package -C /
-extract=$?
-rm -rf $package >/dev/null 2>&1
-
-
-echo ''
-if [ $extract -eq 0 ]; then
-echo "> $plugin-$version package installed successfully"
-echo "> Uploaded By ElieSat"
-sleep 3s
-
-else
-
-echo "> $plugin-$version package installation failed"
-sleep 3s
-fi
-
-fi
-exit
diff --git a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/sh/ipaudiopro_1.4.sh b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/sh/ipaudiopro_1.4.sh
deleted file mode 100644
index fdc3c54..0000000
--- a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/sh/ipaudiopro_1.4.sh
+++ /dev/null
@@ -1,120 +0,0 @@
-#! /bin/bash
-##DESCRIPTION=IPAUDIO
-wget https://raw.githubusercontent.com/emil237/updates-enigma/main/update-all-python.sh -O - | /bin/bash
-
-plugin="ipaudiopro"
-git_url="https://github.com/emilnabil/ipaudiopro/raw/refs/heads/main"
-version="1.4"
-PLUGIN_PATH="/usr/lib/enigma2/python/Plugins/Extensions/IPaudioPro"
-package="enigma2-plugin-extensions-$plugin"
-temp_dir="/tmp"
-OPKG_DIR="/etc/opkg/"
-PYTHON_VERSION=$(python --version 2>&1 | awk '{print $2}')
-
-if [ -z "$PYTHON_VERSION" ]; then
- echo "Python is not installed or could not detect Python version."
- exit 1
-fi
-
-if command -v apt-get > /dev/null 2>&1; then
-INSTALL="apt-get install -y"
-CHECK_INSTALLED="dpkg -l"
-CHECK_VERSION="dpkg-query -W -f='\${Version}'"
-OS='DreamOS'
-
-elif command -v opkg > /dev/null 2>&1; then
-INSTALL="opkg install --force-reinstall --force-depends"
-CHECK_INSTALLED="opkg list-installed"
-CHECK_VERSION="opkg info"
-OS='Opensource'
-
-else
-echo "Unsupported OS"
-exit 1
-
-fi
-
-if [ -d "$PLUGIN_PATH" ]; then
-echo "Removing existing plugin..."
-
-if command -v opkg > /dev/null; then
-opkg remove enigma2-plugin-extensions-ipaudiopro
-
-elif command -v apt-get > /dev/null; then
-apt-get remove enigma2-plugin-extensions-ipaudiopro -y
-
-fi
-rm -rf "$PLUGIN_PATH"
-fi
-
-arch=$(uname -m)
-
-case $PYTHON_VERSION in
-
-3.9.*) PYTHON='PY3'; PY_VERSION='3_9';;
-3.10.*) PYTHON='PY3'; PY_VERSION='3_11';;
-3.11.*) PYTHON='PY3'; PY_VERSION='3_11';;
-3.12.[1-5]) PYTHON='PY3'; PY_VERSION='3_12';;
-3.12.[6-9]) PYTHON='PY3'; PY_VERSION='3_12_ff7';;
-3.13.*) PYTHON='PY3'; PY_VERSION='py3_13_ff7';;
-2.7.*) PYTHON='PY2'; PY_VERSION='2_7';;
-*)
-echo "Python version not supported."
-sleep 4
-exit 1
-;;
-
-esac
-
-if [ "$arch" = "mips" ]; then
-package_url="https://github.com/emilnabil/ipaudiopro/raw/refs/heads/main/enigma2-plugin-extensions-ipaudiopro_${version}_mips32el_py${PY_VERSION}.ipk"
-elif [ "$arch" = "armv7l" ]; then
-if ls "$OPKG_DIR" | grep -q "cortexa15hf-neon-vfpv4"; then
-package_url="https://github.com/emilnabil/ipaudiopro/raw/refs/heads/main/enigma2-plugin-extensions-ipaudiopro_${version}_cortexa15hf-neon-vfpv4_py${PY_VERSION}.ipk"
-elif ls "$OPKG_DIR" | grep -q "cortexa9hf-neon"; then
-package_url="https://github.com/emilnabil/ipaudiopro/raw/refs/heads/main/enigma2-plugin-extensions-ipaudiopro_${version}_cortexa9hf-neon_py${PY_VERSION}.ipk"
-elif ls "$OPKG_DIR" | grep -q "cortexa7hf-vfp"; then
-package_url="https://github.com/emilnabil/ipaudiopro/raw/refs/heads/main/enigma2-plugin-extensions-ipaudiopro_${version}_cortexa7hf-vfp_py${PY_VERSION}.ipk"
-elif ls "$OPKG_DIR" | grep -q "armv7ahf-neon"; then
-package_url="https://github.com/emilnabil/ipaudiopro/raw/refs/heads/main/enigma2-plugin-extensions-ipaudiopro_${version}_armv7ahf-neon_py${PY_VERSION}.ipk"
-else
-echo "Unknown CPU architecture"
-exit 1
-fi
-
-else
-echo "Unsupported architecture"
-exit 1
-fi
-
-cd /tmp || exit 1
-
-wget "$package_url" -O enigma2-plugin-extensions-ipaudiopro.ipk
-
-$INSTALL "/tmp/enigma2-plugin-extensions-ipaudiopro.ipk"
-rm -f "/tmp/enigma2-plugin-extensions-ipaudiopro.ipk"
-wget -O "/usr/lib/enigma2/python/Plugins/Extensions/IPaudioPro/logo.png" "https://dreambox4u.com/emilnabil237/plugins/ipaudiopro/logo.png"
-wget -O "/etc/enigma2/IPAudioPro.json" "https://dreambox4u.com/emilnabil237/plugins/ipaudiopro/IPAudioPro.json"
-
-echo "###############################################################
-# IPAudioPro version 1.4 installed #
-# Uploaded By Emil_Nabil #
-###############################################################"
-
-sleep 3
-echo " Your Device Will RESTART Now "
-sleep 2
-if grep -q "DreamOS" /etc/issue; then
-if command -v systemctl > /dev/null 2>&1; then
-echo "Restarting Enigma2 using systemctl..."
-systemctl restart enigma2
-else
-echo "Systemctl not found, restarting Enigma2 using killall..."
-killall -9 enigma2
-fi
-else
-echo "Killing and restarting Enigma2 process..."
-killall -9 enigma2
-fi
-
-exit 0
diff --git a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/skins/fhd/LinuxsatPanel.xml b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/skins/fhd/LinuxsatPanel.xml
index e1ca2d8..92973a8 100644
--- a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/skins/fhd/LinuxsatPanel.xml
+++ b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/skins/fhd/LinuxsatPanel.xml
@@ -61,5 +61,7 @@