Add opt-in beta update UI and checks
Introduce an opt-in beta updates feature: add a styled "Receive Beta Updates" checkbox in the Updater UI with i18n strings, load/save logic in the custom options dialog, and a default config key (check_beta_updates=false). Add _fetch_github_beta_version to the update thread to query GitHub releases (selecting the highest tag) and emit update_available for newer beta builds. When beta checking is enabled, the thread will perform the GitHub beta check and return early to avoid conflicting with the PyPI check. This enables users to opt into preview releases safely.
This commit is contained in:
@@ -966,6 +966,12 @@ class CustomOptionsDialog(QDialog):
|
|||||||
logger.info(f"Saving auto-update settings: enabled={enabled}, frequency={frequency}")
|
logger.info(f"Saving auto-update settings: enabled={enabled}, frequency={frequency}")
|
||||||
result = update_auto_update_settings(enabled, frequency)
|
result = update_auto_update_settings(enabled, frequency)
|
||||||
logger.info(f"Auto-update settings save result: {result}")
|
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:
|
except Exception as e:
|
||||||
logger.exception(f"Error saving auto-update settings: {e}")
|
logger.exception(f"Error saving auto-update settings: {e}")
|
||||||
|
|
||||||
|
|||||||
@@ -441,6 +441,40 @@ class UpdaterTabWidget(QWidget):
|
|||||||
deno_layout.addLayout(deno_button_layout)
|
deno_layout.addLayout(deno_button_layout)
|
||||||
|
|
||||||
layout.addWidget(deno_group)
|
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 ===
|
# === yt-dlp Release Channel Section ===
|
||||||
ytdlp_channel_group = QGroupBox(_("settings.ytdlp_channel"))
|
ytdlp_channel_group = QGroupBox(_("settings.ytdlp_channel"))
|
||||||
@@ -618,6 +652,10 @@ class UpdaterTabWidget(QWidget):
|
|||||||
# Set checkbox
|
# Set checkbox
|
||||||
self.auto_update_enabled.setChecked(auto_settings["enabled"])
|
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
|
# Set current selection based on saved settings
|
||||||
current_frequency = auto_settings["frequency"]
|
current_frequency = auto_settings["frequency"]
|
||||||
if current_frequency == "startup":
|
if current_frequency == "startup":
|
||||||
@@ -655,6 +693,10 @@ class UpdaterTabWidget(QWidget):
|
|||||||
frequency = "weekly"
|
frequency = "weekly"
|
||||||
|
|
||||||
return enabled, frequency
|
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:
|
def _on_channel_changed(self, checked: bool) -> None:
|
||||||
"""Handle channel selection change."""
|
"""Handle channel selection change."""
|
||||||
|
|||||||
@@ -116,9 +116,63 @@ class UpdateCheckThread(QThread):
|
|||||||
# Silently fallback if GitHub API fails (rate limiting, network issues, etc.)
|
# Silently fallback if GitHub API fails (rate limiting, network issues, etc.)
|
||||||
return fallback
|
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):
|
def run(self):
|
||||||
"""Check for updates using parallel network requests for better performance."""
|
"""Check for updates using parallel network requests for better performance."""
|
||||||
try:
|
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
|
# Use ThreadPoolExecutor to make both requests in parallel
|
||||||
# This reduces total wait time from potentially 15s to ~8s max
|
# This reduces total wait time from potentially 15s to ~8s max
|
||||||
with ThreadPoolExecutor(max_workers=2) as executor:
|
with ThreadPoolExecutor(max_workers=2) as executor:
|
||||||
|
|||||||
@@ -298,6 +298,8 @@
|
|||||||
"ytdlp_channel_switched": "✅ Successfully switched to {channel} channel!",
|
"ytdlp_channel_switched": "✅ Successfully switched to {channel} channel!",
|
||||||
"ytdlp_channel_switch_failed": "❌ Failed to switch channel: {error}",
|
"ytdlp_channel_switch_failed": "❌ Failed to switch channel: {error}",
|
||||||
"ytdlp_current_channel": "Current channel: {channel}",
|
"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_title": "Auto-Update Settings",
|
||||||
"auto_update_header": "🔄 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.",
|
"auto_update_description": "Configure automatic updates for yt-dlp to ensure you always have the latest features and bug fixes.",
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ class ConfigManager:
|
|||||||
"geo_proxy_url": None,
|
"geo_proxy_url": None,
|
||||||
"auto_update_ytdlp": True,
|
"auto_update_ytdlp": True,
|
||||||
"auto_update_frequency": "daily",
|
"auto_update_frequency": "daily",
|
||||||
|
"check_beta_updates": False,
|
||||||
"last_update_check": 0,
|
"last_update_check": 0,
|
||||||
"language": "en",
|
"language": "en",
|
||||||
"ytdlp_channel": "stable",
|
"ytdlp_channel": "stable",
|
||||||
|
|||||||
Reference in New Issue
Block a user