Refactor SettingsDialog for improved readability and maintainability
- Cleaned up whitespace and formatting throughout the settings_dialog.py file. - Enhanced type hints for better clarity and type checking. - Consolidated URL mapping handling in get_config_data method. - Improved error handling and logging for configuration operations. - Added comments for better understanding of the code structure and functionality.
This commit is contained in:
parent
03991fdea5
commit
986793632e
4 changed files with 468 additions and 480 deletions
|
|
@ -53,7 +53,9 @@ class ConfigValidator:
|
||||||
# Check type
|
# Check type
|
||||||
expected_type = rules.get("type")
|
expected_type = rules.get("type")
|
||||||
if expected_type and not isinstance(value, expected_type):
|
if expected_type and not isinstance(value, expected_type):
|
||||||
errors.append(f"{field}: expected {expected_type.__name__}, got {type(value).__name__}")
|
errors.append(
|
||||||
|
f"{field}: expected {expected_type.__name__}, got {type(value).__name__}"
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Check allowed values
|
# Check allowed values
|
||||||
|
|
@ -104,7 +106,7 @@ class ConfigProfile:
|
||||||
|
|
||||||
PROFILES_DIR = Path.home() / ".webdrop-bridge" / "profiles"
|
PROFILES_DIR = Path.home() / ".webdrop-bridge" / "profiles"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
"""Initialize profile manager."""
|
"""Initialize profile manager."""
|
||||||
self.PROFILES_DIR.mkdir(parents=True, exist_ok=True)
|
self.PROFILES_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -231,9 +231,6 @@ class UpdateManager:
|
||||||
except socket.timeout as e:
|
except socket.timeout as e:
|
||||||
logger.error(f"Socket timeout (5s) connecting to {self.api_endpoint}")
|
logger.error(f"Socket timeout (5s) connecting to {self.api_endpoint}")
|
||||||
return None
|
return None
|
||||||
except TimeoutError as e:
|
|
||||||
logger.error(f"Timeout error: {e}")
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to fetch release: {type(e).__name__}: {e}")
|
logger.error(f"Failed to fetch release: {type(e).__name__}: {e}")
|
||||||
import traceback
|
import traceback
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ from PySide6.QtWebEngineCore import QWebEngineDownloadRequest, QWebEngineScript
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QLabel,
|
QLabel,
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
|
QMessageBox,
|
||||||
QSizePolicy,
|
QSizePolicy,
|
||||||
QSpacerItem,
|
QSpacerItem,
|
||||||
QStatusBar,
|
QStatusBar,
|
||||||
|
|
@ -205,7 +206,7 @@ class _DragBridge(QObject):
|
||||||
Exposed to JavaScript as 'bridge' object.
|
Exposed to JavaScript as 'bridge' object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, window: 'MainWindow', parent: Optional[QObject] = None):
|
def __init__(self, window: "MainWindow", parent: Optional[QObject] = None):
|
||||||
"""Initialize the drag bridge.
|
"""Initialize the drag bridge.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -282,12 +283,14 @@ class MainWindow(QMainWindow):
|
||||||
|
|
||||||
# Set window icon
|
# Set window icon
|
||||||
# Support both development mode and PyInstaller bundle
|
# Support both development mode and PyInstaller bundle
|
||||||
if hasattr(sys, '_MEIPASS'):
|
if hasattr(sys, "_MEIPASS"):
|
||||||
# Running as PyInstaller bundle
|
# Running as PyInstaller bundle
|
||||||
icon_path = Path(sys._MEIPASS) / "resources" / "icons" / "app.ico" # type: ignore
|
icon_path = Path(sys._MEIPASS) / "resources" / "icons" / "app.ico" # type: ignore
|
||||||
else:
|
else:
|
||||||
# Running in development mode
|
# Running in development mode
|
||||||
icon_path = Path(__file__).parent.parent.parent.parent / "resources" / "icons" / "app.ico"
|
icon_path = (
|
||||||
|
Path(__file__).parent.parent.parent.parent / "resources" / "icons" / "app.ico"
|
||||||
|
)
|
||||||
|
|
||||||
if icon_path.exists():
|
if icon_path.exists():
|
||||||
self.setWindowIcon(QIcon(str(icon_path)))
|
self.setWindowIcon(QIcon(str(icon_path)))
|
||||||
|
|
@ -404,7 +407,7 @@ class MainWindow(QMainWindow):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Load local file
|
# Load local file
|
||||||
html_content = file_path.read_text(encoding='utf-8')
|
html_content = file_path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
# Inject WebChannel bridge JavaScript
|
# Inject WebChannel bridge JavaScript
|
||||||
injected_html = self._inject_drag_bridge(html_content)
|
injected_html = self._inject_drag_bridge(html_content)
|
||||||
|
|
@ -438,7 +441,7 @@ class MainWindow(QMainWindow):
|
||||||
qwebchannel_code = ""
|
qwebchannel_code = ""
|
||||||
qwebchannel_file = QFile(":/qtwebchannel/qwebchannel.js")
|
qwebchannel_file = QFile(":/qtwebchannel/qwebchannel.js")
|
||||||
if qwebchannel_file.open(QIODevice.OpenModeFlag.ReadOnly | QIODevice.OpenModeFlag.Text):
|
if qwebchannel_file.open(QIODevice.OpenModeFlag.ReadOnly | QIODevice.OpenModeFlag.Text):
|
||||||
qwebchannel_code = bytes(qwebchannel_file.readAll()).decode('utf-8') # type: ignore
|
qwebchannel_code = bytes(qwebchannel_file.readAll()).decode("utf-8") # type: ignore
|
||||||
qwebchannel_file.close()
|
qwebchannel_file.close()
|
||||||
logger.debug("Loaded qwebchannel.js inline to avoid CSP issues")
|
logger.debug("Loaded qwebchannel.js inline to avoid CSP issues")
|
||||||
else:
|
else:
|
||||||
|
|
@ -462,7 +465,7 @@ class MainWindow(QMainWindow):
|
||||||
search_paths.append(Path(__file__).parent / "bridge_script_intercept.js")
|
search_paths.append(Path(__file__).parent / "bridge_script_intercept.js")
|
||||||
|
|
||||||
# 2. PyInstaller bundle (via sys._MEIPASS)
|
# 2. PyInstaller bundle (via sys._MEIPASS)
|
||||||
if hasattr(sys, '_MEIPASS'):
|
if hasattr(sys, "_MEIPASS"):
|
||||||
search_paths.append(Path(sys._MEIPASS) / "webdrop_bridge" / "ui" / "bridge_script_intercept.js") # type: ignore
|
search_paths.append(Path(sys._MEIPASS) / "webdrop_bridge" / "ui" / "bridge_script_intercept.js") # type: ignore
|
||||||
|
|
||||||
# 3. Installed executable's directory (handles MSI installation where all files are packaged together)
|
# 3. Installed executable's directory (handles MSI installation where all files are packaged together)
|
||||||
|
|
@ -487,17 +490,21 @@ class MainWindow(QMainWindow):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if script_path is None:
|
if script_path is None:
|
||||||
raise FileNotFoundError("bridge_script_intercept.js not found in any expected location")
|
raise FileNotFoundError(
|
||||||
|
"bridge_script_intercept.js not found in any expected location"
|
||||||
|
)
|
||||||
|
|
||||||
with open(script_path, 'r', encoding='utf-8') as f:
|
with open(script_path, "r", encoding="utf-8") as f:
|
||||||
bridge_code = f.read()
|
bridge_code = f.read()
|
||||||
|
|
||||||
# Load download interceptor using similar search path logic
|
# Load download interceptor using similar search path logic
|
||||||
download_search_paths = []
|
download_search_paths = []
|
||||||
download_search_paths.append(Path(__file__).parent / "download_interceptor.js")
|
download_search_paths.append(Path(__file__).parent / "download_interceptor.js")
|
||||||
if hasattr(sys, '_MEIPASS'):
|
if hasattr(sys, "_MEIPASS"):
|
||||||
download_search_paths.append(Path(sys._MEIPASS) / "webdrop_bridge" / "ui" / "download_interceptor.js") # type: ignore
|
download_search_paths.append(Path(sys._MEIPASS) / "webdrop_bridge" / "ui" / "download_interceptor.js") # type: ignore
|
||||||
download_search_paths.append(exe_dir / "webdrop_bridge" / "ui" / "download_interceptor.js")
|
download_search_paths.append(
|
||||||
|
exe_dir / "webdrop_bridge" / "ui" / "download_interceptor.js"
|
||||||
|
)
|
||||||
|
|
||||||
download_interceptor_code = ""
|
download_interceptor_code = ""
|
||||||
for path in download_search_paths:
|
for path in download_search_paths:
|
||||||
|
|
@ -507,7 +514,7 @@ class MainWindow(QMainWindow):
|
||||||
|
|
||||||
if download_interceptor_path:
|
if download_interceptor_path:
|
||||||
try:
|
try:
|
||||||
with open(download_interceptor_path, 'r', encoding='utf-8') as f:
|
with open(download_interceptor_path, "r", encoding="utf-8") as f:
|
||||||
download_interceptor_code = f.read()
|
download_interceptor_code = f.read()
|
||||||
logger.debug(f"Loaded download interceptor from {download_interceptor_path}")
|
logger.debug(f"Loaded download interceptor from {download_interceptor_path}")
|
||||||
except (OSError, IOError) as e:
|
except (OSError, IOError) as e:
|
||||||
|
|
@ -521,11 +528,13 @@ class MainWindow(QMainWindow):
|
||||||
if download_interceptor_code:
|
if download_interceptor_code:
|
||||||
combined_code += "\n\n" + download_interceptor_code
|
combined_code += "\n\n" + download_interceptor_code
|
||||||
|
|
||||||
logger.debug(f"Combined script size: {len(combined_code)} chars "
|
logger.debug(
|
||||||
|
f"Combined script size: {len(combined_code)} chars "
|
||||||
f"(qwebchannel: {len(qwebchannel_code)}, "
|
f"(qwebchannel: {len(qwebchannel_code)}, "
|
||||||
f"config: {len(config_code)}, "
|
f"config: {len(config_code)}, "
|
||||||
f"bridge: {len(bridge_code)}, "
|
f"bridge: {len(bridge_code)}, "
|
||||||
f"interceptor: {len(download_interceptor_code)})")
|
f"interceptor: {len(download_interceptor_code)})"
|
||||||
|
)
|
||||||
logger.debug(f"URL mappings in config: {len(self.config.url_mappings)}")
|
logger.debug(f"URL mappings in config: {len(self.config.url_mappings)}")
|
||||||
for i, mapping in enumerate(self.config.url_mappings):
|
for i, mapping in enumerate(self.config.url_mappings):
|
||||||
logger.debug(f" Mapping {i+1}: {mapping.url_prefix} → {mapping.local_path}")
|
logger.debug(f" Mapping {i+1}: {mapping.url_prefix} → {mapping.local_path}")
|
||||||
|
|
@ -553,10 +562,7 @@ class MainWindow(QMainWindow):
|
||||||
# Convert URL mappings to format expected by bridge script
|
# Convert URL mappings to format expected by bridge script
|
||||||
mappings = []
|
mappings = []
|
||||||
for mapping in self.config.url_mappings:
|
for mapping in self.config.url_mappings:
|
||||||
mappings.append({
|
mappings.append({"url_prefix": mapping.url_prefix, "local_path": mapping.local_path})
|
||||||
"url_prefix": mapping.url_prefix,
|
|
||||||
"local_path": mapping.local_path
|
|
||||||
})
|
|
||||||
|
|
||||||
logger.debug(f"Generating config injection with {len(mappings)} URL mappings")
|
logger.debug(f"Generating config injection with {len(mappings)} URL mappings")
|
||||||
for i, m in enumerate(mappings):
|
for i, m in enumerate(mappings):
|
||||||
|
|
@ -610,8 +616,9 @@ class MainWindow(QMainWindow):
|
||||||
|
|
||||||
def _apply_stylesheet(self) -> None:
|
def _apply_stylesheet(self) -> None:
|
||||||
"""Apply application stylesheet if available."""
|
"""Apply application stylesheet if available."""
|
||||||
stylesheet_path = Path(__file__).parent.parent.parent.parent / \
|
stylesheet_path = (
|
||||||
"resources" / "stylesheets" / "default.qss"
|
Path(__file__).parent.parent.parent.parent / "resources" / "stylesheets" / "default.qss"
|
||||||
|
)
|
||||||
|
|
||||||
if stylesheet_path.exists():
|
if stylesheet_path.exists():
|
||||||
try:
|
try:
|
||||||
|
|
@ -632,7 +639,7 @@ class MainWindow(QMainWindow):
|
||||||
logger.info(f"Drag started: {source} -> {local_path}")
|
logger.info(f"Drag started: {source} -> {local_path}")
|
||||||
|
|
||||||
# Ask user if they want to check out the asset
|
# Ask user if they want to check out the asset
|
||||||
if source.startswith('http'):
|
if source.startswith("http"):
|
||||||
self._prompt_checkout(source, local_path)
|
self._prompt_checkout(source, local_path)
|
||||||
|
|
||||||
def _prompt_checkout(self, azure_url: str, local_path: str) -> None:
|
def _prompt_checkout(self, azure_url: str, local_path: str) -> None:
|
||||||
|
|
@ -650,7 +657,7 @@ class MainWindow(QMainWindow):
|
||||||
filename = Path(local_path).name
|
filename = Path(local_path).name
|
||||||
|
|
||||||
# Extract asset ID
|
# Extract asset ID
|
||||||
match = re.search(r'/([^/]+)/[^/]+$', azure_url)
|
match = re.search(r"/([^/]+)/[^/]+$", azure_url)
|
||||||
if not match:
|
if not match:
|
||||||
logger.warning(f"Could not extract asset ID from URL: {azure_url}")
|
logger.warning(f"Could not extract asset ID from URL: {azure_url}")
|
||||||
return
|
return
|
||||||
|
|
@ -709,13 +716,21 @@ class MainWindow(QMainWindow):
|
||||||
# After a short delay, read the result from window variable
|
# After a short delay, read the result from window variable
|
||||||
def check_result():
|
def check_result():
|
||||||
read_code = f"window['{callback_id}']"
|
read_code = f"window['{callback_id}']"
|
||||||
self.web_view.page().runJavaScript(read_code, lambda result: self._handle_checkout_status(result, azure_url, filename, callback_id))
|
self.web_view.page().runJavaScript(
|
||||||
|
read_code,
|
||||||
|
lambda result: self._handle_checkout_status(
|
||||||
|
result, azure_url, filename, callback_id
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Wait 500ms for async fetch to complete
|
# Wait 500ms for async fetch to complete
|
||||||
from PySide6.QtCore import QTimer
|
from PySide6.QtCore import QTimer
|
||||||
|
|
||||||
QTimer.singleShot(500, check_result)
|
QTimer.singleShot(500, check_result)
|
||||||
|
|
||||||
def _handle_checkout_status(self, result, azure_url: str, filename: str, callback_id: str) -> None:
|
def _handle_checkout_status(
|
||||||
|
self, result, azure_url: str, filename: str, callback_id: str
|
||||||
|
) -> None:
|
||||||
"""Handle the result of checkout status check.
|
"""Handle the result of checkout status check.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -738,22 +753,25 @@ class MainWindow(QMainWindow):
|
||||||
# Parse JSON string
|
# Parse JSON string
|
||||||
try:
|
try:
|
||||||
import json
|
import json
|
||||||
|
|
||||||
parsed_result = json.loads(result)
|
parsed_result = json.loads(result)
|
||||||
except (json.JSONDecodeError, ValueError) as e:
|
except (json.JSONDecodeError, ValueError) as e:
|
||||||
logger.warning(f"Failed to parse checkout status result: {e}")
|
logger.warning(f"Failed to parse checkout status result: {e}")
|
||||||
self._show_checkout_dialog(azure_url, filename)
|
self._show_checkout_dialog(azure_url, filename)
|
||||||
return
|
return
|
||||||
|
|
||||||
if parsed_result.get('error'):
|
if parsed_result.get("error"):
|
||||||
logger.warning(f"Could not check checkout status: {parsed_result}")
|
logger.warning(f"Could not check checkout status: {parsed_result}")
|
||||||
self._show_checkout_dialog(azure_url, filename)
|
self._show_checkout_dialog(azure_url, filename)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if already checked out
|
# Check if already checked out
|
||||||
has_checkout = parsed_result.get('hasCheckout', False)
|
has_checkout = parsed_result.get("hasCheckout", False)
|
||||||
if has_checkout:
|
if has_checkout:
|
||||||
checkout_info = parsed_result.get('checkout', {})
|
checkout_info = parsed_result.get("checkout", {})
|
||||||
logger.info(f"Asset {filename} is already checked out: {checkout_info}, skipping dialog")
|
logger.info(
|
||||||
|
f"Asset {filename} is already checked out: {checkout_info}, skipping dialog"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Not checked out, show confirmation dialog
|
# Not checked out, show confirmation dialog
|
||||||
|
|
@ -774,7 +792,7 @@ class MainWindow(QMainWindow):
|
||||||
"Checkout Asset",
|
"Checkout Asset",
|
||||||
f"Do you want to check out this asset?\n\n{filename}",
|
f"Do you want to check out this asset?\n\n{filename}",
|
||||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
QMessageBox.StandardButton.Yes
|
QMessageBox.StandardButton.Yes,
|
||||||
)
|
)
|
||||||
|
|
||||||
if reply == QMessageBox.StandardButton.Yes:
|
if reply == QMessageBox.StandardButton.Yes:
|
||||||
|
|
@ -796,7 +814,7 @@ class MainWindow(QMainWindow):
|
||||||
try:
|
try:
|
||||||
# Extract asset ID from URL (middle segment between domain and filename)
|
# Extract asset ID from URL (middle segment between domain and filename)
|
||||||
# Format: https://domain/container/ASSET_ID/filename
|
# Format: https://domain/container/ASSET_ID/filename
|
||||||
match = re.search(r'/([^/]+)/[^/]+$', azure_url)
|
match = re.search(r"/([^/]+)/[^/]+$", azure_url)
|
||||||
if not match:
|
if not match:
|
||||||
logger.warning(f"Could not extract asset ID from URL: {azure_url}")
|
logger.warning(f"Could not extract asset ID from URL: {azure_url}")
|
||||||
return
|
return
|
||||||
|
|
@ -850,11 +868,11 @@ class MainWindow(QMainWindow):
|
||||||
def on_result(result):
|
def on_result(result):
|
||||||
"""Callback when JavaScript completes."""
|
"""Callback when JavaScript completes."""
|
||||||
if result and isinstance(result, dict):
|
if result and isinstance(result, dict):
|
||||||
if result.get('success'):
|
if result.get("success"):
|
||||||
logger.info(f"✅ Checkout successful for asset {asset_id}")
|
logger.info(f"✅ Checkout successful for asset {asset_id}")
|
||||||
else:
|
else:
|
||||||
status = result.get('status', 'unknown')
|
status = result.get("status", "unknown")
|
||||||
error = result.get('error', 'unknown error')
|
error = result.get("error", "unknown error")
|
||||||
logger.warning(f"Checkout API returned status {status}: {error}")
|
logger.warning(f"Checkout API returned status {status}: {error}")
|
||||||
else:
|
else:
|
||||||
logger.debug(f"Checkout API call completed (result: {result})")
|
logger.debug(f"Checkout API call completed (result: {result})")
|
||||||
|
|
@ -873,7 +891,12 @@ class MainWindow(QMainWindow):
|
||||||
error: Error message
|
error: Error message
|
||||||
"""
|
"""
|
||||||
logger.warning(f"Drag failed for {source}: {error}")
|
logger.warning(f"Drag failed for {source}: {error}")
|
||||||
# Can be extended with user notification or status bar message
|
# Show error dialog to user
|
||||||
|
QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"Drag-and-Drop Error",
|
||||||
|
f"Could not complete the drag-and-drop operation.\n\nError: {error}",
|
||||||
|
)
|
||||||
|
|
||||||
def _on_download_requested(self, download: QWebEngineDownloadRequest) -> None:
|
def _on_download_requested(self, download: QWebEngineDownloadRequest) -> None:
|
||||||
"""Handle download requests from the embedded web view.
|
"""Handle download requests from the embedded web view.
|
||||||
|
|
@ -919,9 +942,7 @@ class MainWindow(QMainWindow):
|
||||||
logger.info(f"Download started: {filename}")
|
logger.info(f"Download started: {filename}")
|
||||||
|
|
||||||
# Update status bar (temporarily)
|
# Update status bar (temporarily)
|
||||||
self.status_bar.showMessage(
|
self.status_bar.showMessage(f"📥 Download: {filename}", 3000)
|
||||||
f"📥 Download: {filename}", 3000
|
|
||||||
)
|
|
||||||
|
|
||||||
# Connect to state changed for progress tracking
|
# Connect to state changed for progress tracking
|
||||||
download.stateChanged.connect(
|
download.stateChanged.connect(
|
||||||
|
|
@ -953,19 +974,13 @@ class MainWindow(QMainWindow):
|
||||||
|
|
||||||
if state == QWebEngineDownloadRequest.DownloadState.DownloadCompleted:
|
if state == QWebEngineDownloadRequest.DownloadState.DownloadCompleted:
|
||||||
logger.info(f"Download completed: {file_path.name}")
|
logger.info(f"Download completed: {file_path.name}")
|
||||||
self.status_bar.showMessage(
|
self.status_bar.showMessage(f"Download completed: {file_path.name}", 5000)
|
||||||
f"Download completed: {file_path.name}", 5000
|
|
||||||
)
|
|
||||||
elif state == QWebEngineDownloadRequest.DownloadState.DownloadCancelled:
|
elif state == QWebEngineDownloadRequest.DownloadState.DownloadCancelled:
|
||||||
logger.info(f"Download cancelled: {file_path.name}")
|
logger.info(f"Download cancelled: {file_path.name}")
|
||||||
self.status_bar.showMessage(
|
self.status_bar.showMessage(f"⚠️ Download abgebrochen: {file_path.name}", 3000)
|
||||||
f"⚠️ Download abgebrochen: {file_path.name}", 3000
|
|
||||||
)
|
|
||||||
elif state == QWebEngineDownloadRequest.DownloadState.DownloadInterrupted:
|
elif state == QWebEngineDownloadRequest.DownloadState.DownloadInterrupted:
|
||||||
logger.warning(f"Download interrupted: {file_path.name}")
|
logger.warning(f"Download interrupted: {file_path.name}")
|
||||||
self.status_bar.showMessage(
|
self.status_bar.showMessage(f"❌ Download fehlgeschlagen: {file_path.name}", 5000)
|
||||||
f"❌ Download fehlgeschlagen: {file_path.name}", 5000
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in download finished handler: {e}", exc_info=True)
|
logger.error(f"Error in download finished handler: {e}", exc_info=True)
|
||||||
|
|
||||||
|
|
@ -1049,8 +1064,8 @@ class MainWindow(QMainWindow):
|
||||||
|
|
||||||
# Execute JS to check if our script is loaded
|
# Execute JS to check if our script is loaded
|
||||||
self.web_view.page().runJavaScript(
|
self.web_view.page().runJavaScript(
|
||||||
"typeof window.__webdrop_bridge_injected !== 'undefined' && window.__webdrop_bridge_injected === true",
|
"typeof window.__webdrop_intercept_injected !== 'undefined' && window.__webdrop_intercept_injected === true",
|
||||||
check_script
|
check_script,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _create_navigation_toolbar(self) -> None:
|
def _create_navigation_toolbar(self) -> None:
|
||||||
|
|
@ -1065,29 +1080,25 @@ class MainWindow(QMainWindow):
|
||||||
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, toolbar)
|
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, toolbar)
|
||||||
|
|
||||||
# Back button
|
# Back button
|
||||||
back_action = self.web_view.pageAction(
|
back_action = self.web_view.pageAction(self.web_view.page().WebAction.Back)
|
||||||
self.web_view.page().WebAction.Back
|
|
||||||
)
|
|
||||||
toolbar.addAction(back_action)
|
toolbar.addAction(back_action)
|
||||||
|
|
||||||
# Forward button
|
# Forward button
|
||||||
forward_action = self.web_view.pageAction(
|
forward_action = self.web_view.pageAction(self.web_view.page().WebAction.Forward)
|
||||||
self.web_view.page().WebAction.Forward
|
|
||||||
)
|
|
||||||
toolbar.addAction(forward_action)
|
toolbar.addAction(forward_action)
|
||||||
|
|
||||||
# Separator
|
# Separator
|
||||||
toolbar.addSeparator()
|
toolbar.addSeparator()
|
||||||
|
|
||||||
# Home button
|
# Home button
|
||||||
home_action = toolbar.addAction(self.style().standardIcon(self.style().StandardPixmap.SP_DirHomeIcon), "")
|
home_action = toolbar.addAction(
|
||||||
|
self.style().standardIcon(self.style().StandardPixmap.SP_DirHomeIcon), ""
|
||||||
|
)
|
||||||
home_action.setToolTip("Home")
|
home_action.setToolTip("Home")
|
||||||
home_action.triggered.connect(self._navigate_home)
|
home_action.triggered.connect(self._navigate_home)
|
||||||
|
|
||||||
# Refresh button
|
# Refresh button
|
||||||
refresh_action = self.web_view.pageAction(
|
refresh_action = self.web_view.pageAction(self.web_view.page().WebAction.Reload)
|
||||||
self.web_view.page().WebAction.Reload
|
|
||||||
)
|
|
||||||
toolbar.addAction(refresh_action)
|
toolbar.addAction(refresh_action)
|
||||||
|
|
||||||
# Add stretch spacer to push help buttons to the right
|
# Add stretch spacer to push help buttons to the right
|
||||||
|
|
@ -1211,7 +1222,7 @@ class MainWindow(QMainWindow):
|
||||||
|
|
||||||
# Properly delete WebEnginePage before the profile is released
|
# Properly delete WebEnginePage before the profile is released
|
||||||
# This ensures cookies and session data are saved correctly
|
# This ensures cookies and session data are saved correctly
|
||||||
if hasattr(self, 'web_view') and self.web_view:
|
if hasattr(self, "web_view") and self.web_view:
|
||||||
page = self.web_view.page()
|
page = self.web_view.page()
|
||||||
if page:
|
if page:
|
||||||
# Disconnect signals to prevent callbacks during shutdown
|
# Disconnect signals to prevent callbacks during shutdown
|
||||||
|
|
@ -1240,10 +1251,7 @@ class MainWindow(QMainWindow):
|
||||||
try:
|
try:
|
||||||
# Create update manager
|
# Create update manager
|
||||||
cache_dir = Path.home() / ".webdrop-bridge"
|
cache_dir = Path.home() / ".webdrop-bridge"
|
||||||
manager = UpdateManager(
|
manager = UpdateManager(current_version=self.config.app_version, config_dir=cache_dir)
|
||||||
current_version=self.config.app_version,
|
|
||||||
config_dir=cache_dir
|
|
||||||
)
|
|
||||||
|
|
||||||
# Run async check in background
|
# Run async check in background
|
||||||
self._run_async_check(manager)
|
self._run_async_check(manager)
|
||||||
|
|
@ -1267,7 +1275,7 @@ class MainWindow(QMainWindow):
|
||||||
# IMPORTANT: Keep references to prevent garbage collection
|
# IMPORTANT: Keep references to prevent garbage collection
|
||||||
# Store in a list to keep worker alive during thread execution
|
# Store in a list to keep worker alive during thread execution
|
||||||
self._background_threads.append(thread)
|
self._background_threads.append(thread)
|
||||||
self._background_workers = getattr(self, '_background_workers', {})
|
self._background_workers = getattr(self, "_background_workers", {})
|
||||||
self._background_workers[id(thread)] = worker
|
self._background_workers[id(thread)] = worker
|
||||||
|
|
||||||
logger.debug(f"Created worker and thread, thread id: {id(thread)}")
|
logger.debug(f"Created worker and thread, thread id: {id(thread)}")
|
||||||
|
|
@ -1284,18 +1292,19 @@ class MainWindow(QMainWindow):
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.warning("Update check taking too long (30s timeout)")
|
logger.warning("Update check taking too long (30s timeout)")
|
||||||
if hasattr(self, 'checking_dialog') and self.checking_dialog:
|
if hasattr(self, "checking_dialog") and self.checking_dialog:
|
||||||
self.checking_dialog.close()
|
self.checking_dialog.close()
|
||||||
self.set_update_status("Check timed out - no server response", emoji="⏱️")
|
self.set_update_status("Check timed out - no server response", emoji="⏱️")
|
||||||
|
|
||||||
# Show error dialog
|
# Show error dialog
|
||||||
from PySide6.QtWidgets import QMessageBox
|
from PySide6.QtWidgets import QMessageBox
|
||||||
|
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self,
|
self,
|
||||||
"Update Check Timeout",
|
"Update Check Timeout",
|
||||||
"The server did not respond within 30 seconds.\n\n"
|
"The server did not respond within 30 seconds.\n\n"
|
||||||
"This may be due to a network issue or server unavailability.\n\n"
|
"This may be due to a network issue or server unavailability.\n\n"
|
||||||
"Please check your connection and try again."
|
"Please check your connection and try again.",
|
||||||
)
|
)
|
||||||
|
|
||||||
safety_timer = QTimer()
|
safety_timer = QTimer()
|
||||||
|
|
@ -1356,10 +1365,11 @@ class MainWindow(QMainWindow):
|
||||||
# If this is a manual check and we get the "Ready" status, it means no updates
|
# If this is a manual check and we get the "Ready" status, it means no updates
|
||||||
if self._is_manual_check and status == "Ready":
|
if self._is_manual_check and status == "Ready":
|
||||||
# Close checking dialog first, then show result
|
# Close checking dialog first, then show result
|
||||||
if hasattr(self, 'checking_dialog') and self.checking_dialog:
|
if hasattr(self, "checking_dialog") and self.checking_dialog:
|
||||||
self.checking_dialog.close()
|
self.checking_dialog.close()
|
||||||
|
|
||||||
from webdrop_bridge.ui.update_manager_ui import NoUpdateDialog
|
from webdrop_bridge.ui.update_manager_ui import NoUpdateDialog
|
||||||
|
|
||||||
dialog = NoUpdateDialog(parent=self)
|
dialog = NoUpdateDialog(parent=self)
|
||||||
self._is_manual_check = False
|
self._is_manual_check = False
|
||||||
dialog.exec()
|
dialog.exec()
|
||||||
|
|
@ -1375,14 +1385,15 @@ class MainWindow(QMainWindow):
|
||||||
self._is_manual_check = False
|
self._is_manual_check = False
|
||||||
|
|
||||||
# Close checking dialog first, then show error
|
# Close checking dialog first, then show error
|
||||||
if hasattr(self, 'checking_dialog') and self.checking_dialog:
|
if hasattr(self, "checking_dialog") and self.checking_dialog:
|
||||||
self.checking_dialog.close()
|
self.checking_dialog.close()
|
||||||
|
|
||||||
from PySide6.QtWidgets import QMessageBox
|
from PySide6.QtWidgets import QMessageBox
|
||||||
|
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self,
|
self,
|
||||||
"Update Check Failed",
|
"Update Check Failed",
|
||||||
f"Could not check for updates:\n\n{error_message}\n\nPlease try again later."
|
f"Could not check for updates:\n\n{error_message}\n\nPlease try again later.",
|
||||||
)
|
)
|
||||||
|
|
||||||
def _on_update_available(self, release) -> None:
|
def _on_update_available(self, release) -> None:
|
||||||
|
|
@ -1397,11 +1408,7 @@ class MainWindow(QMainWindow):
|
||||||
# Show update available dialog
|
# Show update available dialog
|
||||||
from webdrop_bridge.ui.update_manager_ui import UpdateAvailableDialog
|
from webdrop_bridge.ui.update_manager_ui import UpdateAvailableDialog
|
||||||
|
|
||||||
dialog = UpdateAvailableDialog(
|
dialog = UpdateAvailableDialog(version=release.version, changelog=release.body, parent=self)
|
||||||
version=release.version,
|
|
||||||
changelog=release.body,
|
|
||||||
parent=self
|
|
||||||
)
|
|
||||||
|
|
||||||
# Connect dialog signals
|
# Connect dialog signals
|
||||||
dialog.update_now.connect(lambda: self._on_user_update_now(release))
|
dialog.update_now.connect(lambda: self._on_user_update_now(release))
|
||||||
|
|
@ -1467,8 +1474,7 @@ class MainWindow(QMainWindow):
|
||||||
|
|
||||||
# Create update manager
|
# Create update manager
|
||||||
manager = UpdateManager(
|
manager = UpdateManager(
|
||||||
current_version=self.config.app_version,
|
current_version=self.config.app_version, config_dir=Path.home() / ".webdrop-bridge"
|
||||||
config_dir=Path.home() / ".webdrop-bridge"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create and start background thread
|
# Create and start background thread
|
||||||
|
|
@ -1557,9 +1563,7 @@ class MainWindow(QMainWindow):
|
||||||
|
|
||||||
# Show install confirmation dialog
|
# Show install confirmation dialog
|
||||||
install_dialog = InstallDialog(parent=self)
|
install_dialog = InstallDialog(parent=self)
|
||||||
install_dialog.install_now.connect(
|
install_dialog.install_now.connect(lambda: self._do_install(installer_path))
|
||||||
lambda: self._do_install(installer_path)
|
|
||||||
)
|
|
||||||
install_dialog.exec()
|
install_dialog.exec()
|
||||||
|
|
||||||
def _on_download_failed(self, error: str) -> None:
|
def _on_download_failed(self, error: str) -> None:
|
||||||
|
|
@ -1572,10 +1576,11 @@ class MainWindow(QMainWindow):
|
||||||
self.set_update_status(error, emoji="❌")
|
self.set_update_status(error, emoji="❌")
|
||||||
|
|
||||||
from PySide6.QtWidgets import QMessageBox
|
from PySide6.QtWidgets import QMessageBox
|
||||||
|
|
||||||
QMessageBox.critical(
|
QMessageBox.critical(
|
||||||
self,
|
self,
|
||||||
"Download Failed",
|
"Download Failed",
|
||||||
f"Could not download the update:\n\n{error}\n\nPlease try again later."
|
f"Could not download the update:\n\n{error}\n\nPlease try again later.",
|
||||||
)
|
)
|
||||||
|
|
||||||
def _do_install(self, installer_path: Path) -> None:
|
def _do_install(self, installer_path: Path) -> None:
|
||||||
|
|
@ -1589,8 +1594,7 @@ class MainWindow(QMainWindow):
|
||||||
from webdrop_bridge.core.updater import UpdateManager
|
from webdrop_bridge.core.updater import UpdateManager
|
||||||
|
|
||||||
manager = UpdateManager(
|
manager = UpdateManager(
|
||||||
current_version=self.config.app_version,
|
current_version=self.config.app_version, config_dir=Path.home() / ".webdrop-bridge"
|
||||||
config_dir=Path.home() / ".webdrop-bridge"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if manager.install_update(installer_path):
|
if manager.install_update(installer_path):
|
||||||
|
|
@ -1639,10 +1643,7 @@ class UpdateCheckWorker(QObject):
|
||||||
# Check for updates with short timeout (network call has its own timeout)
|
# Check for updates with short timeout (network call has its own timeout)
|
||||||
logger.debug("Starting update check with 10-second timeout")
|
logger.debug("Starting update check with 10-second timeout")
|
||||||
release = loop.run_until_complete(
|
release = loop.run_until_complete(
|
||||||
asyncio.wait_for(
|
asyncio.wait_for(self.manager.check_for_updates(), timeout=10)
|
||||||
self.manager.check_for_updates(),
|
|
||||||
timeout=10
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
logger.debug(f"Update check completed, release={release}")
|
logger.debug(f"Update check completed, release={release}")
|
||||||
|
|
||||||
|
|
@ -1711,10 +1712,7 @@ class UpdateDownloadWorker(QObject):
|
||||||
# Download with 5 minute timeout (300 seconds)
|
# Download with 5 minute timeout (300 seconds)
|
||||||
logger.info("Starting download with 5-minute timeout")
|
logger.info("Starting download with 5-minute timeout")
|
||||||
installer_path = loop.run_until_complete(
|
installer_path = loop.run_until_complete(
|
||||||
asyncio.wait_for(
|
asyncio.wait_for(self.manager.download_update(self.release), timeout=300)
|
||||||
self.manager.download_update(self.release),
|
|
||||||
timeout=300
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not installer_path:
|
if not installer_path:
|
||||||
|
|
@ -1730,8 +1728,7 @@ class UpdateDownloadWorker(QObject):
|
||||||
logger.info("Starting checksum verification")
|
logger.info("Starting checksum verification")
|
||||||
checksum_ok = loop.run_until_complete(
|
checksum_ok = loop.run_until_complete(
|
||||||
asyncio.wait_for(
|
asyncio.wait_for(
|
||||||
self.manager.verify_checksum(installer_path, self.release),
|
self.manager.verify_checksum(installer_path, self.release), timeout=30
|
||||||
timeout=30
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1747,7 +1744,9 @@ class UpdateDownloadWorker(QObject):
|
||||||
except asyncio.TimeoutError as e:
|
except asyncio.TimeoutError as e:
|
||||||
logger.error(f"Download/verification timed out: {e}")
|
logger.error(f"Download/verification timed out: {e}")
|
||||||
self.update_status.emit("Operation timed out", "⏱️")
|
self.update_status.emit("Operation timed out", "⏱️")
|
||||||
self.download_failed.emit("Download or verification timed out (no response from server)")
|
self.download_failed.emit(
|
||||||
|
"Download or verification timed out (no response from server)"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error during download: {e}")
|
logger.error(f"Error during download: {e}")
|
||||||
self.download_failed.emit(f"Download error: {str(e)[:50]}")
|
self.download_failed.emit(f"Download error: {str(e)[:50]}")
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from PySide6.QtCore import Qt
|
from PySide6.QtCore import Qt
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
|
QComboBox,
|
||||||
QDialog,
|
QDialog,
|
||||||
QDialogButtonBox,
|
QDialogButtonBox,
|
||||||
QFileDialog,
|
QFileDialog,
|
||||||
|
|
@ -41,7 +42,7 @@ class SettingsDialog(QDialog):
|
||||||
- Profiles: Save/load/delete configuration profiles
|
- Profiles: Save/load/delete configuration profiles
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config: Config, parent=None):
|
def __init__(self, config: Config, parent: Optional[QWidget] = None):
|
||||||
"""Initialize the settings dialog.
|
"""Initialize the settings dialog.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -98,17 +99,16 @@ class SettingsDialog(QDialog):
|
||||||
from webdrop_bridge.config import URLMapping
|
from webdrop_bridge.config import URLMapping
|
||||||
|
|
||||||
url_mappings = [
|
url_mappings = [
|
||||||
URLMapping(
|
URLMapping(url_prefix=m["url_prefix"], local_path=m["local_path"])
|
||||||
url_prefix=m["url_prefix"],
|
|
||||||
local_path=m["local_path"]
|
|
||||||
)
|
|
||||||
for m in config_data["url_mappings"]
|
for m in config_data["url_mappings"]
|
||||||
]
|
]
|
||||||
|
|
||||||
# Update the config object with new values
|
# Update the config object with new values
|
||||||
old_log_level = self.config.log_level
|
old_log_level = self.config.log_level
|
||||||
self.config.log_level = config_data["log_level"]
|
self.config.log_level = config_data["log_level"]
|
||||||
self.config.log_file = Path(config_data["log_file"]) if config_data["log_file"] else None
|
self.config.log_file = (
|
||||||
|
Path(config_data["log_file"]) if config_data["log_file"] else None
|
||||||
|
)
|
||||||
self.config.allowed_roots = [Path(r).resolve() for r in config_data["allowed_roots"]]
|
self.config.allowed_roots = [Path(r).resolve() for r in config_data["allowed_roots"]]
|
||||||
self.config.allowed_urls = config_data["allowed_urls"]
|
self.config.allowed_urls = config_data["allowed_urls"]
|
||||||
self.config.webapp_url = config_data["webapp_url"]
|
self.config.webapp_url = config_data["webapp_url"]
|
||||||
|
|
@ -130,7 +130,7 @@ class SettingsDialog(QDialog):
|
||||||
reconfigure_logging(
|
reconfigure_logging(
|
||||||
logger_name="webdrop_bridge",
|
logger_name="webdrop_bridge",
|
||||||
level=self.config.log_level,
|
level=self.config.log_level,
|
||||||
log_file=self.config.log_file
|
log_file=self.config.log_file,
|
||||||
)
|
)
|
||||||
logger.info(f"✅ Log level updated to {self.config.log_level}")
|
logger.info(f"✅ Log level updated to {self.config.log_level}")
|
||||||
|
|
||||||
|
|
@ -157,7 +157,9 @@ class SettingsDialog(QDialog):
|
||||||
|
|
||||||
self.webapp_url_input = QLineEdit()
|
self.webapp_url_input = QLineEdit()
|
||||||
self.webapp_url_input.setText(self.config.webapp_url)
|
self.webapp_url_input.setText(self.config.webapp_url)
|
||||||
self.webapp_url_input.setPlaceholderText("e.g., http://localhost:8080 or file:///./webapp/index.html")
|
self.webapp_url_input.setPlaceholderText(
|
||||||
|
"e.g., http://localhost:8080 or file:///./webapp/index.html"
|
||||||
|
)
|
||||||
url_layout.addWidget(self.webapp_url_input)
|
url_layout.addWidget(self.webapp_url_input)
|
||||||
|
|
||||||
open_btn = QPushButton("Open")
|
open_btn = QPushButton("Open")
|
||||||
|
|
@ -208,6 +210,7 @@ class SettingsDialog(QDialog):
|
||||||
def _open_webapp_url(self) -> None:
|
def _open_webapp_url(self) -> None:
|
||||||
"""Open the webapp URL in the default browser."""
|
"""Open the webapp URL in the default browser."""
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
||||||
url = self.webapp_url_input.text().strip()
|
url = self.webapp_url_input.text().strip()
|
||||||
if url:
|
if url:
|
||||||
# Handle file:// URLs
|
# Handle file:// URLs
|
||||||
|
|
@ -224,14 +227,14 @@ class SettingsDialog(QDialog):
|
||||||
url_prefix, ok1 = QInputDialog.getText(
|
url_prefix, ok1 = QInputDialog.getText(
|
||||||
self,
|
self,
|
||||||
"Add URL Mapping",
|
"Add URL Mapping",
|
||||||
"Enter Azure Blob Storage URL prefix:\n(e.g., https://myblob.blob.core.windows.net/container/)"
|
"Enter Azure Blob Storage URL prefix:\n(e.g., https://myblob.blob.core.windows.net/container/)",
|
||||||
)
|
)
|
||||||
|
|
||||||
if ok1 and url_prefix:
|
if ok1 and url_prefix:
|
||||||
local_path, ok2 = QInputDialog.getText(
|
local_path, ok2 = QInputDialog.getText(
|
||||||
self,
|
self,
|
||||||
"Add URL Mapping",
|
"Add URL Mapping",
|
||||||
"Enter local file system path:\n(e.g., C:\\Share or /mnt/share)"
|
"Enter local file system path:\n(e.g., C:\\Share or /mnt/share)",
|
||||||
)
|
)
|
||||||
|
|
||||||
if ok2 and local_path:
|
if ok2 and local_path:
|
||||||
|
|
@ -249,22 +252,16 @@ class SettingsDialog(QDialog):
|
||||||
self._show_error("Please select a mapping to edit")
|
self._show_error("Please select a mapping to edit")
|
||||||
return
|
return
|
||||||
|
|
||||||
url_prefix = self.url_mappings_table.item(current_row, 0).text()
|
url_prefix = self.url_mappings_table.item(current_row, 0).text() # type: ignore
|
||||||
local_path = self.url_mappings_table.item(current_row, 1).text()
|
local_path = self.url_mappings_table.item(current_row, 1).text() # type: ignore
|
||||||
|
|
||||||
new_url_prefix, ok1 = QInputDialog.getText(
|
new_url_prefix, ok1 = QInputDialog.getText(
|
||||||
self,
|
self, "Edit URL Mapping", "Enter Azure Blob Storage URL prefix:", text=url_prefix
|
||||||
"Edit URL Mapping",
|
|
||||||
"Enter Azure Blob Storage URL prefix:",
|
|
||||||
text=url_prefix
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if ok1 and new_url_prefix:
|
if ok1 and new_url_prefix:
|
||||||
new_local_path, ok2 = QInputDialog.getText(
|
new_local_path, ok2 = QInputDialog.getText(
|
||||||
self,
|
self, "Edit URL Mapping", "Enter local file system path:", text=local_path
|
||||||
"Edit URL Mapping",
|
|
||||||
"Enter local file system path:",
|
|
||||||
text=local_path
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if ok2 and new_local_path:
|
if ok2 and new_local_path:
|
||||||
|
|
@ -345,6 +342,7 @@ class SettingsDialog(QDialog):
|
||||||
# Log level selection
|
# Log level selection
|
||||||
layout.addWidget(QLabel("Log Level:"))
|
layout.addWidget(QLabel("Log Level:"))
|
||||||
from PySide6.QtWidgets import QComboBox
|
from PySide6.QtWidgets import QComboBox
|
||||||
|
|
||||||
self.log_level_combo: QComboBox = self._create_log_level_widget()
|
self.log_level_combo: QComboBox = self._create_log_level_widget()
|
||||||
layout.addWidget(self.log_level_combo)
|
layout.addWidget(self.log_level_combo)
|
||||||
|
|
||||||
|
|
@ -443,10 +441,8 @@ class SettingsDialog(QDialog):
|
||||||
widget.setLayout(layout)
|
widget.setLayout(layout)
|
||||||
return widget
|
return widget
|
||||||
|
|
||||||
def _create_log_level_widget(self):
|
def _create_log_level_widget(self) -> QComboBox:
|
||||||
"""Create log level selection widget."""
|
"""Create log level selection widget."""
|
||||||
from PySide6.QtWidgets import QComboBox
|
|
||||||
|
|
||||||
combo = QComboBox()
|
combo = QComboBox()
|
||||||
levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
||||||
combo.addItems(levels)
|
combo.addItems(levels)
|
||||||
|
|
@ -469,9 +465,7 @@ class SettingsDialog(QDialog):
|
||||||
from PySide6.QtWidgets import QInputDialog
|
from PySide6.QtWidgets import QInputDialog
|
||||||
|
|
||||||
url, ok = QInputDialog.getText(
|
url, ok = QInputDialog.getText(
|
||||||
self,
|
self, "Add URL", "Enter URL pattern (e.g., http://example.com or http://*.example.com):"
|
||||||
"Add URL",
|
|
||||||
"Enter URL pattern (e.g., http://example.com or http://*.example.com):"
|
|
||||||
)
|
)
|
||||||
if ok and url:
|
if ok and url:
|
||||||
self.urls_list.addItem(url)
|
self.urls_list.addItem(url)
|
||||||
|
|
@ -484,10 +478,7 @@ class SettingsDialog(QDialog):
|
||||||
def _browse_log_file(self) -> None:
|
def _browse_log_file(self) -> None:
|
||||||
"""Browse for log file location."""
|
"""Browse for log file location."""
|
||||||
file_path, _ = QFileDialog.getSaveFileName(
|
file_path, _ = QFileDialog.getSaveFileName(
|
||||||
self,
|
self, "Select Log File", str(Path.home()), "Log Files (*.log);;All Files (*)"
|
||||||
"Select Log File",
|
|
||||||
str(Path.home()),
|
|
||||||
"Log Files (*.log);;All Files (*)"
|
|
||||||
)
|
)
|
||||||
if file_path:
|
if file_path:
|
||||||
self.log_file_input.setText(file_path)
|
self.log_file_input.setText(file_path)
|
||||||
|
|
@ -503,9 +494,7 @@ class SettingsDialog(QDialog):
|
||||||
from PySide6.QtWidgets import QInputDialog
|
from PySide6.QtWidgets import QInputDialog
|
||||||
|
|
||||||
profile_name, ok = QInputDialog.getText(
|
profile_name, ok = QInputDialog.getText(
|
||||||
self,
|
self, "Save Profile", "Enter profile name (e.g., work, personal):"
|
||||||
"Save Profile",
|
|
||||||
"Enter profile name (e.g., work, personal):"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if ok and profile_name:
|
if ok and profile_name:
|
||||||
|
|
@ -546,10 +535,7 @@ class SettingsDialog(QDialog):
|
||||||
def _export_config(self) -> None:
|
def _export_config(self) -> None:
|
||||||
"""Export configuration to file."""
|
"""Export configuration to file."""
|
||||||
file_path, _ = QFileDialog.getSaveFileName(
|
file_path, _ = QFileDialog.getSaveFileName(
|
||||||
self,
|
self, "Export Configuration", str(Path.home()), "JSON Files (*.json);;All Files (*)"
|
||||||
"Export Configuration",
|
|
||||||
str(Path.home()),
|
|
||||||
"JSON Files (*.json);;All Files (*)"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if file_path:
|
if file_path:
|
||||||
|
|
@ -561,10 +547,7 @@ class SettingsDialog(QDialog):
|
||||||
def _import_config(self) -> None:
|
def _import_config(self) -> None:
|
||||||
"""Import configuration from file."""
|
"""Import configuration from file."""
|
||||||
file_path, _ = QFileDialog.getOpenFileName(
|
file_path, _ = QFileDialog.getOpenFileName(
|
||||||
self,
|
self, "Import Configuration", str(Path.home()), "JSON Files (*.json);;All Files (*)"
|
||||||
"Import Configuration",
|
|
||||||
str(Path.home()),
|
|
||||||
"JSON Files (*.json);;All Files (*)"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if file_path:
|
if file_path:
|
||||||
|
|
@ -574,7 +557,7 @@ class SettingsDialog(QDialog):
|
||||||
except ConfigurationError as e:
|
except ConfigurationError as e:
|
||||||
self._show_error(f"Failed to import configuration: {e}")
|
self._show_error(f"Failed to import configuration: {e}")
|
||||||
|
|
||||||
def _apply_config_data(self, config_data: dict) -> None:
|
def _apply_config_data(self, config_data: Dict[str, Any]) -> None:
|
||||||
"""Apply imported configuration data to UI.
|
"""Apply imported configuration data to UI.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -599,7 +582,7 @@ class SettingsDialog(QDialog):
|
||||||
self.width_spin.setValue(config_data.get("window_width", 800))
|
self.width_spin.setValue(config_data.get("window_width", 800))
|
||||||
self.height_spin.setValue(config_data.get("window_height", 600))
|
self.height_spin.setValue(config_data.get("window_height", 600))
|
||||||
|
|
||||||
def get_config_data(self) -> dict:
|
def get_config_data(self) -> Dict[str, Any]:
|
||||||
"""Get updated configuration data from dialog.
|
"""Get updated configuration data from dialog.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -608,20 +591,26 @@ class SettingsDialog(QDialog):
|
||||||
Raises:
|
Raises:
|
||||||
ConfigurationError: If configuration is invalid
|
ConfigurationError: If configuration is invalid
|
||||||
"""
|
"""
|
||||||
|
if self.url_mappings_table:
|
||||||
|
url_mappings_table_count = self.url_mappings_table.rowCount() or 0
|
||||||
|
else:
|
||||||
|
url_mappings_table_count = 0
|
||||||
config_data = {
|
config_data = {
|
||||||
"app_name": self.config.app_name,
|
"app_name": self.config.app_name,
|
||||||
"app_version": self.config.app_version,
|
"app_version": self.config.app_version,
|
||||||
"log_level": self.log_level_combo.currentText(),
|
"log_level": self.log_level_combo.currentText(),
|
||||||
"log_file": self.log_file_input.text() or None,
|
"log_file": self.log_file_input.text() or None,
|
||||||
"allowed_roots": [self.paths_list.item(i).text() for i in range(self.paths_list.count())],
|
"allowed_roots": [
|
||||||
|
self.paths_list.item(i).text() for i in range(self.paths_list.count())
|
||||||
|
],
|
||||||
"allowed_urls": [self.urls_list.item(i).text() for i in range(self.urls_list.count())],
|
"allowed_urls": [self.urls_list.item(i).text() for i in range(self.urls_list.count())],
|
||||||
"webapp_url": self.webapp_url_input.text().strip(),
|
"webapp_url": self.webapp_url_input.text().strip(),
|
||||||
"url_mappings": [
|
"url_mappings": [
|
||||||
{
|
{
|
||||||
"url_prefix": self.url_mappings_table.item(i, 0).text(),
|
"url_prefix": self.url_mappings_table.item(i, 0).text() if self.url_mappings_table.item(i, 0) else "", # type: ignore
|
||||||
"local_path": self.url_mappings_table.item(i, 1).text()
|
"local_path": self.url_mappings_table.item(i, 1).text() if self.url_mappings_table.item(i, 1) else "", # type: ignore
|
||||||
}
|
}
|
||||||
for i in range(self.url_mappings_table.rowCount())
|
for i in range(url_mappings_table_count)
|
||||||
],
|
],
|
||||||
"window_width": self.width_spin.value(),
|
"window_width": self.width_spin.value(),
|
||||||
"window_height": self.height_spin.value(),
|
"window_height": self.height_spin.value(),
|
||||||
|
|
@ -640,4 +629,5 @@ class SettingsDialog(QDialog):
|
||||||
message: Error message
|
message: Error message
|
||||||
"""
|
"""
|
||||||
from PySide6.QtWidgets import QMessageBox
|
from PySide6.QtWidgets import QMessageBox
|
||||||
|
|
||||||
QMessageBox.critical(self, "Error", message)
|
QMessageBox.critical(self, "Error", message)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue