From db2a2df1f8920b056f3109bda1a6de50697e9608 Mon Sep 17 00:00:00 2001 From: sebdelsol Date: Fri, 29 Mar 2024 02:36:51 +0100 Subject: [PATCH] v1.4.12.45 beta 1 --- README.md | 6 +- build/changelog.md | 5 ++ build_config.py | 2 +- dev/tools/nsis/template.nsi | 73 +++++++++--------- src/mitm/addon/__init__.py | 15 ++-- src/mitm/cache.py | 149 +++++++++++++++++++++--------------- src/mitm/proxies.py | 2 +- src/sfvip/cache.py | 64 +++++++++++++++- src/sfvip/proxies.py | 6 +- translations/bulgarian.json | 3 +- translations/english.json | 3 +- translations/french.json | 3 +- translations/german.json | 5 +- translations/greek.json | 3 +- translations/italian.json | 3 +- translations/loc/texts.py | 3 +- translations/polish.json | 3 +- translations/russian.json | 3 +- translations/serbian.json | 5 +- translations/slovenian.json | 3 +- translations/spanish.json | 3 +- translations/turkish.json | 3 +- 22 files changed, 222 insertions(+), 143 deletions(-) diff --git a/README.md b/README.md index c677e3cb..2f723e9c 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,13 @@ You'll find them in the app folder[^3]: [^3]:The app is installed by default in `%LocalAppData%\Programs\Sfvip All x64`, `%LocalAppData%\Programs\Sfvip All x86` or the installation directory you've specified during the installation. # Build -[![version](https://custom-icon-badges.demolab.com/badge/Build%201.4.12.44-informational?logo=github)](/build_config.py#L27) -[![Sloc](https://custom-icon-badges.demolab.com/badge/Sloc%208.4k-informational?logo=file-code)](https://api.codetabs.com/v1/loc/?github=sebdelsol/sfvip-all) +[![version](https://custom-icon-badges.demolab.com/badge/Build%201.4.12.45-informational?logo=github)](/build_config.py#L27) +[![Sloc](https://custom-icon-badges.demolab.com/badge/Sloc%208.5k-informational?logo=file-code)](https://api.codetabs.com/v1/loc/?github=sebdelsol/sfvip-all) [![Ruff](https://custom-icon-badges.demolab.com/badge/Ruff-informational?logo=ruff-color)](https://docs.astral.sh/ruff/) [![Python](https://custom-icon-badges.demolab.com/badge/Python%203.11.8-linen?logo=python-color)](https://www.python.org/downloads/release/python-3118/) [![mitmproxy](https://custom-icon-badges.demolab.com/badge/Mitmproxy%2010.2.4-linen?logo=mitmproxy-black)](https://mitmproxy.org/) [![Nsis](https://custom-icon-badges.demolab.com/badge/Nsis%203.09-linen?logo=nsis-color)](https://nsis.sourceforge.io/Download) -[![Nuitka](https://custom-icon-badges.demolab.com/badge/Nuitka%202.1.3-linen?logo=nuitka)](https://nuitka.net/) +[![Nuitka](https://custom-icon-badges.demolab.com/badge/Nuitka%202.1.4-linen?logo=nuitka)](https://nuitka.net/) [![PyInstaller](https://custom-icon-badges.demolab.com/badge/PyInstaller%206.5.0-linen?logo=pyinstaller-windowed)](https://pyinstaller.org/en/stable/) * [***NSIS***](https://nsis.sourceforge.io/Download) will be automatically installed if missing. diff --git a/build/changelog.md b/build/changelog.md index fffff308..72de0ccf 100644 --- a/build/changelog.md +++ b/build/changelog.md @@ -1,3 +1,8 @@ +## 1.4.12.45 +* Bump _Nuitka_ to 2.1.4. +* Fix MAC account update info. +* Fix MAC account progress not correctly hiding. + ## 1.4.12.44 * Fix UI minor bug when launching several _Sfvip All_. * Fix uninstall old version if the installation folder differs. diff --git a/build_config.py b/build_config.py index 6376b970..f66473f4 100644 --- a/build_config.py +++ b/build_config.py @@ -24,7 +24,7 @@ class Build: main: ClassVar = "sfvip_all.py" company: ClassVar = "sebdelsol" name: ClassVar = "Sfvip All" - version: ClassVar = "1.4.12.44" + version: ClassVar = "1.4.12.45" dir: ClassVar = "build" enable_console: ClassVar = False logs_dir: ClassVar = "../logs" diff --git a/dev/tools/nsis/template.nsi b/dev/tools/nsis/template.nsi index dc339ce1..0a866b2f 100644 --- a/dev/tools/nsis/template.nsi +++ b/dev/tools/nsis/template.nsi @@ -18,6 +18,7 @@ ShowUninstDetails hide !define MUI_ICON "{{dist}}\{{ico}}" !define MUI_UNICON "{{dist}}\{{ico}}" +!define UNINSTALL_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{name}} {{bitness}}" ; ------------- ; directory page @@ -25,6 +26,28 @@ ShowUninstDetails hide !define MUI_PAGE_CUSTOMFUNCTION_PRE "SetInstDir" !insertmacro MUI_PAGE_DIRECTORY +var OldInstDir + +; Set install directory if in the registry +Function SetInstDir + ; check in the registry for already installed version + ReadRegStr $0 HKCU "${UNINSTALL_KEY}" "InstallLocation" + ${If} ${Errors} + ClearErrors + ${Else} + StrCpy $InstDir $0 + ${Endif} + ; save $InstDir + StrCpy $OldInstDir $InstDir + ; no directory page if /AUTOINSTDIR=yes + ${GetParameters} $0 + ${GetOptions} $0 "/AUTOINSTDIR=" $1 + ClearErrors + ${If} $1 == "yes" + Abort + ${Endif} +FunctionEnd + ; ------------- ; install pages ; ------------- @@ -33,6 +56,18 @@ Page Custom old.AppRunningPage old.AppRunningPageFinalize Page Custom AppRunningPage AppRunningPageFinalize !insertmacro MUI_PAGE_INSTFILES +; Uninstall version stored in the registry if different from instdir +Function UninstallOldVersionIfNeeded + ReadRegStr $0 HKCU "${UNINSTALL_KEY}" "InstallLocation" + ${If} ${Errors} + ClearErrors + ${ElseIf} $InstDir != $0 + ${If} ${FileExists} "$0\uninstall.exe" + ExecWait "$0\uninstall.exe /S" + ${EndIf} + ${Endif} +FunctionEnd + ; ------------- ; finish pages ; ------------- @@ -108,7 +143,6 @@ FunctionEnd !define GetProcess "Get-Process '{{name}}' -ErrorAction SilentlyContinue" var NbAppRunning -var OldInstDir !macro GetNbAppRunning old ; MessageBox MB_OK "old=`${old}`" @@ -184,7 +218,6 @@ var NextButton ; ------------ ; Install Page ; ------------ -!define UNINSTALL_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{name}} {{bitness}}" Section "Install" SectionIn RO ; Read-only @@ -225,39 +258,3 @@ Section "Uninstall" Delete "$InstDir\uninstall.exe" Delete "$SMPROGRAMS\{{name}} {{bitness}}.lnk" SectionEnd - -; -------------- -; Set install directory if in the registry -; -------------- -Function SetInstDir - ; check in the registry for already installed version - ReadRegStr $0 HKCU "${UNINSTALL_KEY}" "InstallLocation" - ${If} ${Errors} - ClearErrors - ${Else} - StrCpy $InstDir $0 - ${Endif} - ; save $InstDir - StrCpy $OldInstDir $InstDir - ; no directory page if /AUTOINSTDIR=yes - ${GetParameters} $0 - ${GetOptions} $0 "/AUTOINSTDIR=" $1 - ClearErrors - ${If} $1 == "yes" - Abort - ${Endif} -FunctionEnd - -; -------------- -; Uninstall version stored in the registry if different from instdir -; -------------- -Function UninstallOldVersionIfNeeded - ReadRegStr $0 HKCU "${UNINSTALL_KEY}" "InstallLocation" - ${If} ${Errors} - ClearErrors - ${ElseIf} $InstDir != $0 - ${If} ${FileExists} "$0\uninstall.exe" - ExecWait "$0\uninstall.exe /S" - ${EndIf} - ${Endif} -FunctionEnd diff --git a/src/mitm/addon/__init__.py b/src/mitm/addon/__init__.py index 8795ddd5..580d46db 100644 --- a/src/mitm/addon/__init__.py +++ b/src/mitm/addon/__init__.py @@ -8,7 +8,7 @@ from mitmproxy import http from mitmproxy.proxy.server_hooks import ServerConnectionHookData -from ..cache import AllCached, MACCache, UpdateCacheProgressT +from ..cache import AllCached, MacCache, UpdateCacheProgressT from ..epg import EPG, EpgCallbacks from ..utils import APItype, get_query_key, response_json from .all import AllCategoryName, AllPanels @@ -156,11 +156,14 @@ def __init__( timeout: int, ) -> None: self.api_request = ApiRequest(accounts_urls) - self.mac_cache = MACCache(roaming, update_progress, all_config.all_cached) + self.mac_cache = MacCache(roaming, update_progress, all_config.all_cached) self.epg = EPG(roaming, epg_callbacks, timeout) self.m3u_stream = M3UStream(self.epg) self.panels = AllPanels(all_config.all_name) + def cache_stop_all(self) -> None: + self.mac_cache.stop_all() + def epg_update(self, url: str) -> None: self.epg.ask_update(url) @@ -171,10 +174,12 @@ def epg_prefer_update(self, prefer_internal: bool) -> None: self.epg.update_prefer(prefer_internal) def running(self) -> None: + self.mac_cache.start() self.epg.start() def done(self) -> None: self.epg.stop() + self.mac_cache.stop() def wait_running(self, timeout: int) -> bool: return self.epg.wait_running(timeout) @@ -185,8 +190,6 @@ async def request(self, flow: http.HTTPFlow) -> None: match api, get_query_key(flow, "action"): case APItype.MAC, "get_ordered_list": await self.mac_cache.load_response(flow) - case APItype.MAC, _: - self.mac_cache.stop(flow) case APItype.XC, action if action: self.panels.serve_all(flow, action) @@ -224,16 +227,14 @@ async def response(self, flow: http.HTTPFlow) -> None: set_epg_server(flow, self.epg, api) else: self.m3u_stream.start(flow) - self.mac_cache.stop_all() - # TODO progress for MAC Cache not hiding !! nee to call self.mac_cache.stop_all(), but where? async def error(self, flow: http.HTTPFlow) -> None: # logger.debug("ERROR %s", flow.request.pretty_url) if not self.m3u_stream.stop(flow): if api := await self.api_request(flow): match api, get_query_key(flow, "action"): case APItype.MAC, "get_ordered_list": - self.mac_cache.stop(flow) + self.mac_cache.done(flow) def server_disconnected(self, data: ServerConnectionHookData) -> None: # logger.debug("DISCONNECT %s %s", data.server.peername, data.server.transport_protocol) diff --git a/src/mitm/cache.py b/src/mitm/cache.py index 1fd3cab7..d7d61e2d 100644 --- a/src/mitm/cache.py +++ b/src/mitm/cache.py @@ -1,4 +1,5 @@ import logging +import multiprocessing import pickle import time from enum import Enum, auto @@ -7,6 +8,8 @@ from mitmproxy import http +from shared.job_runner import JobRunner + from ..winapi import mutex from .cache_cleaner import CacheCleaner from .utils import ProgressStep, content_json, get_int, get_query_key, json_encoder @@ -82,29 +85,29 @@ def sanitize_filename(filename: str) -> str: return filename -class MACCacheFile: +class MacCacheFile: def __init__(self, cache_dir: Path, query: MacQuery) -> None: self.query = query self.file_path = cache_dir / sanitize_filename(str(self.query)) self.mutex = mutex.SystemWideMutex(f"file lock for {self.file_path}") def open_and_do( - self, mode: Literal["rb", "wb"], do: Callable[[IO[bytes]], T], *exceptions: type[Exception] - ) -> Optional[T]: + self, mode: Literal["rb", "wb"], do: Callable[[IO[bytes]], None], *exceptions: type[Exception] + ) -> None: with self.mutex: try: with self.file_path.open(mode) as file: if file: - return do(file) + do(file) except (*exceptions, PermissionError, FileNotFoundError, OSError): pass - return None -class MacCacheLoad(MACCacheFile): +class MacCacheLoad(MacCacheFile): def __init__(self, cache_dir: Path, query: MacQuery) -> None: self.total: int = 0 self.actual: int = 0 + self.timestamp: float = 0 self.content: bytes = b"" super().__init__(cache_dir, query) self._load() @@ -117,11 +120,14 @@ def _load(file: IO[bytes]) -> None: and isinstance(total, int) and (actual := pickle.load(file)) and isinstance(actual, int) + and (timestamp := pickle.load(file)) + and isinstance(timestamp, float) and (content := pickle.load(file)) and isinstance(content, bytes) ): self.total = total self.actual = actual + self.timestamp = timestamp self.content = content logger.info("Load Cache from '%s' (%s out of %s)", file.name, self.actual, self.total) @@ -129,20 +135,21 @@ def _load(file: IO[bytes]) -> None: @property def valid(self) -> bool: - return bool(self.total and self.actual and self.content) + return bool(self.total and self.actual and self.timestamp and self.content) @property - def missing_percent(self) -> float: - return _in_beetween((self.total - self.actual) / self.total, 0, 100) if self.total else 1 + def complete(self) -> float: + return _in_beetween(self.actual / self.total, 0, 1) if self.total else 0 -class MacCacheSave(MACCacheFile): +class MacCacheSave(MacCacheFile): def __init__(self, cache_dir: Path, query: MacQuery, update_progress: UpdateCacheProgressT) -> None: self.total: int = 0 self.valid: bool = True self.contents: list[bytes] = [] self.max_pages: float = 0 - self.progress_step = ProgressStep(step=0.0005) + # update the progress as often as we can to avoid the progress watchdog timeout + self.progress_step = ProgressStep(step=0) self.update_progress = update_progress logger.info("Start creating Cache for %s.%s", query.server, query.type) super().__init__(cache_dir, query) @@ -165,10 +172,11 @@ async def update(self, response: http.Response, page: int) -> bool: self.max_pages = total / max_page_items self.progress_step.set_total(self.max_pages) self.update_progress(CacheProgress(CacheProgressEvent.START)) - else: - logger.warning("Wrong 1st page for %s cache", str(self.query)) - self.valid = False - return False + if not self.max_pages: + logger.warning("Missing 1st page for %s cache", str(self.query)) + self.valid = False + return False + # logger.info("Page %s - total %s", page, self.max_pages) self.contents.append(content) if progress := self.progress_step.progress(page): self.update_progress(CacheProgress(CacheProgressEvent.SHOW, progress)) @@ -187,33 +195,35 @@ def update_with_loaded(data_to_update: list[dict], loaded: MacCacheLoad) -> list return list(ids.values()) return data_to_update - def save(self, loaded: Optional[MacCacheLoad]) -> Optional[bool]: - def _save(file: IO[bytes]) -> bool: - if self.valid and self.total: - data_to_save: list[dict] = [] - for content in self.contents: - if (js := get_js(content, dict)) and (data := js.get("data")) and isinstance(data, list): - data_to_save.extend(data) - # update with loaded if not complete - if self.total != len(data_to_save) and loaded and loaded.valid: - data_to_save = self.update_with_loaded(data_to_save, loaded) - actual = len(data_to_save) - js = set_js(dict(max_page_items=actual, total_items=actual, data=data_to_save)) - content = json_encoder.encode(js) - pickle.dump(self.total, file) - pickle.dump(actual, file) - pickle.dump(content, file) - logger.info("Save Cache to '%s' (%s out of %s)", file.name, actual, self.total) - return True - return False + def save(self, loaded: Optional[MacCacheLoad]) -> None: + def _save(file: IO[bytes]) -> None: + data_to_save: list[dict] = [] + for content in self.contents: + if (js := get_js(content, dict)) and (data := js.get("data")) and isinstance(data, list): + data_to_save.extend(data) + # update with loaded if not complete + not_complete = len(data_to_save) < self.total + if not_complete and loaded and loaded.valid: + data_to_save = self.update_with_loaded(data_to_save, loaded) + timestamp = loaded.timestamp + else: + timestamp = time.time() + actual = len(data_to_save) + js = set_js(dict(max_page_items=actual, total_items=actual, data=data_to_save)) + content = json_encoder.encode(js) + pickle.dump(self.total, file) + pickle.dump(actual, file) + pickle.dump(timestamp, file) + pickle.dump(content, file) + logger.info("Save Cache to '%s' (%s out of %s)", file.name, actual, self.total) self.update_progress(CacheProgress(CacheProgressEvent.STOP)) - return self.open_and_do("wb", _save, pickle.PickleError, TypeError) + if self.valid and self.total: + self.open_and_do("wb", _save, pickle.PickleError, TypeError) class AllCached(NamedTuple): complete: str - missing: str today: str one_day: str several_days: str @@ -222,18 +232,17 @@ class AllCached(NamedTuple): all_updates: dict[ValidMediaTypes, str] = {} def title(self, loaded: MacCacheLoad) -> str: - if missing_percent := loaded.missing_percent: - percent = _in_beetween(round(missing_percent * 100), 1, 99) - missing_str = f"⚠️ {self.missing.format(percent=percent)}" + if loaded.complete < 1: + percent = _in_beetween(round(loaded.complete * 100), 1, 99) + missing_str = f"⚠️ {self.complete.format(percent=percent)}" else: - missing_str = f"✔ {self.complete}" + missing_str = f"✔ {self.complete.format(percent=100)}" return ( f"{self.all_names.get(loaded.query.type, '')} - {self.fast_cached.capitalize()}" - f"\n{self._days_ago(loaded.file_path)} {missing_str}" + f"\n{self._days_ago(loaded.timestamp)} {missing_str}" ) - def _days_ago(self, path: Path) -> str: - timestamp = path.stat().st_mtime + def _days_ago(self, timestamp: float) -> str: days = int((time.time() - timestamp) / (3600 * 24)) match days: case 0: @@ -244,7 +253,7 @@ def _days_ago(self, path: Path) -> str: return self.several_days.format(days=days) -class MACCache(CacheCleaner): +class MacCache(CacheCleaner): cached_all_category = "cached_all_category" cached_header = "ListCached" cached_header_bytes = cached_header.encode() @@ -253,7 +262,9 @@ class MACCache(CacheCleaner): all_category = "*" def __init__(self, roaming: Path, update_progress: UpdateCacheProgressT, all_cached: AllCached) -> None: - super().__init__(roaming, MACCache.clean_after_days, *MACCache.suffixes) + super().__init__(roaming, MacCache.clean_after_days, *MacCache.suffixes) + self._stop_all_job = JobRunner[bool](self._done_all, "Cache stop all job") + self.saved_queries_lock = multiprocessing.Lock() self.saved_queries: dict[MacQuery, MacCacheSave] = {} self.loaded_queries: dict[MacQuery, MacCacheLoad] = {} self.update_progress = update_progress @@ -262,31 +273,45 @@ def __init__(self, roaming: Path, update_progress: UpdateCacheProgressT, all_cac async def save_response(self, flow: http.HTTPFlow) -> None: if ( (response := flow.response) - and get_query_key(flow, "category") == MACCache.all_category + and get_query_key(flow, "category") == MacCache.all_category and (page := get_int(get_query_key(flow, "p"))) - and MACCache.cached_header_bytes not in response.headers + and MacCache.cached_header_bytes not in response.headers and (query := MacQuery.get_from(flow)) ): - if query not in self.saved_queries: - self.saved_queries[query] = MacCacheSave(self.cache_dir, query, self.update_progress) - if await self.saved_queries[query].update(flow.response, page): - self.save(query) + with self.saved_queries_lock: + if query not in self.saved_queries: + self.saved_queries[query] = MacCacheSave(self.cache_dir, query, self.update_progress) + if await self.saved_queries[query].update(response, page): + self._save(query) - def save(self, query: MacQuery) -> None: + def _save(self, query: MacQuery) -> None: self.saved_queries[query].save(self.loaded_queries.get(query)) del self.saved_queries[query] - def stop(self, flow: http.HTTPFlow) -> None: - if (query := MacQuery.get_from(flow)) in self.saved_queries: - self.save(query) + def done(self, flow: http.HTTPFlow) -> None: + with self.saved_queries_lock: + if (query := MacQuery.get_from(flow)) in self.saved_queries: + logger.info("Stop creating Cache for %s", str(query)) + self._save(query) + + def _done_all(self, _) -> None: + with self.saved_queries_lock: + logger.info("Stop creating all Caches") + for query in self.saved_queries.copy(): + self._save(query) def stop_all(self) -> None: - for query in self.saved_queries.copy(): - self.save(query) + self._stop_all_job.add_job(True) + + def start(self) -> None: + self._stop_all_job.start() + + def stop(self) -> None: + self._stop_all_job.stop() async def load_response(self, flow: http.HTTPFlow) -> None: if ( - get_query_key(flow, "category") == MACCache.cached_all_category + get_query_key(flow, "category") == MacCache.cached_all_category and (query := MacQuery.get_from(flow)) and (loaded := self.loaded_queries[query]) and (loaded.valid) @@ -295,7 +320,7 @@ async def load_response(self, flow: http.HTTPFlow) -> None: content=loaded.content, headers={ "Content-Type": "application/json", - MACCache.cached_header: "", + MacCache.cached_header: "", }, ) @@ -307,7 +332,7 @@ def inject_all_cached_category(self, flow: http.HTTPFlow) -> None: and (categories := get_reponse_js(response, list)) and (all_category := categories[0]) and isinstance(all_category, dict) - and (all_category.get("id") == MACCache.all_category) + and (all_category.get("id") == MacCache.all_category) ): # clean queries for other servers for existing_query in self.loaded_queries.copy(): @@ -318,8 +343,8 @@ def inject_all_cached_category(self, flow: http.HTTPFlow) -> None: if loaded.valid: cached_all_category = dict( censored=0, - alias=MACCache.all_category, - id=MACCache.cached_all_category, + alias=MacCache.all_category, + id=MacCache.cached_all_category, title=self.all_cached.title(loaded), ) categories.insert(1, cached_all_category) diff --git a/src/mitm/proxies.py b/src/mitm/proxies.py index a9ccaffe..bb352897 100644 --- a/src/mitm/proxies.py +++ b/src/mitm/proxies.py @@ -62,7 +62,7 @@ def __init__(self, addon: SfVipAddOn, modes: set[Mode]) -> None: super().__init__() def run(self) -> None: - socket.setdefaulttimeout(0) # TODO is it better ?? + socket.setdefaulttimeout(0) # faster proxy ?! with LogProcess(logger, "Mitmproxy"): if set_current_process_high_priority(): logger.info("Set process to high priority") diff --git a/src/sfvip/cache.py b/src/sfvip/cache.py index a8cce70a..f361ba06 100644 --- a/src/sfvip/cache.py +++ b/src/sfvip/cache.py @@ -1,12 +1,64 @@ +import logging +import queue +import threading +from enum import Enum, auto +from typing import Callable + from shared.job_runner import JobRunner from ..mitm.cache import CacheProgress, CacheProgressEvent, UpdateCacheProgressT from .ui import UI +logger = logging.getLogger(__name__) + + +class WatchdogState(Enum): + STOPPED = auto() + ARMED = auto() + DISARMED = auto() + + +class TimeoutWatchdog: + def __init__(self, timeout: float, callback: Callable[[], None]) -> None: + self._activity: queue.Queue[WatchdogState] = queue.Queue() + self._thread = threading.Thread(target=self._watch) + self._callback = callback + self._timeout = timeout + + def start(self) -> None: + self._thread.start() + + def stop(self) -> None: + self._activity.put(WatchdogState.STOPPED) + self._thread.join() + + def ping(self) -> None: + self._activity.put(WatchdogState.ARMED) + + def disarm(self) -> None: + self._activity.put(WatchdogState.DISARMED) + + def _watch(self) -> None: + state = WatchdogState.DISARMED + while True: + try: + timeout = None if state == WatchdogState.DISARMED else self._timeout + state = self._activity.get(timeout=timeout) + if state == WatchdogState.STOPPED: + break + except queue.Empty: # timeout + if state == WatchdogState.ARMED: + state = WatchdogState.DISARMED + self._callback() + class CacheProgressListener: - def __init__(self, ui: UI) -> None: + _timeout = 4 # seconds without progress + + def __init__(self, ui: UI, stop_all: Callable[[], None]) -> None: self._cache_progress_job_runner = JobRunner[CacheProgress](self._on_progress_changed, "Cache progress job") + self._watchdog = TimeoutWatchdog(self._timeout, self._no_progress_timeout) + self._stop_all = stop_all self._ui = ui @property @@ -14,17 +66,27 @@ def update_progress(self) -> UpdateCacheProgressT: return self._cache_progress_job_runner.add_job def start(self) -> None: + self._watchdog.start() self._cache_progress_job_runner.start() def stop(self) -> None: + self._watchdog.stop() self._cache_progress_job_runner.stop() self._ui.progress_bar.hide() + def _no_progress_timeout(self) -> None: + logger.info("Cache Progress timeout") + self._ui.progress_bar.hide() + self._stop_all() + def _on_progress_changed(self, cache_progress: CacheProgress) -> None: match cache_progress.event: case CacheProgressEvent.START: + # self._watchdog.ping() # TODO ??! self._ui.progress_bar.show() case CacheProgressEvent.SHOW: + self._watchdog.ping() self._ui.progress_bar.set_progress(cache_progress.progress) case CacheProgressEvent.STOP: + self._watchdog.disarm() self._ui.progress_bar.hide() diff --git a/src/sfvip/proxies.py b/src/sfvip/proxies.py index 17f4bc9f..67e2cd03 100644 --- a/src/sfvip/proxies.py +++ b/src/sfvip/proxies.py @@ -59,7 +59,6 @@ def get_all_config(player_capabilities: PlayerCapabilities) -> AddonAllConfig: vod=None if player_capabilities.has_all_categories else LOC.AllMovies, ), AllCached( - missing=LOC.Missing, complete=LOC.Complete, today=LOC.UpdatedToday, one_day=LOC.Updated1DayAgo, @@ -89,7 +88,7 @@ def __init__( ), ui, ) - self._cache_progress = CacheProgressListener(ui) + self._cache_progress = CacheProgressListener(ui, self.cache_stop_all) self._addon = SfVipAddOn( accounts_proxies.urls, get_all_config(player_capabilities), @@ -111,6 +110,9 @@ def __init__( def by_upstreams(self) -> dict[str, str]: return self._by_upstreams + def cache_stop_all(self) -> None: + self._addon.cache_stop_all() + def epg_update(self, url: str) -> None: self._addon.epg_update(url) diff --git a/translations/bulgarian.json b/translations/bulgarian.json index 1b18fa72..5e29fd4c 100644 --- a/translations/bulgarian.json +++ b/translations/bulgarian.json @@ -58,6 +58,5 @@ "LibmpvTip": "Libmpv декодира и визуализира аудио и видео. Активирайте актуализациите, за да получите последната версия, оптимизирана за вашия компютър.", "ProxyTip": "{name} използва локален прокси сървър, за да прихваща всички заявки към доставчика IPTV и да инжектира категориите 'all' и външния EPG", "UserProxyTip": "Действително потребителско прокси, ако съществува", - "Missing": "{percent}% липсва", - "Complete": "100% пълно" + "Complete": "{percent}% Завършен" } \ No newline at end of file diff --git a/translations/english.json b/translations/english.json index af08080c..345e9999 100644 --- a/translations/english.json +++ b/translations/english.json @@ -58,6 +58,5 @@ "LibmpvTip": "Libmpv decodes & renders audio and video. Enable the updates to get the last version optimized for your computer.", "ProxyTip": "{name} uses a local proxy to intercept all requests to the IPTV provider and inject the 'all' categories and the external EPG", "UserProxyTip": "Actual user proxy if it exists", - "Missing": "{percent}% missing", - "Complete": "100% Complete" + "Complete": "{percent}% Complete" } \ No newline at end of file diff --git a/translations/french.json b/translations/french.json index 28b99c77..033e3da8 100644 --- a/translations/french.json +++ b/translations/french.json @@ -58,6 +58,5 @@ "LibmpvTip": "Libmpv décode et rend l'audio et la vidéo. Activez les mises à jour pour obtenir la dernière version optimisée pour votre ordinateur.", "ProxyTip": "{name} utilise un proxy local pour intercepter toutes les demandes adressées au fournisseur IPTV et injecter les catégories 'all' et l'EPG externe.", "UserProxyTip": "Proxy utilisateur réel s'il existe", - "Missing": "{percent}% manquant", - "Complete": "100% Complet" + "Complete": "{percent}% Complet" } \ No newline at end of file diff --git a/translations/german.json b/translations/german.json index 48d71670..00432edc 100644 --- a/translations/german.json +++ b/translations/german.json @@ -57,7 +57,6 @@ "EPGUrlTip": "Geben Sie die URL des externen EPGs ein, sie sollte mit \"xml\" oder \"xml.gz\" enden.", "LibmpvTip": "Libmpv dekodiert und rendert Audio und Video. Aktivieren Sie die Updates, um die letzte für Ihren Computer optimierte Version zu erhalten.", "ProxyTip": "{name} verwendet einen lokalen Proxy, um alle Anfragen an den Anbieter IPTV abzufangen und die 'all'-Kategorien und den externen EPG zu injizieren", - "UserProxyTip": "Tatsächlicher Benutzer-Proxy, wenn er existiert", - "Missing": "{percent}% fehlt", - "Complete": "100% vollständig" + "UserProxyTip": "Aktueller Benutzer-Proxy, falls vorhanden", + "Complete": "{percent}% Vollständig" } \ No newline at end of file diff --git a/translations/greek.json b/translations/greek.json index 911f9d8a..1dcc5965 100644 --- a/translations/greek.json +++ b/translations/greek.json @@ -58,6 +58,5 @@ "LibmpvTip": "Το Libmpv αποκωδικοποιεί και αποδίδει ήχο και βίντεο. Ενεργοποιήστε τις ενημερώσεις για να λάβετε την τελευταία έκδοση βελτιστοποιημένη για τον υπολογιστή σας.", "ProxyTip": "Το {name} χρησιμοποιεί έναν τοπικό μεσάζοντα για να υποκλέψει όλες τις αιτήσεις προς τον πάροχο IPTV και να εισάγει τις κατηγορίες 'all' και το εξωτερικό EPG", "UserProxyTip": "Πραγματικός μεσάζων χρήστη, αν υπάρχει", - "Missing": "{percent}% λείπει", - "Complete": "100% Πλήρης" + "Complete": "{percent}% Πλήρης" } \ No newline at end of file diff --git a/translations/italian.json b/translations/italian.json index 1d23f566..4aa77328 100644 --- a/translations/italian.json +++ b/translations/italian.json @@ -58,6 +58,5 @@ "LibmpvTip": "Libmpv decodifica e rende audio e video. Attivare gli aggiornamenti per ottenere l'ultima versione ottimizzata per il proprio computer.", "ProxyTip": "{name} utilizza un proxy locale per intercettare tutte le richieste al provider IPTV e iniettare le categorie 'all' e l'EPG esterno.", "UserProxyTip": "Proxy utente effettivo, se esiste", - "Missing": "{percent}% mancante", - "Complete": "100% Completo" + "Complete": "{percent}% Completo" } \ No newline at end of file diff --git a/translations/loc/texts.py b/translations/loc/texts.py index eb32a2c6..02226bc5 100644 --- a/translations/loc/texts.py +++ b/translations/loc/texts.py @@ -80,8 +80,7 @@ class Texts: "and inject the 'all' categories and the external EPG" ) UserProxyTip: str = "Actual user proxy if it exists" - Missing: str = "{percent}% missing" - Complete: str = "100% Complete" + Complete: str = "{percent}% Complete" def as_dict(self) -> dict[str, str]: return dataclasses.asdict(self) diff --git a/translations/polish.json b/translations/polish.json index bf303dbc..a5daa524 100644 --- a/translations/polish.json +++ b/translations/polish.json @@ -58,6 +58,5 @@ "LibmpvTip": "Libmpv dekoduje i renderuje audio i wideo. Włącz aktualizacje, aby uzyskać ostatnią wersję zoptymalizowaną dla twojego komputera.", "ProxyTip": "{name} używa lokalnego proxy do przechwytywania wszystkich żądań do dostawcy IPTV i wstrzykiwania kategorii \"all\" i zewnętrznego EPG.", "UserProxyTip": "Rzeczywiste proxy użytkownika, jeśli istnieje", - "Missing": "{percent}% brak", - "Complete": "100% kompletny" + "Complete": "{percent}% Ukończone" } \ No newline at end of file diff --git a/translations/russian.json b/translations/russian.json index 9b0e9bbe..ab20e06b 100644 --- a/translations/russian.json +++ b/translations/russian.json @@ -58,6 +58,5 @@ "LibmpvTip": "Libmpv декодирует и рендерит аудио и видео. Включите обновления, чтобы получить последнюю версию, оптимизированную для вашего компьютера.", "ProxyTip": "{name} использует локальный прокси для перехвата всех запросов к провайдеру IPTV и введения категорий 'all' и внешнего EPG.", "UserProxyTip": "Фактический пользовательский прокси, если он существует", - "Missing": "{percent}% отсутствует", - "Complete": "100% завершено" + "Complete": "{percent}% Завершено" } \ No newline at end of file diff --git a/translations/serbian.json b/translations/serbian.json index d8c10eec..9e578c6c 100644 --- a/translations/serbian.json +++ b/translations/serbian.json @@ -53,11 +53,10 @@ "Yes": "да", "No": "Не", "EPGPreferYes": "Да: Прво претражите ЕПГ провајдера IPTV. Користите екстерни ЕПГ само када не успе.", - "EPGPreferNo": "Не: прво претражите екстерни ЕПГ. Користите ЕПГ провајдера IPTV само када не успе.", + "EPGPreferNo": "Не: прво претражите екстерни ЕПГ. Користите ЕПГ IPTV провајдера само када не успе.", "EPGUrlTip": "Унесите УРЛ спољног ЕПГ-а, требало би да се заврши са 'xml' или 'xml.gz'", "LibmpvTip": "Либмпв декодира и приказује аудио и видео. Омогућите ажурирања да бисте последњу верзију оптимизовали за ваш рачунар.", "ProxyTip": "{name} користи локални прокси да пресретне све захтеве добављачу IPTV и убаци 'све' категорије и екстерни ЕПГ", "UserProxyTip": "Стварни кориснички прокси ако постоји", - "Missing": "{percent}% недостаје", - "Complete": "100% завршено" + "Complete": "{percent}% завршено" } \ No newline at end of file diff --git a/translations/slovenian.json b/translations/slovenian.json index 0bc26a2b..f54307f5 100644 --- a/translations/slovenian.json +++ b/translations/slovenian.json @@ -58,6 +58,5 @@ "LibmpvTip": "Libmpv dekodira in prikazuje zvok in video. Omogočite posodobitve, da dobite zadnjo različico, optimizirano za vaš računalnik.", "ProxyTip": "{name} uporablja lokalni posrednik za prestrezanje vseh zahtev do ponudnika IPTV in vnašanje kategorij 'all' in zunanjega EPG", "UserProxyTip": "Dejanski uporabniški posrednik, če obstaja", - "Missing": "{percent}% manjka", - "Complete": "100 % Popolno" + "Complete": "{percent}% dokončano" } \ No newline at end of file diff --git a/translations/spanish.json b/translations/spanish.json index f148aba9..9c1585f6 100644 --- a/translations/spanish.json +++ b/translations/spanish.json @@ -58,6 +58,5 @@ "LibmpvTip": "Libmpv decodifica y renderiza audio y video. Activa las actualizaciones para obtener la última versión optimizada para tu ordenador.", "ProxyTip": "{name} utiliza un proxy local para interceptar todas las peticiones al proveedor IPTV e inyectar las categorías 'all' y la EPG externa", "UserProxyTip": "Proxy de usuario real si existe", - "Missing": "Falta {percent}", - "Complete": "100% Completo" + "Complete": "{percent}% Completo" } \ No newline at end of file diff --git a/translations/turkish.json b/translations/turkish.json index 0d4337e5..494e9613 100644 --- a/translations/turkish.json +++ b/translations/turkish.json @@ -58,6 +58,5 @@ "LibmpvTip": "Libmpv ses ve videonun kodunu çözer ve işler. Bilgisayarınız için optimize edilmiş son sürümü almak için güncellemeleri etkinleştirin.", "ProxyTip": "{name}, IPTV sağlayıcısına yapılan tüm istekleri engellemek ve 'tüm' kategorileri ve harici EPG'yi enjekte etmek için yerel bir proxy kullanır", "UserProxyTip": "Varsa gerçek kullanıcı proxy'si", - "Missing": "{percent}% kayıp", - "Complete": "100 Tamamlandı" + "Complete": "{percent}% Tamamlandı" } \ No newline at end of file