diff --git a/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_custom.py b/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_custom.py index 5cd798c..e8f3d28 100644 --- a/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_custom.py +++ b/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_custom.py @@ -966,6 +966,12 @@ class CustomOptionsDialog(QDialog): logger.info(f"Saving auto-update settings: enabled={enabled}, frequency={frequency}") result = update_auto_update_settings(enabled, frequency) logger.info(f"Auto-update settings save result: {result}") + + # Save beta update setting + beta_enabled = self.updater_tab.get_beta_update_setting() + ConfigManager.set("check_beta_updates", beta_enabled) + logger.info(f"Saved beta updates setting: {beta_enabled}") + except Exception as e: logger.exception(f"Error saving auto-update settings: {e}") diff --git a/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_updater.py b/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_updater.py index 39a29c7..eca6651 100644 --- a/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_updater.py +++ b/ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_updater.py @@ -441,6 +441,40 @@ class UpdaterTabWidget(QWidget): deno_layout.addLayout(deno_button_layout) layout.addWidget(deno_group) + + # === App Updates Section === + app_update_group = QGroupBox(_("settings.app_updates_title")) + app_update_layout = QVBoxLayout() + + self.beta_updates_checkbox = QCheckBox(_("settings.check_beta_updates")) + self.beta_updates_checkbox.setStyleSheet( + """ + QCheckBox { + color: #ffffff; + spacing: 5px; + padding: 3px; + } + QCheckBox::indicator { + width: 18px; + height: 18px; + border-radius: 9px; + } + QCheckBox::indicator:unchecked { + border: 2px solid #666666; + background: #1d1e22; + border-radius: 9px; + } + QCheckBox::indicator:checked { + border: 2px solid #c90000; + background: #c90000; + border-radius: 9px; + } + """ + ) + app_update_layout.addWidget(self.beta_updates_checkbox) + + app_update_group.setLayout(app_update_layout) + layout.addWidget(app_update_group) # === yt-dlp Release Channel Section === ytdlp_channel_group = QGroupBox(_("settings.ytdlp_channel")) @@ -618,6 +652,10 @@ class UpdaterTabWidget(QWidget): # Set checkbox self.auto_update_enabled.setChecked(auto_settings["enabled"]) + # Load beta setting + beta_enabled = ConfigManager.get("check_beta_updates") or False + self.beta_updates_checkbox.setChecked(beta_enabled) + # Set current selection based on saved settings current_frequency = auto_settings["frequency"] if current_frequency == "startup": @@ -655,6 +693,10 @@ class UpdaterTabWidget(QWidget): frequency = "weekly" return enabled, frequency + + def get_beta_update_setting(self) -> bool: + """Returns the beta update setting from the dialog.""" + return self.beta_updates_checkbox.isChecked() def _on_channel_changed(self, checked: bool) -> None: """Handle channel selection change.""" diff --git a/ytsage/gui/ytsage_gui_main.py b/ytsage/gui/ytsage_gui_main.py index 3a55ec9..250338c 100644 --- a/ytsage/gui/ytsage_gui_main.py +++ b/ytsage/gui/ytsage_gui_main.py @@ -116,9 +116,63 @@ class UpdateCheckThread(QThread): # Silently fallback if GitHub API fails (rate limiting, network issues, etc.) return fallback + def _fetch_github_beta_version(self) -> tuple[str | None, str | None, str | None]: + """Fetch latest version code from GitHub releases (including betas). Returns (version, tag, changelog).""" + try: + response = requests.get( + "https://api.github.com/repos/oop7/YTSage/releases", + headers={"Accept": "application/vnd.github.v3+json"}, + timeout=self.GITHUB_TIMEOUT, + ) + if response.status_code != 200: + logger.debug(f"GitHub Releases API returned {response.status_code}") + return None, None, None + + releases = response.json() + if not releases: + return None, None, None + + latest_release = None + highest_ver = version.parse("0.0.0") + + for rel in releases: + tag = rel.get("tag_name", "") + ver_str = tag.lstrip("v") + try: + v = version.parse(ver_str) + if v > highest_ver: + highest_ver = v + latest_release = rel + except Exception: + continue + + if latest_release: + return str(highest_ver), latest_release.get("tag_name"), latest_release.get("body") + return None, None, None + + except Exception as e: + logger.debug(f"GitHub beta check error: {e}") + return None, None, None + def run(self): """Check for updates using parallel network requests for better performance.""" try: + # Check for beta updates if enabled + check_beta = ConfigManager.get("check_beta_updates") + + if check_beta: + latest_ver_str, tag, changelog = self._fetch_github_beta_version() + + if latest_ver_str and version.parse(latest_ver_str) > version.parse(self.current_version): + release_url = f"https://github.com/oop7/YTSage/releases/tag/{tag}" + if not changelog: + changelog = "View the full changelog on GitHub." + self.update_available.emit(latest_ver_str, release_url, changelog) + # Return if beta check completes (whether update found or not), + # effectively skipping PyPI check if beta is enabled. + # This ensures we don't downgrade or conflict. + return + # 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: diff --git a/ytsage/languages/en.json b/ytsage/languages/en.json index fe74363..f9c59a0 100644 --- a/ytsage/languages/en.json +++ b/ytsage/languages/en.json @@ -298,6 +298,8 @@ "ytdlp_channel_switched": "✅ Successfully switched to {channel} channel!", "ytdlp_channel_switch_failed": "❌ Failed to switch channel: {error}", "ytdlp_current_channel": "Current channel: {channel}", + "app_updates_title": "YTSage Updates", + "check_beta_updates": "Receive Beta Updates", "auto_update_title": "Auto-Update Settings", "auto_update_header": "🔄 Auto-Update Settings", "auto_update_description": "Configure automatic updates for yt-dlp to ensure you always have the latest features and bug fixes.", diff --git a/ytsage/utils/ytsage_config_manager.py b/ytsage/utils/ytsage_config_manager.py index 919d546..4c58d58 100644 --- a/ytsage/utils/ytsage_config_manager.py +++ b/ytsage/utils/ytsage_config_manager.py @@ -83,6 +83,7 @@ class ConfigManager: "geo_proxy_url": None, "auto_update_ytdlp": True, "auto_update_frequency": "daily", + "check_beta_updates": False, "last_update_check": 0, "language": "en", "ytdlp_channel": "stable",