Marshal channel-switch UI updates back to the GUI thread
The yt-dlp stable/nightly channel switcher mutated QLabel/QRadioButton state directly from a raw threading.Thread, which is undefined behavior in Qt. The worker now only runs the subprocess and emits a signal; the connected slot applies all widget updates on the GUI thread via Qt's queued delivery. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -200,18 +200,23 @@ class DenoUpdateThread(QThread):
|
|||||||
|
|
||||||
class UpdaterTabWidget(QWidget):
|
class UpdaterTabWidget(QWidget):
|
||||||
"""Widget for the Updater tab in Custom Options dialog."""
|
"""Widget for the Updater tab in Custom Options dialog."""
|
||||||
|
|
||||||
|
# Emitted from the channel-switch worker thread; queued back to the GUI
|
||||||
|
# thread so widget updates never happen off-thread.
|
||||||
|
_channel_switch_finished = Signal(bool, str, str, str) # success, new_channel, current_channel, error
|
||||||
|
|
||||||
def __init__(self, parent=None) -> None:
|
def __init__(self, parent=None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._parent: "CustomOptionsDialog" = cast("CustomOptionsDialog", self.parent())
|
self._parent: "CustomOptionsDialog" = cast("CustomOptionsDialog", self.parent())
|
||||||
|
|
||||||
# State variables
|
# State variables
|
||||||
self.current_version = "Unknown"
|
self.current_version = "Unknown"
|
||||||
self.latest_version = "Unknown"
|
self.latest_version = "Unknown"
|
||||||
self.update_available = False
|
self.update_available = False
|
||||||
|
|
||||||
self._init_ui()
|
self._init_ui()
|
||||||
self._load_auto_update_settings()
|
self._load_auto_update_settings()
|
||||||
|
self._channel_switch_finished.connect(self._on_channel_switch_finished)
|
||||||
|
|
||||||
def _init_ui(self) -> None:
|
def _init_ui(self) -> None:
|
||||||
"""Initialize the UI components."""
|
"""Initialize the UI components."""
|
||||||
@@ -815,69 +820,55 @@ class UpdaterTabWidget(QWidget):
|
|||||||
# Success - save the preference
|
# Success - save the preference
|
||||||
ConfigManager.set("ytdlp_channel", new_channel)
|
ConfigManager.set("ytdlp_channel", new_channel)
|
||||||
logger.info(f"Successfully switched to {new_channel} channel")
|
logger.info(f"Successfully switched to {new_channel} channel")
|
||||||
|
|
||||||
# Update UI
|
|
||||||
self.channel_status_label.setText(_("settings.ytdlp_channel_switched", channel=new_channel))
|
|
||||||
self.channel_status_label.setStyleSheet(
|
|
||||||
"color: #00cc00; font-size: 11px; padding: 5px; "
|
|
||||||
"background-color: #2a2d36; border-radius: 4px; margin: 5px 0;"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Make executable on Unix systems
|
# Make executable on Unix systems
|
||||||
if OS_NAME != "Windows":
|
if OS_NAME != "Windows":
|
||||||
import os
|
import os
|
||||||
os.chmod(yt_dlp_path, 0o755)
|
os.chmod(yt_dlp_path, 0o755)
|
||||||
|
|
||||||
|
self._channel_switch_finished.emit(True, new_channel, current_channel, "")
|
||||||
else:
|
else:
|
||||||
# Failed - revert radio button
|
|
||||||
error_msg = result.stderr.strip() if result.stderr else result.stdout.strip() if result.stdout else "Unknown error"
|
error_msg = result.stderr.strip() if result.stderr else result.stdout.strip() if result.stdout else "Unknown error"
|
||||||
logger.error(f"Failed to switch channel: {error_msg}")
|
logger.error(f"Failed to switch channel: {error_msg}")
|
||||||
|
self._channel_switch_finished.emit(False, new_channel, current_channel, error_msg)
|
||||||
self.channel_status_label.setText(_("settings.ytdlp_channel_switch_failed", error=error_msg))
|
|
||||||
self.channel_status_label.setStyleSheet(
|
|
||||||
"color: #ff6666; font-size: 11px; padding: 5px; "
|
|
||||||
"background-color: #2a2d36; border-radius: 4px; margin: 5px 0;"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Revert radio selection
|
|
||||||
if current_channel == "nightly":
|
|
||||||
self.channel_nightly_radio.setChecked(True)
|
|
||||||
else:
|
|
||||||
self.channel_stable_radio.setChecked(True)
|
|
||||||
|
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
logger.error("Channel switch timed out")
|
logger.error("Channel switch timed out")
|
||||||
self.channel_status_label.setText(_("settings.ytdlp_channel_switch_failed", error="Timeout"))
|
self._channel_switch_finished.emit(False, new_channel, current_channel, "Timeout")
|
||||||
self.channel_status_label.setStyleSheet(
|
|
||||||
"color: #ff6666; font-size: 11px; padding: 5px; "
|
|
||||||
"background-color: #2a2d36; border-radius: 4px; margin: 5px 0;"
|
|
||||||
)
|
|
||||||
# Revert radio selection
|
|
||||||
if current_channel == "nightly":
|
|
||||||
self.channel_nightly_radio.setChecked(True)
|
|
||||||
else:
|
|
||||||
self.channel_stable_radio.setChecked(True)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Error switching channel: {e}")
|
logger.exception(f"Error switching channel: {e}")
|
||||||
self.channel_status_label.setText(_("settings.ytdlp_channel_switch_failed", error=str(e)))
|
self._channel_switch_finished.emit(False, new_channel, current_channel, str(e))
|
||||||
self.channel_status_label.setStyleSheet(
|
|
||||||
"color: #ff6666; font-size: 11px; padding: 5px; "
|
|
||||||
"background-color: #2a2d36; border-radius: 4px; margin: 5px 0;"
|
|
||||||
)
|
|
||||||
# Revert radio selection
|
|
||||||
if current_channel == "nightly":
|
|
||||||
self.channel_nightly_radio.setChecked(True)
|
|
||||||
else:
|
|
||||||
self.channel_stable_radio.setChecked(True)
|
|
||||||
finally:
|
|
||||||
# Re-enable radio buttons
|
|
||||||
self.channel_stable_radio.setEnabled(True)
|
|
||||||
self.channel_nightly_radio.setEnabled(True)
|
|
||||||
|
|
||||||
# Start the thread
|
# Start the thread
|
||||||
thread = threading.Thread(target=switch_channel, daemon=True)
|
thread = threading.Thread(target=switch_channel, daemon=True)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
|
@Slot(bool, str, str, str)
|
||||||
|
def _on_channel_switch_finished(self, success: bool, new_channel: str, current_channel: str, error_msg: str) -> None:
|
||||||
|
"""Apply the channel-switch outcome to the UI (always on the GUI thread)."""
|
||||||
|
if success:
|
||||||
|
self.channel_status_label.setText(_("settings.ytdlp_channel_switched", channel=new_channel))
|
||||||
|
self.channel_status_label.setStyleSheet(
|
||||||
|
"color: #00cc00; font-size: 11px; padding: 5px; "
|
||||||
|
"background-color: #2a2d36; border-radius: 4px; margin: 5px 0;"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.channel_status_label.setText(_("settings.ytdlp_channel_switch_failed", error=error_msg))
|
||||||
|
self.channel_status_label.setStyleSheet(
|
||||||
|
"color: #ff6666; font-size: 11px; padding: 5px; "
|
||||||
|
"background-color: #2a2d36; border-radius: 4px; margin: 5px 0;"
|
||||||
|
)
|
||||||
|
# Revert radio selection
|
||||||
|
if current_channel == "nightly":
|
||||||
|
self.channel_nightly_radio.setChecked(True)
|
||||||
|
else:
|
||||||
|
self.channel_stable_radio.setChecked(True)
|
||||||
|
|
||||||
|
# Re-enable radio buttons
|
||||||
|
self.channel_stable_radio.setEnabled(True)
|
||||||
|
self.channel_nightly_radio.setEnabled(True)
|
||||||
|
|
||||||
|
|
||||||
def _update_channel_status(self, channel: str) -> None:
|
def _update_channel_status(self, channel: str) -> None:
|
||||||
"""Update the channel status label."""
|
"""Update the channel status label."""
|
||||||
self.channel_status_label.setText(_("settings.ytdlp_current_channel", channel=channel))
|
self.channel_status_label.setText(_("settings.ytdlp_current_channel", channel=channel))
|
||||||
|
|||||||
Reference in New Issue
Block a user