""" Settings-related dialogs for YTSage application. Contains dialogs for configuring download settings and auto-update preferences. """ import threading import time from datetime import datetime import requests from packaging import version as version_parser from PySide6.QtCore import Qt, QTimer from PySide6.QtWidgets import ( QButtonGroup, QCheckBox, QComboBox, QDialog, QDialogButtonBox, QFileDialog, QGroupBox, QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPushButton, QRadioButton, QVBoxLayout, ) from src.core.ytsage_utils import ( check_and_update_ytdlp_auto, get_auto_update_settings, get_ytdlp_version, update_auto_update_settings, ) from src.utils.ytsage_logger import logger class DownloadSettingsDialog(QDialog): def __init__(self, current_path, current_limit, current_unit_index, parent=None) -> None: super().__init__(parent) self.setWindowTitle("Download Settings") self.setMinimumWidth(450) self.setMinimumHeight(400) self.current_path = current_path self.current_limit = current_limit if current_limit is not None else "" self.current_unit_index = current_unit_index # Apply main app styling self.setStyleSheet( """ QDialog { background-color: #15181b; color: #ffffff; } QWidget { background-color: #15181b; color: #ffffff; } QLabel { color: #ffffff; } QGroupBox { color: #ffffff; border: 2px solid #1b2021; border-radius: 4px; margin-top: 10px; padding-top: 10px; font-weight: bold; background-color: #15181b; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px 0 5px; color: #ffffff; } QLineEdit { padding: 8px; border: 2px solid #1b2021; border-radius: 4px; background-color: #1b2021; color: #ffffff; selection-background-color: #c90000; selection-color: #ffffff; } QPushButton { padding: 8px 15px; background-color: #c90000; border: none; border-radius: 4px; color: white; font-weight: bold; min-height: 20px; } QPushButton:hover { background-color: #a50000; } QPushButton:pressed { background-color: #800000; } QPushButton:disabled { background-color: #666666; color: #999999; } QCheckBox { spacing: 5px; color: #ffffff; } QCheckBox::indicator { width: 18px; height: 18px; border-radius: 9px; } QCheckBox::indicator:unchecked { border: 2px solid #666666; background: #15181b; } QCheckBox::indicator:checked { border: 2px solid #c90000; background: #c90000; } QRadioButton { spacing: 5px; color: #ffffff; } QRadioButton::indicator { width: 18px; height: 18px; border-radius: 9px; } QRadioButton::indicator:unchecked { border: 2px solid #666666; background: #15181b; } QRadioButton::indicator:checked { border: 2px solid #c90000; background: #c90000; } QComboBox { padding: 5px; border: 2px solid #1b2021; border-radius: 4px; background-color: #1b2021; color: #ffffff; min-height: 20px; } QComboBox::drop-down { border: none; width: 20px; } QComboBox QAbstractItemView { border: 2px solid #1b2021; border-radius: 4px; background-color: #15181b; color: #ffffff; selection-background-color: #c90000; selection-color: #ffffff; } """ ) layout = QVBoxLayout(self) # --- Download Path Section --- path_group_box = QGroupBox("Download Path") path_layout = QVBoxLayout() self.path_display = QLabel(str(self.current_path)) self.path_display.setWordWrap(True) self.path_display.setStyleSheet( "QLabel { color: #ffffff; padding: 5px; border: 1px solid #1b2021; border-radius: 4px; background-color: #1b2021; }" ) path_layout.addWidget(self.path_display) browse_button = QPushButton("Browse...") browse_button.clicked.connect(self.browse_new_path) path_layout.addWidget(browse_button) path_group_box.setLayout(path_layout) layout.addWidget(path_group_box) # --- Speed Limit Section --- speed_group_box = QGroupBox("Speed Limit") speed_layout = QHBoxLayout() self.speed_limit_input = QLineEdit(str(self.current_limit)) self.speed_limit_input.setPlaceholderText("None") speed_layout.addWidget(self.speed_limit_input) self.speed_limit_unit = QComboBox() self.speed_limit_unit.addItems(["KB/s", "MB/s"]) self.speed_limit_unit.setCurrentIndex(self.current_unit_index) speed_layout.addWidget(self.speed_limit_unit) speed_group_box.setLayout(speed_layout) layout.addWidget(speed_group_box) # --- Auto-Update yt-dlp Section --- auto_update_group_box = QGroupBox("Auto-Update yt-dlp") auto_update_layout = QVBoxLayout() # Load current auto-update settings auto_settings = get_auto_update_settings() # Enable/Disable auto-update checkbox self.auto_update_enabled = QCheckBox("Enable automatic yt-dlp updates") self.auto_update_enabled.setChecked(auto_settings["enabled"]) auto_update_layout.addWidget(self.auto_update_enabled) # Frequency options frequency_label = QLabel("Update frequency:") frequency_label.setStyleSheet("color: #ffffff; margin-top: 10px;") auto_update_layout.addWidget(frequency_label) self.startup_radio = QRadioButton("Check on every startup (minimum 1 hour between checks)") self.daily_radio = QRadioButton("Check daily") self.weekly_radio = QRadioButton("Check weekly") # Set current selection based on saved settings current_frequency = auto_settings["frequency"] if current_frequency == "startup": self.startup_radio.setChecked(True) elif current_frequency == "daily": self.daily_radio.setChecked(True) else: # weekly self.weekly_radio.setChecked(True) auto_update_layout.addWidget(self.startup_radio) auto_update_layout.addWidget(self.daily_radio) auto_update_layout.addWidget(self.weekly_radio) # Test update button test_update_layout = QHBoxLayout() test_update_button = QPushButton("Check for Updates Now") test_update_button.clicked.connect(self.test_update_check) test_update_layout.addWidget(test_update_button) test_update_layout.addStretch() auto_update_layout.addLayout(test_update_layout) auto_update_group_box.setLayout(auto_update_layout) layout.addWidget(auto_update_group_box) # Dialog buttons (OK/Cancel) button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) layout.addWidget(button_box) def browse_new_path(self) -> None: new_path = QFileDialog.getExistingDirectory(self, "Select Download Directory", str(self.current_path)) if new_path: self.current_path = new_path self.path_display.setText(self.current_path) def get_selected_path(self) -> str: """Returns the confirmed path after the dialog is accepted.""" return self.current_path def get_selected_speed_limit(self) -> str | None: """Returns the entered speed limit value (as string or None).""" limit_str = self.speed_limit_input.text().strip() if not limit_str: return None try: float(limit_str) # Check if convertible to float return limit_str except ValueError: logger.info("Invalid speed limit input in dialog") return None def get_selected_unit_index(self) -> int: """Returns the index of the selected speed limit unit.""" return self.speed_limit_unit.currentIndex() def _create_styled_message_box(self, icon, title, text) -> QMessageBox: """Create a styled QMessageBox that matches the app theme.""" msg_box = QMessageBox(self) msg_box.setIcon(icon) msg_box.setWindowTitle(title) msg_box.setText(text) msg_box.setWindowIcon(self.windowIcon()) msg_box.setStyleSheet( """ QMessageBox { background-color: #15181b; color: #ffffff; } QMessageBox QLabel { color: #ffffff; } QMessageBox QPushButton { padding: 8px 15px; background-color: #c90000; border: none; border-radius: 4px; color: white; font-weight: bold; min-width: 80px; } QMessageBox QPushButton:hover { background-color: #a50000; } QMessageBox QPushButton:pressed { background-color: #800000; } """ ) return msg_box def test_update_check(self) -> None: """Test the update check functionality.""" try: # Get current version current_version = get_ytdlp_version() if "Error" in current_version: msg_box = self._create_styled_message_box( QMessageBox.Icon.Warning, "Update Check", "Could not determine current yt-dlp version.", ) msg_box.exec() return # Get latest version from PyPI response = requests.get("https://pypi.org/pypi/yt-dlp/json", timeout=10) response.raise_for_status() latest_version = response.json()["info"]["version"] # Clean up version strings current_version = current_version.replace("_", ".") latest_version = latest_version.replace("_", ".") if version_parser.parse(latest_version) > version_parser.parse(current_version): msg_box = self._create_styled_message_box( QMessageBox.Icon.Information, "Update Check", f"Update available!\n\nCurrent: {current_version}\nLatest: {latest_version}\n\nUse the 'Update yt-dlp' button in the main window to update.", ) msg_box.exec() else: msg_box = self._create_styled_message_box( QMessageBox.Icon.Information, "Update Check", f"yt-dlp is up to date!\n\nCurrent version: {current_version}", ) msg_box.exec() except Exception as e: msg_box = self._create_styled_message_box( QMessageBox.Icon.Warning, "Update Check", f"Error checking for updates: {e}", ) msg_box.exec() def get_auto_update_settings(self) -> tuple[bool, str]: """Returns the auto-update settings from the dialog.""" enabled = self.auto_update_enabled.isChecked() if self.startup_radio.isChecked(): frequency = "startup" elif self.daily_radio.isChecked(): frequency = "daily" else: # weekly_radio is checked frequency = "weekly" return enabled, frequency def accept(self) -> None: """Override accept to save auto-update settings.""" try: # Save auto-update settings enabled, frequency = self.get_auto_update_settings() if update_auto_update_settings(enabled, frequency): QMessageBox.information( self, "Settings Saved", "Auto-update settings have been saved successfully!", ) else: QMessageBox.warning(self, "Error", "Failed to save auto-update settings.") except Exception as e: QMessageBox.critical(self, "Error", f"Error saving auto-update settings: {e}") # Call the parent accept method to close the dialog super().accept() class AutoUpdateSettingsDialog(QDialog): def __init__(self, parent=None) -> None: super().__init__(parent) self.setWindowTitle("Auto-Update Settings") self.setMinimumWidth(400) self.setMinimumHeight(300) # Set the window icon to match the main app if parent: self.setWindowIcon(parent.windowIcon()) self.init_ui() self.load_current_settings() self.apply_styling() def init_ui(self) -> None: layout = QVBoxLayout(self) # Title title_label = QLabel("