From 6c9a62f6b806ccdb3ccd7f26e6b884755a6ce1f4 Mon Sep 17 00:00:00 2001 From: oop7 <110548351+oop7@users.noreply.github.com> Date: Sat, 24 Jan 2026 16:58:17 +0200 Subject: [PATCH] Improve update check with parallel network requests Refactored UpdateCheckThread to use ThreadPoolExecutor for parallel requests to PyPI and GitHub, reducing total wait time. Added error handling and timeouts for faster failure detection, and improved changelog fetching with a fallback message. --- src/gui/ytsage_gui_main.py | 86 ++++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 23 deletions(-) diff --git a/src/gui/ytsage_gui_main.py b/src/gui/ytsage_gui_main.py index 3727b32..342c035 100644 --- a/src/gui/ytsage_gui_main.py +++ b/src/gui/ytsage_gui_main.py @@ -54,46 +54,86 @@ from src.utils.ytsage_localization import LocalizationManager, _ from src.utils.ytsage_history_manager import HistoryManager from src.gui.ytsage_stylesheet import StyleSheet +from concurrent.futures import ThreadPoolExecutor, as_completed + class UpdateCheckThread(QThread): + """Background thread for checking application updates with parallel network requests.""" + update_available = Signal(str, str, str) # version, url, changelog + # Reduced timeouts for faster failure detection + PYPI_TIMEOUT = 8 + GITHUB_TIMEOUT = 5 + def __init__(self, current_version): super().__init__() self.current_version = current_version - def run(self): + def _fetch_pypi_version(self) -> tuple[str | None, str | None]: + """Fetch latest version from PyPI. Returns (version, error).""" try: - # Get the latest version info from PyPI (no rate limiting unlike GitHub API) response = requests.get( "https://pypi.org/pypi/ytsage/json", - timeout=10, + timeout=self.PYPI_TIMEOUT, ) response.raise_for_status() - pypi_data = response.json() - latest_version = pypi_data["info"]["version"] + return pypi_data["info"]["version"], None + except requests.Timeout: + return None, "PyPI request timed out" + except requests.RequestException as e: + return None, f"PyPI request failed: {e}" + except Exception as e: + return None, f"Error parsing PyPI response: {e}" - # Compare versions - if version.parse(latest_version) > version.parse(self.current_version): - release_url = "https://github.com/oop7/YTSage/releases/latest" + def _fetch_github_changelog(self) -> str: + """Fetch changelog from GitHub. Returns changelog text or fallback message.""" + fallback = "View the full changelog on the [GitHub Releases](https://github.com/oop7/YTSage/releases) page." + try: + response = requests.get( + "https://api.github.com/repos/oop7/YTSage/releases/latest", + headers={"Accept": "application/vnd.github.v3+json"}, + timeout=self.GITHUB_TIMEOUT, + ) + if response.status_code == 200: + gh_data = response.json() + return gh_data.get("body", fallback) or fallback + return fallback + except Exception: + # Silently fallback if GitHub API fails (rate limiting, network issues, etc.) + return fallback + + def run(self): + """Check for updates using parallel network requests for better performance.""" + try: + # Use ThreadPoolExecutor to make both requests in parallel + # This reduces total wait time from potentially 15s to ~8s max + with ThreadPoolExecutor(max_workers=2) as executor: + # Submit both tasks + pypi_future = executor.submit(self._fetch_pypi_version) + github_future = executor.submit(self._fetch_github_changelog) + + # Get PyPI result (this is required) + latest_version, error = pypi_future.result() - # Try to fetch changelog from GitHub (with fallback if rate-limited) - changelog = "View the full changelog on the [GitHub Releases](https://github.com/oop7/YTSage/releases) page." - try: - gh_response = requests.get( - "https://api.github.com/repos/oop7/YTSage/releases/latest", - headers={"Accept": "application/vnd.github.v3+json"}, - timeout=5, - ) - if gh_response.status_code == 200: - gh_data = gh_response.json() - changelog = gh_data.get("body", changelog) - except Exception: - # Silently fallback to static message if GitHub API fails - pass + if error: + logger.debug(f"Update check failed: {error}") + return - self.update_available.emit(latest_version, release_url, changelog) + if not latest_version: + logger.debug("No version returned from PyPI") + return + + # Compare versions + if version.parse(latest_version) > version.parse(self.current_version): + release_url = "https://github.com/oop7/YTSage/releases/latest" + + # Get GitHub changelog (may already be complete due to parallel execution) + changelog = github_future.result() + + self.update_available.emit(latest_version, release_url, changelog) + except Exception as e: logger.debug(f"Failed to check for updates: {e}")