Add enable_checkout configuration option and update drag handling logic

This commit is contained in:
claudi 2026-02-25 13:34:37 +01:00
parent 986793632e
commit cbd1f3f77c
3 changed files with 30 additions and 31 deletions

View file

@ -18,5 +18,6 @@
"log_file": null, "log_file": null,
"window_width": 1024, "window_width": 1024,
"window_height": 768, "window_height": 768,
"enable_logging": true "enable_logging": true,
"enable_checkout": false
} }

View file

@ -58,6 +58,8 @@ class Config:
window_height: Initial window height in pixels window_height: Initial window height in pixels
window_title: Main window title (default: "{app_name} v{app_version}") window_title: Main window title (default: "{app_name} v{app_version}")
enable_logging: Whether to write logs to file enable_logging: Whether to write logs to file
enable_checkout: Whether to check asset checkout status and show checkout dialog
on drag. Disabled by default as checkout support is optional.
Raises: Raises:
ConfigurationError: If configuration values are invalid ConfigurationError: If configuration values are invalid
@ -78,6 +80,7 @@ class Config:
window_height: int = 768 window_height: int = 768
window_title: str = "" window_title: str = ""
enable_logging: bool = True enable_logging: bool = True
enable_checkout: bool = False
@classmethod @classmethod
def from_file(cls, config_path: Path) -> "Config": def from_file(cls, config_path: Path) -> "Config":
@ -106,10 +109,7 @@ class Config:
# Parse URL mappings # Parse URL mappings
mappings = [ 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 data.get("url_mappings", []) for m in data.get("url_mappings", [])
] ]
@ -138,11 +138,12 @@ class Config:
app_name = data.get("app_name", "WebDrop Bridge") app_name = data.get("app_name", "WebDrop Bridge")
stored_window_title = data.get("window_title", "") stored_window_title = data.get("window_title", "")
# Regenerate default window titles on version upgrade # Regenerate default window titles on version upgrade
# If the stored title matches the pattern "{app_name} v{version}", regenerate it # If the stored title matches the pattern "{app_name} v{version}", regenerate it
# with the current version. This ensures the title updates automatically on upgrades. # with the current version. This ensures the title updates automatically on upgrades.
import re import re
version_pattern = re.compile(rf"^{re.escape(app_name)}\s+v[\d.]+$") version_pattern = re.compile(rf"^{re.escape(app_name)}\s+v[\d.]+$")
if stored_window_title and version_pattern.match(stored_window_title): if stored_window_title and version_pattern.match(stored_window_title):
# Detected a default-pattern title with old version, regenerate # Detected a default-pattern title with old version, regenerate
@ -170,6 +171,7 @@ class Config:
window_height=data.get("window_height", 768), window_height=data.get("window_height", 768),
window_title=window_title, window_title=window_title,
enable_logging=data.get("enable_logging", True), enable_logging=data.get("enable_logging", True),
enable_checkout=data.get("enable_checkout", False),
) )
@classmethod @classmethod
@ -195,8 +197,9 @@ class Config:
app_name = os.getenv("APP_NAME", "WebDrop Bridge") app_name = os.getenv("APP_NAME", "WebDrop Bridge")
# Version always comes from __init__.py for consistency # Version always comes from __init__.py for consistency
from webdrop_bridge import __version__ from webdrop_bridge import __version__
app_version = __version__ app_version = __version__
log_level = os.getenv("LOG_LEVEL", "INFO").upper() log_level = os.getenv("LOG_LEVEL", "INFO").upper()
log_file_str = os.getenv("LOG_FILE", None) log_file_str = os.getenv("LOG_FILE", None)
allowed_roots_str = os.getenv("ALLOWED_ROOTS", "Z:/,C:/Users/Public") allowed_roots_str = os.getenv("ALLOWED_ROOTS", "Z:/,C:/Users/Public")
@ -208,13 +211,13 @@ class Config:
default_title = f"{app_name} v{app_version}" default_title = f"{app_name} v{app_version}"
window_title = os.getenv("WINDOW_TITLE", default_title) window_title = os.getenv("WINDOW_TITLE", default_title)
enable_logging = os.getenv("ENABLE_LOGGING", "true").lower() == "true" enable_logging = os.getenv("ENABLE_LOGGING", "true").lower() == "true"
enable_checkout = os.getenv("ENABLE_CHECKOUT", "false").lower() == "true"
# Validate log level # Validate log level
valid_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"} valid_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
if log_level not in valid_levels: if log_level not in valid_levels:
raise ConfigurationError( raise ConfigurationError(
f"Invalid LOG_LEVEL: {log_level}. " f"Invalid LOG_LEVEL: {log_level}. " f"Must be one of: {', '.join(valid_levels)}"
f"Must be one of: {', '.join(valid_levels)}"
) )
# Validate and parse allowed roots # Validate and parse allowed roots
@ -225,9 +228,7 @@ class Config:
if not root_path.exists(): if not root_path.exists():
logger.warning(f"Allowed root does not exist: {p.strip()}") logger.warning(f"Allowed root does not exist: {p.strip()}")
elif not root_path.is_dir(): elif not root_path.is_dir():
raise ConfigurationError( raise ConfigurationError(f"Allowed root '{p.strip()}' is not a directory")
f"Allowed root '{p.strip()}' is not a directory"
)
else: else:
allowed_roots.append(root_path) allowed_roots.append(root_path)
except ConfigurationError: except ConfigurationError:
@ -240,8 +241,7 @@ class Config:
# Validate window dimensions # Validate window dimensions
if window_width <= 0 or window_height <= 0: if window_width <= 0 or window_height <= 0:
raise ConfigurationError( raise ConfigurationError(
f"Window dimensions must be positive: " f"Window dimensions must be positive: " f"{window_width}x{window_height}"
f"{window_width}x{window_height}"
) )
# Create log file path if logging enabled # Create log file path if logging enabled
@ -261,10 +261,11 @@ class Config:
raise ConfigurationError("WEBAPP_URL cannot be empty") raise ConfigurationError("WEBAPP_URL cannot be empty")
# Parse allowed URLs (empty string = no restriction) # Parse allowed URLs (empty string = no restriction)
allowed_urls = [ allowed_urls = (
url.strip() for url in allowed_urls_str.split(",") [url.strip() for url in allowed_urls_str.split(",") if url.strip()]
if url.strip() if allowed_urls_str
] if allowed_urls_str else [] else []
)
# Parse URL mappings (Azure Blob Storage → Local Paths) # Parse URL mappings (Azure Blob Storage → Local Paths)
# Format: url_prefix1=local_path1;url_prefix2=local_path2 # Format: url_prefix1=local_path1;url_prefix2=local_path2
@ -282,10 +283,7 @@ class Config:
) )
url_prefix, local_path_str = mapping.split("=", 1) url_prefix, local_path_str = mapping.split("=", 1)
url_mappings.append( url_mappings.append(
URLMapping( URLMapping(url_prefix=url_prefix.strip(), local_path=local_path_str.strip())
url_prefix=url_prefix.strip(),
local_path=local_path_str.strip()
)
) )
except (ValueError, OSError) as e: except (ValueError, OSError) as e:
raise ConfigurationError( raise ConfigurationError(
@ -305,6 +303,7 @@ class Config:
window_height=window_height, window_height=window_height,
window_title=window_title, window_title=window_title,
enable_logging=enable_logging, enable_logging=enable_logging,
enable_checkout=enable_checkout,
) )
def to_file(self, config_path: Path) -> None: def to_file(self, config_path: Path) -> None:
@ -312,18 +311,14 @@ class Config:
Args: Args:
config_path: Path to save configuration to config_path: Path to save configuration to
Creates parent directories if they don't exist. Creates parent directories if they don't exist.
""" """
data = { data = {
"app_name": self.app_name, "app_name": self.app_name,
"webapp_url": self.webapp_url, "webapp_url": self.webapp_url,
"url_mappings": [ "url_mappings": [
{ {"url_prefix": m.url_prefix, "local_path": m.local_path} for m in self.url_mappings
"url_prefix": m.url_prefix,
"local_path": m.local_path
}
for m in self.url_mappings
], ],
"allowed_roots": [str(p) for p in self.allowed_roots], "allowed_roots": [str(p) for p in self.allowed_roots],
"allowed_urls": self.allowed_urls, "allowed_urls": self.allowed_urls,
@ -336,6 +331,7 @@ class Config:
"window_height": self.window_height, "window_height": self.window_height,
"window_title": self.window_title, "window_title": self.window_title,
"enable_logging": self.enable_logging, "enable_logging": self.enable_logging,
"enable_checkout": self.enable_checkout,
} }
config_path.parent.mkdir(parents=True, exist_ok=True) config_path.parent.mkdir(parents=True, exist_ok=True)
@ -350,6 +346,7 @@ class Config:
Path to default config file in user's AppData/Roaming Path to default config file in user's AppData/Roaming
""" """
import platform import platform
if platform.system() == "Windows": if platform.system() == "Windows":
base = Path.home() / "AppData" / "Roaming" base = Path.home() / "AppData" / "Roaming"
else: else:
@ -359,7 +356,7 @@ class Config:
@staticmethod @staticmethod
def get_default_log_dir() -> Path: def get_default_log_dir() -> Path:
"""Get the default directory for log files. """Get the default directory for log files.
Always uses user's AppData directory to ensure permissions work Always uses user's AppData directory to ensure permissions work
correctly in both development and installed scenarios. correctly in both development and installed scenarios.
@ -367,6 +364,7 @@ class Config:
Path to default logs directory in user's AppData/Roaming Path to default logs directory in user's AppData/Roaming
""" """
import platform import platform
if platform.system() == "Windows": if platform.system() == "Windows":
base = Path.home() / "AppData" / "Roaming" base = Path.home() / "AppData" / "Roaming"
else: else:

View file

@ -638,8 +638,8 @@ 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 (only when enabled in config)
if source.startswith("http"): if source.startswith("http") and self.config.enable_checkout:
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: