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 @@

- Version + Version 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'', DOTALL) + parts = [m.group(0) for m in regex.finditer(data) + if text in m.group(1).lower()] + return "\n".join(parts) + + def done(result): + self["info"].setText(_("Search")) + if result is None: + self.session.open( + MessageBox, _("Error: No Data Find."), + MessageBox.TYPE_ERROR) + elif not result: self.session.open( MessageBox, - msg, + _("Nothing found for '%s'") % text, MessageBox.TYPE_INFO, - timeout=5 - ) + timeout=5) else: - msg = _( - "New version available\n\nChangelog:\n\nBut not downloadable!!!") - msg = msg.replace( - "available", - "available %s" % - self.new_version) - msg = msg.replace( - "Changelog:", - "Changelog: %s" % - self.new_changelog) self.session.open( - MessageBox, - msg, - MessageBox.TYPE_INFO, - timeout=5 - ) - else: - print("No new version available.") + addInstall, result, _("Search: %s") % text, None) - 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 = (self.PIXMAPS_PER_PAGE * self.ipage) - 1 - self.minentry = (self.ipage - 1) * self.PIXMAPS_PER_PAGE - - elif self.ipage == self.npage: - self.maxentry = len(self.pics) - 1 - self.minentry = (self.ipage - 1) * self.PIXMAPS_PER_PAGE - i1 = 0 - while i1 < self.PIXMAPS_PER_PAGE: - self["label" + str(i1 + 1)].setText(" ") - self["pixmap" + str(i1 + 1) - ].instance.setPixmapFromFile(nss_pic) - i1 += 1 - self.npics = len(self.pics) - i = 0 - i1 = 0 - self.picnum = 0 - ln = self.maxentry - (self.minentry - 1) - while i < ln: - idx = self.minentry + i - # self["label" + str(i + 1)].setText(self.names[idx]) # this show - # label to bottom of png pixmap - pic = self.pics[idx] - if not exists(self.pics[idx]): - pic = nss_pic - self["pixmap" + str(i + 1)].instance.setPixmapFromFile(pic) - i += 1 - self.index = self.minentry - self.paintFrame() - - def key_left(self): - # Decrement the index only if we are not at the first pixmap - if self.index >= 0: - self.index -= 1 - else: - # If we are at the first pixmap, go back to the last pixmap of the - # last page - self.ipage = self.npage - self.index = self.npics - 1 - # Check if we need to change pages - if self.index < self.minentry: - self.ipage -= 1 - if self.ipage < 1: # If we go beyond the first page - self.ipage = self.npage - self.index = self.npics - 1 # Back to the last pixmap of the last page - self.openTest() - else: - self.paintFrame() + self._startAsync(do_search, done) - def key_right(self): - # Increment the index only if we are not at the last pixmap - if self.index < self.npics - 1: - self.index += 1 - else: - # If we are at the last pixmap, go back to the first pixmap of the - # first page - self.index = 0 - self.ipage = 1 - self.openTest() - # Check if we need to change pages - if self.index > self.maxentry: - self.ipage += 1 - if self.ipage > self.npage: # If we exceed the number of pages - self.index = 0 - self.ipage = 1 # Back to first page - self.openTest() - else: - self.paintFrame() - - def key_up(self): - if self.index == 0 and self.ipage == 1: - self.ipage = self.npage - self.index = self.minentry - self.openTest() - - elif self.index >= 5 and not self.ipage == self.npage and self.index == self.minentry: - self.index -= 5 - else: - if self.ipage == self.npage and self.index == self.minentry: - self.ipage = 1 - self.index = 0 - self.openTest() - else: - self.ipage = self.npage - self.index = self.npics - 1 - self.openTest() - self.paintFrame() - - def key_down(self): - if self.index <= self.maxentry - 5: - self.index += 5 - else: - if self.ipage == self.npage: - self.ipage = 1 - self.index = 0 - self.openTest() - else: - self.ipage += 1 - self.index = self.minentry - self.openTest() - - self.paintFrame() - - def keyNumberGlobal(self, number): - number -= 1 - if len(self["menu"].list) > number: - self["menu"].setIndex(number) - self.okbuttonClick() - - def list_sort(self): - if not hasattr(self, "original_data"): - self.original_data = ( - self.names[:], - self.titles[:], - self.pics[:], - self.urls[:]) - self.sorted = False + def start_check_version(self): + self._startAsync( + lambda: check_version(__version__, installer_url, AgentRequest), + self._versionChecked) - if self.sorted: - self.names, self.titles, self.pics, self.urls = self.original_data - self.sorted = False - self["sort"].setText(_("Sort A-Z")) + def _versionChecked(self, result): + if not result: + return + new_version, new_changelog, update_available = result + if update_available: + print("A new version is available:", new_version) + msg = _("New version %s available!\n\nChangelog:\n%s\n\nPress INFO and then the GREEN button to update.") % ( + new_version, new_changelog) + self.session.open( + MessageBox, + msg, + MessageBox.TYPE_INFO, + timeout=10 + ) 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() + print("No new version available.") def refreshPlugins(self): plugins.clearPluginList() @@ -1094,12 +1271,6 @@ def closeRecursive(self): def closeNonRecursive(self): self.session.openWithCallback(self.close, AboutLSS) - def createSummary(self): - return - - def key_info(self): - self.session.open(LSinfo, " Information ") - def okbuttonClick(self): self.idx = self.index if self.idx is None: @@ -1146,43 +1317,15 @@ def okbuttonContinue(self, result): else: title = self.titles[self.idx] - self.data = checkGZIP(xmlurl) - if self.data is not None: - n1 = self.data.find(title, 0) - n2 = self.data.find("", n1) - url = self.data[n1:n2] - self.session.open(addInstall, url, name, None) - else: - self.session.open( - MessageBox, _("Error: No Data Find."), - MessageBox.TYPE_ERROR - ) + self._openCategory(title, name) -class LSskin(Screen): +class LSskin(LPGridScreen): def __init__(self, session, name): - Screen.__init__(self, session) - - try: - Screen.setTitle(self, _("%s") % descplug + " V." + __version__) - except BaseException: - try: - self.setTitle(_("%s") % descplug + " V." + __version__) - except BaseException: - pass - skin = join(skin_path, "LinuxsatPanel.xml") - with codecs.open(skin, "r", encoding="utf-8") as f: - self.skin = f.read() - - self.data = checkGZIP(xmlurl) - # self.data = fetch_url(xmlurl) - if isWQHD(): - self.pos = get_positions("WQHD") - elif isFHD(): - self.pos = get_positions("FHD") - elif isHD(): - self.pos = get_positions("HD") + LPGridScreen.__init__(self, session) + self.data = None + self._startAsync(get_catalog, self._catalogReady) self.name = name menu_list = [] @@ -1262,215 +1405,7 @@ def __init__(self, session, name): "Skins Oe Based ", "oebased.png") - self.names = menu_list - self.sorted = False - # self.combined_data = 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.npics = len(self.names) - # self.npage = int(float(self.npics // self.PIXMAPS_PER_PAGE)) + 1 - self.npage = int(round(self.npics // self.PIXMAPS_PER_PAGE)) + 1 - self.index = 0 - self.maxentry = len(menu_list) - 1 - self.ipage = 1 - self.onLayoutFinish.append(self.openTest) - - 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 = (self.PIXMAPS_PER_PAGE * self.ipage) - 1 - self.minentry = (self.ipage - 1) * self.PIXMAPS_PER_PAGE - - elif self.ipage == self.npage: - self.maxentry = len(self.pics) - 1 - self.minentry = (self.ipage - 1) * self.PIXMAPS_PER_PAGE - i1 = 0 - while i1 < self.PIXMAPS_PER_PAGE: - self["label" + str(i1 + 1)].setText(" ") - self["pixmap" + str(i1 + 1) - ].instance.setPixmapFromFile(nss_pic) - i1 += 1 - self.npics = len(self.pics) - i = 0 - i1 = 0 - self.picnum = 0 - ln = self.maxentry - (self.minentry - 1) - while i < ln: - idx = self.minentry + i - # self["label" + str(i + 1)].setText(self.names[idx]) # this show - # label to bottom of png pixmap - pic = self.pics[idx] - if not exists(self.pics[idx]): - pic = nss_pic - self["pixmap" + str(i + 1)].instance.setPixmapFromFile(pic) - i += 1 - self.index = self.minentry - self.paintFrame() - - def key_left(self): - # Decrement the index only if we are not at the first pixmap - if self.index >= 0: - self.index -= 1 - else: - # If we are at the first pixmap, go back to the last pixmap of the - # last page - self.ipage = self.npage - self.index = self.npics - 1 - # Check if we need to change pages - if self.index < self.minentry: - self.ipage -= 1 - if self.ipage < 1: # If we go beyond the first page - self.ipage = self.npage - self.index = self.npics - 1 # Back to the last pixmap of the last page - self.openTest() - else: - self.paintFrame() - - def key_right(self): - # Increment the index only if we are not at the last pixmap - if self.index < self.npics - 1: - self.index += 1 - else: - # If we are at the last pixmap, go back to the first pixmap of the - # first page - self.index = 0 - self.ipage = 1 - self.openTest() - # Check if we need to change pages - if self.index > self.maxentry: - self.ipage += 1 - if self.ipage > self.npage: # If we exceed the number of pages - self.index = 0 - self.ipage = 1 # Back to first page - self.openTest() - else: - self.paintFrame() - - def key_up(self): - if self.index == 0 and self.ipage == 1: - self.ipage = self.npage - self.index = self.minentry - self.openTest() - - elif self.index >= 5 and not self.ipage == self.npage and self.index == self.minentry: - self.index -= 5 - else: - if self.ipage == self.npage and self.index == self.minentry: - self.ipage = 1 - self.index = 0 - self.openTest() - else: - self.ipage = self.npage - self.index = self.npics - 1 - self.openTest() - self.paintFrame() - - def key_down(self): - if self.index <= self.maxentry - 5: - self.index += 5 - else: - if self.ipage == self.npage: - self.ipage = 1 - self.index = 0 - self.openTest() - else: - self.ipage += 1 - self.index = self.minentry - self.openTest() - - self.paintFrame() - - def keyNumberGlobal(self, number): - number -= 1 - if len(self["menu"].list) > number: - self["menu"].setIndex(number) - self.okbuttonClick() - - 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 ") + self.initGrid(menu_list) def okbuttonClick(self): self.idx = self.index @@ -1478,41 +1413,13 @@ def okbuttonClick(self): return name = self.names[self.idx] title = self.titles[self.idx] - self.data = checkGZIP(xmlurl) - if self.data is not None: - n1 = self.data.find(title, 0) - n2 = self.data.find("", n1) - url = self.data[n1:n2] - self.session.open(addInstall, url, name, None) - else: - self.session.open( - MessageBox, _("Error: No Data Find."), - MessageBox.TYPE_ERROR - ) + self._openCategory(title, name) -class LSChannel(Screen): +class LSChannel(LPGridScreen): def __init__(self, session, name): - Screen.__init__(self, session) - - try: - Screen.setTitle(self, _("%s") % descplug + " V." + __version__) - except BaseException: - try: - self.setTitle(_("%s") % descplug + " V." + __version__) - except BaseException: - pass - 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(): - self.pos = get_positions("FHD") - elif isHD(): - self.pos = get_positions("HD") + LPGridScreen.__init__(self, session) self.name = name menu_list = [] @@ -1522,262 +1429,55 @@ def __init__(self, session, name): # menu_list, titles, pics, urls, title, pic_name, url add_menu_item( - menu_list, - self.titles, - self.pics, - self.urls, - "CIEFP ", - "ciefp.png", - "https://github.com/ciefp/ciefpsettings-enigma2-zipped") - add_menu_item( - menu_list, - self.titles, - self.pics, - self.urls, - "CYRUS ", - "cyrus.png", - "https://www.cyrussettings.com/Set_29_11_2011/Dreambox-IpBox/Config.xml") - add_menu_item( - menu_list, - self.titles, - self.pics, - self.urls, - "MANUTEK ", - "manutek.png", - "https://www.manutek.it/isetting/index.php") - add_menu_item( - menu_list, - self.titles, - self.pics, - self.urls, - "MORPHEUS ", - "morpheus883.png", - "https://github.com/morpheus883/enigma2-zipped") - add_menu_item( - menu_list, - self.titles, - self.pics, - self.urls, - "VHANNIBAL NET ", - "vhannibal1.png", - "https://www.vhannibal.net/asd.php") - add_menu_item( - menu_list, - self.titles, - self.pics, - self.urls, - "VHANNIBAL TEK ", - "vhannibal2.png", - "https://sat.alfa-tech.net/upload/settings/vhannibal/") - - self.names = menu_list - # self.combined_data = 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.npics = len(self.names) - # self.npage = int(float(self.npics // self.PIXMAPS_PER_PAGE)) + 1 - self.npage = int(round(self.npics // self.PIXMAPS_PER_PAGE)) + 1 - self.index = 0 - self.maxentry = len(menu_list) - 1 - self.ipage = 1 - self.onLayoutFinish.append(self.openTest) - - 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 = (self.PIXMAPS_PER_PAGE * self.ipage) - 1 - self.minentry = (self.ipage - 1) * self.PIXMAPS_PER_PAGE - - elif self.ipage == self.npage: - self.maxentry = len(self.pics) - 1 - self.minentry = (self.ipage - 1) * self.PIXMAPS_PER_PAGE - i1 = 0 - while i1 < self.PIXMAPS_PER_PAGE: - self["label" + str(i1 + 1)].setText(" ") - self["pixmap" + str(i1 + 1) - ].instance.setPixmapFromFile(nss_pic) - i1 += 1 - self.npics = len(self.pics) - i = 0 - i1 = 0 - self.picnum = 0 - ln = self.maxentry - (self.minentry - 1) - while i < ln: - idx = self.minentry + i - # self["label" + str(i + 1)].setText(self.names[idx]) # this show - # label to bottom of png pixmap - pic = self.pics[idx] - if not exists(self.pics[idx]): - pic = nss_pic - self["pixmap" + str(i + 1)].instance.setPixmapFromFile(pic) - i += 1 - self.index = self.minentry - self.paintFrame() - - def key_left(self): - # Decrement the index only if we are not at the first pixmap - if self.index >= 0: - self.index -= 1 - else: - # If we are at the first pixmap, go back to the last pixmap of the - # last page - self.ipage = self.npage - self.index = self.npics - 1 - # Check if we need to change pages - if self.index < self.minentry: - self.ipage -= 1 - if self.ipage < 1: # If we go beyond the first page - self.ipage = self.npage - self.index = self.npics - 1 # Back to the last pixmap of the last page - self.openTest() - else: - self.paintFrame() - - def key_right(self): - # Increment the index only if we are not at the last pixmap - if self.index < self.npics - 1: - self.index += 1 - else: - # If we are at the last pixmap, go back to the first pixmap of the - # first page - self.index = 0 - self.ipage = 1 - self.openTest() - # Check if we need to change pages - if self.index > self.maxentry: - self.ipage += 1 - if self.ipage > self.npage: # If we exceed the number of pages - self.index = 0 - self.ipage = 1 # Back to first page - self.openTest() - else: - self.paintFrame() - - def key_up(self): - if self.index == 0 and self.ipage == 1: - self.ipage = self.npage - self.index = self.minentry - self.openTest() - - elif self.index >= 5 and not self.ipage == self.npage and self.index == self.minentry: - self.index -= 5 - else: - if self.ipage == self.npage and self.index == self.minentry: - self.ipage = 1 - self.index = 0 - self.openTest() - else: - self.ipage = self.npage - self.index = self.npics - 1 - self.openTest() - self.paintFrame() - - def key_down(self): - if self.index <= self.maxentry - 5: - self.index += 5 - else: - if self.ipage == self.npage: - self.ipage = 1 - self.index = 0 - self.openTest() - else: - self.ipage += 1 - self.index = self.minentry - self.openTest() - - self.paintFrame() - - def keyNumberGlobal(self, number): - number -= 1 - if len(self["menu"].list) > number: - self["menu"].setIndex(number) - self.okbuttonClick() - - 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 + menu_list, + self.titles, + self.pics, + self.urls, + "CIEFP ", + "ciefp.png", + "https://github.com/ciefp/ciefpsettings-enigma2-zipped") + add_menu_item( + menu_list, + self.titles, + self.pics, + self.urls, + "CYRUS ", + "cyrus.png", + "https://www.cyrussettings.com/Set_29_11_2011/Dreambox-IpBox/Config.xml") + add_menu_item( + menu_list, + self.titles, + self.pics, + self.urls, + "MANUTEK ", + "manutek.png", + "https://www.manutek.it/isetting/index.php") + add_menu_item( + menu_list, + self.titles, + self.pics, + self.urls, + "MORPHEUS ", + "morpheus883.png", + "https://github.com/morpheus883/enigma2-zipped") + add_menu_item( + menu_list, + self.titles, + self.pics, + self.urls, + "VHANNIBAL NET ", + "vhannibal1.png", + "https://www.vhannibal.net/asd.php") + add_menu_item( + menu_list, + self.titles, + self.pics, + self.urls, + "VHANNIBAL TEK ", + "vhannibal2.png", + "https://sat.alfa-tech.net/upload/settings/vhannibal/") - def key_info(self): - self.session.open(LSinfo, " Information ") + self.initGrid(menu_list) def okbuttonClick(self): self.idx = self.index @@ -1788,28 +1488,10 @@ def okbuttonClick(self): self.session.open(addInstall, url, name, "") -class LulullaScript(Screen): +class LulullaScript(LPGridScreen): def __init__(self, session, name): - Screen.__init__(self, session) - - try: - Screen.setTitle(self, _("%s") % descplug + " V." + __version__) - except BaseException: - try: - self.setTitle(_("%s") % descplug + " V." + __version__) - except BaseException: - pass - 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(): - self.pos = get_positions("FHD") - elif isHD(): - self.pos = get_positions("HD") + LPGridScreen.__init__(self, session) self.name = name menu_list = [] @@ -2090,268 +1772,60 @@ def __init__(self, session, name): menu_list, self.titles, self.pics, - self.urls, - "TvSettings", - "tvsettings.png", - "wget -q --no-check-certificate https://raw.githubusercontent.com/Belfagor2005/tvSettings/main/installer.sh -O - | /bin/sh") - add_menu_item( - menu_list, - self.titles, - self.pics, - self.urls, - "TvToM3u", - "tvtom3u.png", - "wget -q --no-check-certificate https://raw.githubusercontent.com/Belfagor2005/TvToM3u/main/installer.sh -O - | /bin/sh") - add_menu_item( - menu_list, - self.titles, - self.pics, - self.urls, - "Vavoo", - "vavoo.png", - "wget -q --no-check-certificate https://raw.githubusercontent.com/Belfagor2005/vavoo/main/installer.sh -O - | /bin/sh") - add_menu_item( - menu_list, - self.titles, - self.pics, - self.urls, - "WiFi Manager", - "wifi_manager.png", - "wget -q --no-check-certificate https://raw.githubusercontent.com/Belfagor2005/WiFi-Manager/main/installer.sh -O - | /bin/sh") - add_menu_item( - menu_list, - self.titles, - self.pics, - self.urls, - "Worldcam", - "worldcam.png", - "wget -q --no-check-certificate https://raw.githubusercontent.com/Belfagor2005/WorldCam/main/installer.sh -O - | /bin/sh") - add_menu_item( - menu_list, - self.titles, - self.pics, - self.urls, - "XC Forever", - "xc.png", - "wget -q --no-check-certificate \"https://raw.githubusercontent.com/Belfagor2005/xc_plugin_forever/main/installer.sh?inline=false\" -O - | /bin/sh") - add_menu_item( - menu_list, - self.titles, - self.pics, - self.urls, - "XXX Plugin", - "xxx_plugin.png", - "wget -q --no-check-certificate https://raw.githubusercontent.com/Belfagor2005/xxxplugin/main/installer.sh -O - | /bin/sh") - - self.names = menu_list - self.sorted = False - # self.combined_data = 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.npics = len(self.names) - # self.npage = int(float(self.npics // self.PIXMAPS_PER_PAGE)) + 1 - self.npage = int(round(self.npics // self.PIXMAPS_PER_PAGE)) + 1 - self.index = 0 - self.maxentry = len(menu_list) - 1 - self.ipage = 1 - self.onLayoutFinish.append(self.openTest) - - 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 = (self.PIXMAPS_PER_PAGE * self.ipage) - 1 - self.minentry = (self.ipage - 1) * self.PIXMAPS_PER_PAGE - - elif self.ipage == self.npage: - self.maxentry = len(self.pics) - 1 - self.minentry = (self.ipage - 1) * self.PIXMAPS_PER_PAGE - i1 = 0 - while i1 < self.PIXMAPS_PER_PAGE: - self["label" + str(i1 + 1)].setText(" ") - self["pixmap" + str(i1 + 1) - ].instance.setPixmapFromFile(nss_pic) - i1 += 1 - self.npics = len(self.pics) - i = 0 - i1 = 0 - self.picnum = 0 - ln = self.maxentry - (self.minentry - 1) - while i < ln: - idx = self.minentry + i - # self["label" + str(i + 1)].setText(self.names[idx]) # this show - # label to bottom of png pixmap - pic = self.pics[idx] - if not exists(self.pics[idx]): - pic = nss_pic - self["pixmap" + str(i + 1)].instance.setPixmapFromFile(pic) - i += 1 - self.index = self.minentry - self.paintFrame() - - def key_left(self): - # Decrement the index only if we are not at the first pixmap - if self.index >= 0: - self.index -= 1 - else: - # If we are at the first pixmap, go back to the last pixmap of the - # last page - self.ipage = self.npage - self.index = self.npics - 1 - # Check if we need to change pages - if self.index < self.minentry: - self.ipage -= 1 - if self.ipage < 1: # If we go beyond the first page - self.ipage = self.npage - self.index = self.npics - 1 # Back to the last pixmap of the last page - self.openTest() - else: - self.paintFrame() - - def key_right(self): - # Increment the index only if we are not at the last pixmap - if self.index < self.npics - 1: - self.index += 1 - else: - # If we are at the last pixmap, go back to the first pixmap of the - # first page - self.index = 0 - self.ipage = 1 - self.openTest() - # Check if we need to change pages - if self.index > self.maxentry: - self.ipage += 1 - if self.ipage > self.npage: # If we exceed the number of pages - self.index = 0 - self.ipage = 1 # Back to first page - self.openTest() - else: - self.paintFrame() - - def key_up(self): - if self.index == 0 and self.ipage == 1: - self.ipage = self.npage - self.index = self.minentry - self.openTest() - - elif self.index >= 5 and not self.ipage == self.npage and self.index == self.minentry: - self.index -= 5 - else: - if self.ipage == self.npage and self.index == self.minentry: - self.ipage = 1 - self.index = 0 - self.openTest() - else: - self.ipage = self.npage - self.index = self.npics - 1 - self.openTest() - self.paintFrame() - - def key_down(self): - if self.index <= self.maxentry - 5: - self.index += 5 - else: - if self.ipage == self.npage: - self.ipage = 1 - self.index = 0 - self.openTest() - else: - self.ipage += 1 - self.index = self.minentry - self.openTest() - - self.paintFrame() - - def keyNumberGlobal(self, number): - number -= 1 - if len(self["menu"].list) > number: - self["menu"].setIndex(number) - self.okbuttonClick() - - 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 + self.urls, + "TvSettings", + "tvsettings.png", + "wget -q --no-check-certificate https://raw.githubusercontent.com/Belfagor2005/tvSettings/main/installer.sh -O - | /bin/sh") + add_menu_item( + menu_list, + self.titles, + self.pics, + self.urls, + "TvToM3u", + "tvtom3u.png", + "wget -q --no-check-certificate https://raw.githubusercontent.com/Belfagor2005/TvToM3u/main/installer.sh -O - | /bin/sh") + add_menu_item( + menu_list, + self.titles, + self.pics, + self.urls, + "Vavoo", + "vavoo.png", + "wget -q --no-check-certificate https://raw.githubusercontent.com/Belfagor2005/vavoo/main/installer.sh -O - | /bin/sh") + add_menu_item( + menu_list, + self.titles, + self.pics, + self.urls, + "WiFi Manager", + "wifi_manager.png", + "wget -q --no-check-certificate https://raw.githubusercontent.com/Belfagor2005/WiFi-Manager/main/installer.sh -O - | /bin/sh") + add_menu_item( + menu_list, + self.titles, + self.pics, + self.urls, + "Worldcam", + "worldcam.png", + "wget -q --no-check-certificate https://raw.githubusercontent.com/Belfagor2005/WorldCam/main/installer.sh -O - | /bin/sh") + add_menu_item( + menu_list, + self.titles, + self.pics, + self.urls, + "XC Forever", + "xc.png", + "wget -q --no-check-certificate \"https://raw.githubusercontent.com/Belfagor2005/xc_plugin_forever/main/installer.sh?inline=false\" -O - | /bin/sh") + add_menu_item( + menu_list, + self.titles, + self.pics, + self.urls, + "XXX Plugin", + "xxx_plugin.png", + "wget -q --no-check-certificate https://raw.githubusercontent.com/Belfagor2005/xxxplugin/main/installer.sh -O - | /bin/sh") - def key_info(self): - self.session.open(LSinfo, " Information ") + self.initGrid(menu_list) def okbuttonClick(self): idx = self.index @@ -2405,42 +1879,11 @@ def console_closed(*args, **kwargs): else: return - def _view_log(self, answer): - if answer: - # Timer needed to open File_Commander from a safe context - 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) - timer = eTimer() - timer.callback.append(open_fc) - timer.start(0, True) - -class CiefpInstaller(Screen): +class CiefpInstaller(LPGridScreen): def __init__(self, session, name): - Screen.__init__(self, session) - - try: - Screen.setTitle(self, _("%s") % descplug + " V." + __version__) - except BaseException: - try: - self.setTitle(_("%s") % descplug + " V." + __version__) - except BaseException: - pass - 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(): - self.pos = get_positions("FHD") - elif isHD(): - self.pos = get_positions("HD") + LPGridScreen.__init__(self, session) self.name = name menu_list = [] self.titles = [] @@ -2609,254 +2052,46 @@ def __init__(self, session, name): "wget -q --no-check-certificate https://raw.githubusercontent.com/ciefp/CiefpTMDBSearch/main/installer.sh -O - | /bin/sh") add_menu_item( menu_list, - self.titles, - self.pics, - self.urls, - "CiefpVibes", - "ciefp_vibes.png", - "wget -q --no-check-certificate https://raw.githubusercontent.com/ciefp/CiefpVibes/main/installer.sh -O - | /bin/sh") - add_menu_item( - menu_list, - self.titles, - self.pics, - self.urls, - "CiefpWhitelistStreamrelay", - "ciefp_wls.png", - "wget -q --no-check-certificate https://raw.githubusercontent.com/ciefp/CiefpWhitelistStreamrelay/main/installer.sh -O - | /bin/sh") - add_menu_item( - menu_list, - self.titles, - self.pics, - self.urls, - "CiefpsettingsMotor", - "ciefp_sm.png", - "wget -q --no-check-certificate https://raw.githubusercontent.com/ciefp/CiefpsettingsMotor/main/installer.sh -O - | /bin/sh") - add_menu_item( - menu_list, - self.titles, - self.pics, - self.urls, - "CiefpsettingsPanel", - "ciefp_sp.png", - "wget -q --no-check-certificate https://raw.githubusercontent.com/ciefp/CiefpsettingsPanel/main/installer.sh -O - | /bin/sh") - add_menu_item( - menu_list, - self.titles, - self.pics, - self.urls, - "WebCamE2PrenjSF", - "ciefp_webcam.png", - "wget -q --no-check-certificate https://raw.githubusercontent.com/ciefp/WebCamE2PrenjSF/main/installer.sh -O - | /bin/sh") - - self.names = menu_list - self.sorted = False - # self.combined_data = 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.npics = len(self.names) - # self.npage = int(float(self.npics // self.PIXMAPS_PER_PAGE)) + 1 - self.npage = int(round(self.npics // self.PIXMAPS_PER_PAGE)) + 1 - self.index = 0 - self.maxentry = len(menu_list) - 1 - self.ipage = 1 - self.onLayoutFinish.append(self.openTest) - - 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 = (self.PIXMAPS_PER_PAGE * self.ipage) - 1 - self.minentry = (self.ipage - 1) * self.PIXMAPS_PER_PAGE - - elif self.ipage == self.npage: - self.maxentry = len(self.pics) - 1 - self.minentry = (self.ipage - 1) * self.PIXMAPS_PER_PAGE - i1 = 0 - while i1 < self.PIXMAPS_PER_PAGE: - self["label" + str(i1 + 1)].setText(" ") - self["pixmap" + str(i1 + 1) - ].instance.setPixmapFromFile(nss_pic) - i1 += 1 - self.npics = len(self.pics) - i = 0 - i1 = 0 - self.picnum = 0 - ln = self.maxentry - (self.minentry - 1) - while i < ln: - idx = self.minentry + i - # self["label" + str(i + 1)].setText(self.names[idx]) # this show - # label to bottom of png pixmap - pic = self.pics[idx] - if not exists(self.pics[idx]): - pic = nss_pic - self["pixmap" + str(i + 1)].instance.setPixmapFromFile(pic) - i += 1 - self.index = self.minentry - self.paintFrame() - - def key_left(self): - # Decrement the index only if we are not at the first pixmap - if self.index >= 0: - self.index -= 1 - else: - # If we are at the first pixmap, go back to the last pixmap of the - # last page - self.ipage = self.npage - self.index = self.npics - 1 - # Check if we need to change pages - if self.index < self.minentry: - self.ipage -= 1 - if self.ipage < 1: # If we go beyond the first page - self.ipage = self.npage - self.index = self.npics - 1 # Back to the last pixmap of the last page - self.openTest() - else: - self.paintFrame() - - def key_right(self): - # Increment the index only if we are not at the last pixmap - if self.index < self.npics - 1: - self.index += 1 - else: - # If we are at the last pixmap, go back to the first pixmap of the - # first page - self.index = 0 - self.ipage = 1 - self.openTest() - # Check if we need to change pages - if self.index > self.maxentry: - self.ipage += 1 - if self.ipage > self.npage: # If we exceed the number of pages - self.index = 0 - self.ipage = 1 # Back to first page - self.openTest() - else: - self.paintFrame() - - def key_up(self): - if self.index == 0 and self.ipage == 1: - self.ipage = self.npage - self.index = self.minentry - self.openTest() - - elif self.index >= 5 and not self.ipage == self.npage and self.index == self.minentry: - self.index -= 5 - else: - if self.ipage == self.npage and self.index == self.minentry: - self.ipage = 1 - self.index = 0 - self.openTest() - else: - self.ipage = self.npage - self.index = self.npics - 1 - self.openTest() - self.paintFrame() - - def key_down(self): - if self.index <= self.maxentry - 5: - self.index += 5 - else: - if self.ipage == self.npage: - self.ipage = 1 - self.index = 0 - self.openTest() - else: - self.ipage += 1 - self.index = self.minentry - self.openTest() - - self.paintFrame() - - def keyNumberGlobal(self, number): - number -= 1 - if len(self["menu"].list) > number: - self["menu"].setIndex(number) - self.okbuttonClick() - - 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 + self.titles, + self.pics, + self.urls, + "CiefpVibes", + "ciefp_vibes.png", + "wget -q --no-check-certificate https://raw.githubusercontent.com/ciefp/CiefpVibes/main/installer.sh -O - | /bin/sh") + add_menu_item( + menu_list, + self.titles, + self.pics, + self.urls, + "CiefpWhitelistStreamrelay", + "ciefp_wls.png", + "wget -q --no-check-certificate https://raw.githubusercontent.com/ciefp/CiefpWhitelistStreamrelay/main/installer.sh -O - | /bin/sh") + add_menu_item( + menu_list, + self.titles, + self.pics, + self.urls, + "CiefpsettingsMotor", + "ciefp_sm.png", + "wget -q --no-check-certificate https://raw.githubusercontent.com/ciefp/CiefpsettingsMotor/main/installer.sh -O - | /bin/sh") + add_menu_item( + menu_list, + self.titles, + self.pics, + self.urls, + "CiefpsettingsPanel", + "ciefp_sp.png", + "wget -q --no-check-certificate https://raw.githubusercontent.com/ciefp/CiefpsettingsPanel/main/installer.sh -O - | /bin/sh") + add_menu_item( + menu_list, + self.titles, + self.pics, + self.urls, + "WebCamE2PrenjSF", + "ciefp_webcam.png", + "wget -q --no-check-certificate https://raw.githubusercontent.com/ciefp/WebCamE2PrenjSF/main/installer.sh -O - | /bin/sh") - def key_info(self): - self.session.open(LSinfo, " Information ") + self.initGrid(menu_list) def okbuttonClick(self): idx = self.index @@ -2921,41 +2156,11 @@ def console_closed(*args, **kwargs): else: return - 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) - timer = eTimer() - timer.callback.append(open_fc) - timer.start(0, True) - -class ScriptInstaller(Screen): +class ScriptInstaller(LPGridScreen): def __init__(self, session, name): - Screen.__init__(self, session) - - try: - Screen.setTitle(self, _("%s") % descplug + " V." + __version__) - except BaseException: - try: - self.setTitle(_("%s") % descplug + " V." + __version__) - except BaseException: - pass - 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(): - self.pos = get_positions("FHD") - elif isHD(): - self.pos = get_positions("HD") + LPGridScreen.__init__(self, session) self.name = name menu_list = [] @@ -3282,56 +2487,7 @@ def __init__(self, session, name): "serviceapp.png", 'opkg update && opkg --force-reinstall --force-overwrite install ffmpeg gstplayer exteplayer3 enigma2-plugin-systemplugins-serviceapp') - self.names = menu_list - self.sorted = False - # self.combined_data = 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.npics = len(self.names) - # self.npage = int(float(self.npics // self.PIXMAPS_PER_PAGE)) + 1 - self.npage = int(round(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.initGrid(menu_list) def Lcn(self, answer=None): if answer is None: @@ -3340,7 +2496,7 @@ def Lcn(self, answer=None): MessageBox, _("Do you want to Order LCN Bouquet?"), MessageBox.TYPE_YESNO) - else: + elif answer: print("Starting LCN scan...") try: from .LCNScanner.Terrestrial import PluginSetup @@ -3351,11 +2507,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() @@ -3389,15 +2545,9 @@ def Checkskin(self, answer=None): MessageBox, _("[Checkskin] This operation checks if the skin has its components (is not sure)..\nDo you really want to continue?"), MessageBox.TYPE_YESNO) - else: + elif answer: from .addons import checkskin - check = checkskin.check_module_skin() - self.timer = eTimer() - try: - self.timer_conn = self.timer.timeout.connect(check) - except BaseException: - self.timer.callback.append(check) - self.timer.start(100, True) + checkskin.check_module_skin() self.session.openWithCallback( self._view_log, MessageBox, @@ -3405,165 +2555,6 @@ def Checkskin(self, answer=None): MessageBox.TYPE_YESNO ) - 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 = (self.PIXMAPS_PER_PAGE * self.ipage) - 1 - self.minentry = (self.ipage - 1) * self.PIXMAPS_PER_PAGE - - elif self.ipage == self.npage: - self.maxentry = len(self.pics) - 1 - self.minentry = (self.ipage - 1) * self.PIXMAPS_PER_PAGE - i1 = 0 - while i1 < self.PIXMAPS_PER_PAGE: - self["label" + str(i1 + 1)].setText(" ") - self["pixmap" + str(i1 + 1) - ].instance.setPixmapFromFile(nss_pic) - i1 += 1 - self.npics = len(self.pics) - i = 0 - i1 = 0 - self.picnum = 0 - ln = self.maxentry - (self.minentry - 1) - while i < ln: - idx = self.minentry + i - # self["label" + str(i + 1)].setText(self.names[idx]) # this show - # label to bottom of png pixmap - pic = self.pics[idx] - if not exists(self.pics[idx]): - pic = nss_pic - self["pixmap" + str(i + 1)].instance.setPixmapFromFile(pic) - i += 1 - self.index = self.minentry - self.paintFrame() - - def key_left(self): - # Decrement the index only if we are not at the first pixmap - if self.index >= 0: - self.index -= 1 - else: - # If we are at the first pixmap, go back to the last pixmap of the - # last page - self.ipage = self.npage - self.index = self.npics - 1 - # Check if we need to change pages - if self.index < self.minentry: - self.ipage -= 1 - if self.ipage < 1: # If we go beyond the first page - self.ipage = self.npage - self.index = self.npics - 1 # Back to the last pixmap of the last page - self.openTest() - else: - self.paintFrame() - - def key_right(self): - # Increment the index only if we are not at the last pixmap - if self.index < self.npics - 1: - self.index += 1 - else: - # If we are at the last pixmap, go back to the first pixmap of the - # first page - self.index = 0 - self.ipage = 1 - self.openTest() - # Check if we need to change pages - if self.index > self.maxentry: - self.ipage += 1 - if self.ipage > self.npage: # If we exceed the number of pages - self.index = 0 - self.ipage = 1 # Back to first page - self.openTest() - else: - self.paintFrame() - - def key_up(self): - if self.index == 0 and self.ipage == 1: - self.ipage = self.npage - self.index = self.minentry - self.openTest() - - elif self.index >= 5 and not self.ipage == self.npage and self.index == self.minentry: - self.index -= 5 - else: - if self.ipage == self.npage and self.index == self.minentry: - self.ipage = 1 - self.index = 0 - self.openTest() - else: - self.ipage = self.npage - self.index = self.npics - 1 - self.openTest() - self.paintFrame() - - def key_down(self): - if self.index <= self.maxentry - 5: - self.index += 5 - else: - if self.ipage == self.npage: - self.ipage = 1 - self.index = 0 - self.openTest() - else: - self.ipage += 1 - self.index = self.minentry - self.openTest() - - self.paintFrame() - - def keyNumberGlobal(self, number): - number -= 1 - if len(self["menu"].list) > number: - self["menu"].setIndex(number) - self.okbuttonClick() - - 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 okbuttonClick(self): idx = self.index print("[okbuttonClick] idx", idx) @@ -3644,18 +2635,6 @@ def console_closed(*args, **kwargs): else: return - 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) - timer = eTimer() - timer.callback.append(open_fc) - timer.start(0, True) - def askForFcl(self): self.session.openWithCallback( self.runScriptWithConsole, @@ -3668,7 +2647,7 @@ def runScriptWithConsole(self, confirmed): script_path = "/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/sh/Fcl.sh" url = "https://raw.githubusercontent.com/Belfagor2005/LinuxsatPanel/refs/heads/main/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/sh/Fcl.sh" try: - response = requests.get(url) + response = requests.get(url, timeout=15) response.raise_for_status() with io.open(script_path, "w", encoding="utf-8") as file: file.write(response.text) @@ -3739,6 +2718,8 @@ def getcl(self, config_type): dat = RequestUrl() print("Request Server url is:", dat) data = make_request(dat) + if not data: + raise ValueError("No data received from server") if PY3: data = six.ensure_str(data) @@ -3770,7 +2751,7 @@ def getcl(self, config_type): regex_patterns = [ r'">C:\s+([\w.-]+)\s+(\d+)\s+(\w+)\s+([\w.-]+)\s*', r'c:\s+([\w.-]+)\s+(\d+)\s+(\w+)\s+([\w.-]+)\s*\s*C:\s+([\w.-]+)\s+(\d+)\s+(\w+)\s+([\w.-]+)\s*' + r'cline">\s*C:\s+([\w.-]+)\s+(\d+)\s+(\w+)\s+([\w.-]+)\s*', 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 @@ + + \ No newline at end of file diff --git a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/skins/hd/LinuxsatPanel.xml b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/skins/hd/LinuxsatPanel.xml index 9696f54..9d328c9 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/skins/hd/LinuxsatPanel.xml +++ b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/skins/hd/LinuxsatPanel.xml @@ -59,5 +59,7 @@ + + \ No newline at end of file diff --git a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/skins/wqhd/LinuxsatPanel.xml b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/skins/wqhd/LinuxsatPanel.xml index 9df678a..20185d1 100644 --- a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/skins/wqhd/LinuxsatPanel.xml +++ b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/skins/wqhd/LinuxsatPanel.xml @@ -59,5 +59,7 @@ + + \ No newline at end of file diff --git a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/translate_utils.py b/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/translate_utils.py deleted file mode 100644 index e88fd0f..0000000 --- a/usr/lib/enigma2/python/Plugins/Extensions/LinuxsatPanel/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()}")