From 9fcb357a10741db1bfae3545320bed11c5d400fa Mon Sep 17 00:00:00 2001 From: Admin9705 <9705@duck.com> Date: Wed, 18 Feb 2026 18:56:25 -0500 Subject: [PATCH] Fix Windows frontend missing files + system tray support - web_server.py: Use sys._MEIPASS for PyInstaller 6.x data file paths - windows_integration.py: Same _MEIPASS fix for Flask folder override - huntarr.spec: Use Tree() for frontend, exclude node_modules, add pystray - huntarr_installer.nsi: Fix File /r glob, remove empty CreateDirectory - requirements.txt: Enable pystray for Windows - system_tray.py: Rewrite with proper _MEIPASS icon loading + shutdown callback - main.py: Integrate system tray on Windows frozen builds - windows-build-nsis.yml: Add pystray install + frontend verification step --- .github/workflows/windows-build-nsis.yml | 12 + distribution/windows/huntarr.spec | 99 ++++--- .../windows/installer/huntarr_installer.nsi | 10 +- distribution/windows/resources/system_tray.py | 269 +++++++----------- main.py | 35 ++- requirements.txt | 2 +- src/primary/utils/windows_integration.py | 34 ++- src/primary/web_server.py | 52 ++-- 8 files changed, 256 insertions(+), 257 deletions(-) diff --git a/.github/workflows/windows-build-nsis.yml b/.github/workflows/windows-build-nsis.yml index 1a859129..ff6e89eb 100644 --- a/.github/workflows/windows-build-nsis.yml +++ b/.github/workflows/windows-build-nsis.yml @@ -52,6 +52,8 @@ jobs: pip install apprise==1.6.0 pip install markdown==3.4.3 pip install pyyaml==6.0.1 + # System tray support + pip install pystray==0.19.5 python -m pip show setuptools pyinstaller - name: Create directories @@ -75,6 +77,16 @@ jobs: # Display contents of dist/Huntarr dir dist/Huntarr + # Verify frontend files were bundled (catch missing files early) + if (!(Test-Path "dist/Huntarr/_internal/frontend/templates/index.html")) { + Write-Host "WARNING: frontend/templates not found in _internal/, checking alternate locations..." + if (!(Test-Path "dist/Huntarr/_internal/templates/index.html")) { + Write-Error "FATAL: Frontend templates missing from PyInstaller bundle!" + exit 1 + } + } + Write-Host "Frontend templates verified in bundle." + - name: Install NSIS run: | choco install nsis -y diff --git a/distribution/windows/huntarr.spec b/distribution/windows/huntarr.spec index 9c78a152..a65d05a2 100644 --- a/distribution/windows/huntarr.spec +++ b/distribution/windows/huntarr.spec @@ -9,17 +9,11 @@ spec_dir = pathlib.Path(os.path.dirname(os.path.abspath(SPECPATH))) project_dir = spec_dir.parent.parent # Go up two levels to project root # In GitHub Actions, the current working directory is already the project root -# Check if we're in GitHub Actions by looking at the environment if os.environ.get('GITHUB_ACTIONS'): - # Use the current directory instead project_dir = pathlib.Path(os.getcwd()) -# Print current directory and list files for debugging print(f"Current directory: {os.getcwd()}") print(f"Project directory: {project_dir}") -print("Files in current directory:") -for file in os.listdir(os.getcwd()): - print(f" {file}") # Find main.py file main_py_path = project_dir / 'main.py' @@ -30,34 +24,48 @@ if not main_py_path.exists(): print(f"Found main.py at: {main_py_path}") else: print("ERROR: main.py not found!") - # Use a placeholder that will cause an error with a clearer message main_py_path = project_dir / 'main.py' block_cipher = None -# Create a list of data files to include with absolute paths +# --------------------------------------------------------------------------- +# Data files to bundle. +# PyInstaller 6.x places these under _internal/ (sys._MEIPASS). +# We include frontend/templates and frontend/static under 'frontend/' so +# code using _MEIPASS/frontend/templates finds them. +# We explicitly EXCLUDE node_modules and frontend/src (dev-only). +# --------------------------------------------------------------------------- + +from PyInstaller.building.datastruct import Tree + +# Collect frontend/templates and frontend/static, skipping node_modules and src +frontend_templates = Tree( + str(project_dir / 'frontend' / 'templates'), + prefix='frontend/templates', + excludes=['*.pyc', '__pycache__'], +) +frontend_static = Tree( + str(project_dir / 'frontend' / 'static'), + prefix='frontend/static', + excludes=['*.pyc', '__pycache__'], +) + datas = [ - (str(project_dir / 'frontend'), 'frontend'), (str(project_dir / 'src'), 'src'), ] -# Add apprise data files to fix attachment directory error +# Also add templates/static at top-level as legacy fallback +datas.append((str(project_dir / 'frontend' / 'templates'), 'templates')) +datas.append((str(project_dir / 'frontend' / 'static'), 'static')) + +# Add apprise data files try: import apprise - import os apprise_path = os.path.dirname(apprise.__file__) - # Add apprise's attachment, plugins, and config directories - apprise_attachment_path = os.path.join(apprise_path, 'attachment') - apprise_plugins_path = os.path.join(apprise_path, 'plugins') - apprise_config_path = os.path.join(apprise_path, 'config') - - if os.path.exists(apprise_attachment_path): - datas.append((apprise_attachment_path, 'apprise/attachment')) - if os.path.exists(apprise_plugins_path): - datas.append((apprise_plugins_path, 'apprise/plugins')) - if os.path.exists(apprise_config_path): - datas.append((apprise_config_path, 'apprise/config')) - + for subdir in ('attachment', 'plugins', 'config'): + p = os.path.join(apprise_path, subdir) + if os.path.exists(p): + datas.append((p, f'apprise/{subdir}')) print(f"Added apprise data directories from: {apprise_path}") except ImportError: print("Warning: apprise not found, skipping apprise data files") @@ -70,28 +78,20 @@ if os.path.exists(str(project_dir / 'tools')): if os.path.exists(str(project_dir / 'assets')): datas.append((str(project_dir / 'assets'), 'assets')) -# Ensure all frontend template files are included -if os.path.exists(str(project_dir / 'frontend')): - print(f"Including frontend directory at {str(project_dir / 'frontend')}") - # Make sure we include all frontend template files - datas.append((str(project_dir / 'frontend/templates'), 'templates')) - datas.append((str(project_dir / 'frontend/static'), 'static')) +# Add distribution/windows/resources for system tray and helpers +resources_dir = project_dir / 'distribution' / 'windows' / 'resources' +if resources_dir.exists(): + datas.append((str(resources_dir), 'resources')) + print(f"Including Windows resources from: {resources_dir}") - # Explicitly check for the login template - login_template = project_dir / 'frontend/templates/login.html' - if os.path.exists(login_template): - print(f"Found login.html at {login_template}") - else: - print(f"WARNING: login.html not found at {login_template}") - - # List all available templates for debugging - template_dir = project_dir / 'frontend/templates' - if os.path.exists(template_dir): - print("Available templates:") - for template_file in os.listdir(template_dir): - print(f" - {template_file}") - else: - print(f"WARNING: Template directory not found at {template_dir}") +# Debug: verify frontend files +template_dir = project_dir / 'frontend' / 'templates' +if template_dir.exists(): + print("Available templates:") + for f in os.listdir(template_dir): + print(f" - {f}") +else: + print(f"WARNING: Template directory not found at {template_dir}") a = Analysis( [str(main_py_path)], @@ -145,7 +145,7 @@ a = Analysis( 'concurrent.futures', # Apprise notification support 'apprise', - 'apprise.common', + 'apprise.common', 'apprise.conversion', 'apprise.decorators', 'apprise.locale', @@ -176,6 +176,9 @@ a = Analysis( 'cryptography.hazmat.primitives.ciphers', 'cryptography.hazmat.backends', 'cryptography.hazmat.backends.openssl', + # System tray support + 'pystray', + 'pystray._win32', ], hookspath=[], hooksconfig={}, @@ -199,13 +202,13 @@ exe = EXE( bootloader_ignore_signals=False, strip=False, upx=True, - console=False, # Hide console window - Huntarr runs as background service + console=False, # Hide console window — runs as system tray app disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, - icon=str(project_dir / 'frontend/static/logo/huntarr.ico'), + icon=str(project_dir / 'frontend' / 'static' / 'logo' / 'huntarr.ico'), ) coll = COLLECT( @@ -213,6 +216,8 @@ coll = COLLECT( a.binaries, a.zipfiles, a.datas, + frontend_templates, + frontend_static, strip=False, upx=True, upx_exclude=[], diff --git a/distribution/windows/installer/huntarr_installer.nsi b/distribution/windows/installer/huntarr_installer.nsi index b7ff68d8..edbe738d 100644 --- a/distribution/windows/installer/huntarr_installer.nsi +++ b/distribution/windows/installer/huntarr_installer.nsi @@ -89,15 +89,15 @@ Section "Huntarr Application (required)" SecCore ; Delete existing service if present (for upgrading from service to non-service) nsExec::ExecToLog '"$INSTDIR\${EXENAME}" --remove-service' - ; Copy all files from dist directory - !echo "Copying files from '${PROJECT_ROOT}\dist\Huntarr\*.*'" - File /r "${PROJECT_ROOT}\dist\Huntarr\*.*" + ; Copy all files from dist directory (PyInstaller output) + !echo "Copying files from '${PROJECT_ROOT}\dist\Huntarr\'" + File /r "${PROJECT_ROOT}\dist\Huntarr\*" ; Copy version.txt file !echo "Copying version.txt from '${PROJECT_ROOT}\version.txt'" File "${PROJECT_ROOT}\version.txt" - ; Create required directories + ; Create required config directories (these are NOT in the PyInstaller bundle) CreateDirectory "$INSTDIR\config" CreateDirectory "$INSTDIR\config\logs" CreateDirectory "$INSTDIR\config\stateful" @@ -109,8 +109,6 @@ Section "Huntarr Application (required)" SecCore CreateDirectory "$INSTDIR\config\tally" CreateDirectory "$INSTDIR\config\eros" CreateDirectory "$INSTDIR\logs" - CreateDirectory "$INSTDIR\frontend\templates" - CreateDirectory "$INSTDIR\frontend\static" ; Set permissions (using PowerShell to avoid quoting issues) nsExec::ExecToLog 'powershell -Command "& {Set-Acl -Path \"$INSTDIR\config\" -AclObject (Get-Acl -Path \"$INSTDIR\config\")}"' diff --git a/distribution/windows/resources/system_tray.py b/distribution/windows/resources/system_tray.py index 2180b38c..a2ca1429 100644 --- a/distribution/windows/resources/system_tray.py +++ b/distribution/windows/resources/system_tray.py @@ -1,199 +1,132 @@ """ Windows System Tray Icon for Huntarr -Provides a system tray icon with menu options to control Huntarr +Provides a system tray icon with menu options to control Huntarr. +Runs in a daemon thread alongside the Waitress web server. """ import os import sys import threading import webbrowser -import pystray -from PIL import Image import logging logger = logging.getLogger('HuntarrSystemTray') + def _safe_port(): - """Parse port from env with fallback. Avoids crash on invalid PORT.""" + """Parse port from env with fallback.""" try: return int(os.environ.get("HUNTARR_PORT", os.environ.get("PORT", 9705))) except (TypeError, ValueError): return 9705 +def _load_icon_image(): + """Load the Huntarr icon from bundled data files. + + Search order: + 1. _MEIPASS/frontend/static/logo/huntarr.ico (PyInstaller 6.x) + 2. _MEIPASS/static/logo/huntarr.ico (legacy fallback) + 3. exe_dir/frontend/static/logo/huntarr.ico (manual copy) + 4. source tree relative path (dev mode) + Falls back to a generated 64x64 orange placeholder. + """ + from PIL import Image + + candidates = [] + + if getattr(sys, 'frozen', False): + meipass = getattr(sys, '_MEIPASS', os.path.dirname(sys.executable)) + exe_dir = os.path.dirname(sys.executable) + candidates += [ + os.path.join(meipass, 'frontend', 'static', 'logo', 'huntarr.ico'), + os.path.join(meipass, 'static', 'logo', 'huntarr.ico'), + os.path.join(exe_dir, 'frontend', 'static', 'logo', 'huntarr.ico'), + ] + else: + # Running from source + project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) + candidates.append(os.path.join(project_root, 'frontend', 'static', 'logo', 'huntarr.ico')) + + for path in candidates: + if os.path.exists(path): + logger.info(f"Loading tray icon from: {path}") + return Image.open(path) + + # Try PNG fallbacks + for base in candidates: + logo_dir = os.path.dirname(base) + for size in ('64', '48', '32'): + png = os.path.join(logo_dir, f'{size}.png') + if os.path.exists(png): + logger.info(f"Loading tray icon from: {png}") + return Image.open(png) + + logger.warning("Huntarr icon not found, using placeholder") + return Image.new('RGB', (64, 64), color=(255, 127, 0)) + + class HuntarrSystemTray: - """System tray icon for Huntarr on Windows""" - - def __init__(self, port=None): - """Initialize the system tray icon - - Args: - port (int): Port number where Huntarr web interface is running. - If None, reads from HUNTARR_PORT or PORT env. - """ + """System tray icon for Huntarr on Windows.""" + + def __init__(self, port=None, shutdown_callback=None): self.port = port if port is not None else _safe_port() - self.icon = None - self.running = True - self.icon_thread = None - - def create_icon_image(self): - """Create or load the icon image for the system tray""" - try: - # Try to load the Huntarr icon from the static folder - if getattr(sys, 'frozen', False): - # Running as PyInstaller bundle - base_path = sys._MEIPASS - else: - # Running as script - base_path = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) - - icon_path = os.path.join(base_path, 'frontend', 'static', 'logo', 'huntarr.ico') - - if os.path.exists(icon_path): - logger.info(f"Loading icon from: {icon_path}") - return Image.open(icon_path) - else: - # Fallback: Try PNG versions - for size in ['64', '48', '32']: - png_path = os.path.join(base_path, 'frontend', 'static', 'logo', f'{size}.png') - if os.path.exists(png_path): - logger.info(f"Loading icon from: {png_path}") - return Image.open(png_path) - - # Final fallback: Create a simple icon - logger.warning("Could not find Huntarr icon, creating placeholder") - return self._create_placeholder_icon() - - except Exception as e: - logger.error(f"Error loading icon: {e}") - return self._create_placeholder_icon() - - def _create_placeholder_icon(self): - """Create a simple placeholder icon""" - # Create a 64x64 orange/blue icon - img = Image.new('RGB', (64, 64), color=(255, 127, 0)) - return img - - def open_web_interface(self, icon=None, item=None): - """Open the Huntarr web interface in the default browser""" + self._shutdown_callback = shutdown_callback + self._icon = None + self._thread = None + + # -- Menu actions -- + + def _open_web(self, icon=None, item=None): try: url = f"http://localhost:{self.port}" webbrowser.open(url) - logger.info(f"Opened web interface: {url}") + logger.info(f"Opened browser: {url}") except Exception as e: - logger.error(f"Error opening web interface: {e}") - - def show_about(self, icon=None, item=None): - """Show about information (opens web interface)""" - self.open_web_interface() - - def exit_app(self, icon=None, item=None): - """Exit Huntarr application""" - logger.info("System tray exit requested") - self.running = False - if self.icon: - self.icon.stop() - - # Signal the main application to shut down - try: - from primary.background import stop_event - if not stop_event.is_set(): - stop_event.set() - logger.info("Stop event set for main application") - except Exception as e: - logger.error(f"Error signaling main application shutdown: {e}") - - # Give threads time to clean up - import time - time.sleep(1) - - # Force exit if needed - os._exit(0) - - def create_menu(self): - """Create the system tray context menu""" - return pystray.Menu( - pystray.MenuItem( - "Open Huntarr", - self.open_web_interface, - default=True - ), - pystray.MenuItem( - "About Huntarr", - self.show_about - ), - pystray.Menu.SEPARATOR, - pystray.MenuItem( - "Exit", - self.exit_app - ) - ) - - def run(self): - """Run the system tray icon (blocking)""" - try: - logger.info("Starting system tray icon...") - - # Create the icon - image = self.create_icon_image() - menu = self.create_menu() - - self.icon = pystray.Icon( - "Huntarr", - image, - "Huntarr - Media Management", - menu - ) - - # Run the icon (this is blocking) - logger.info("System tray icon running") - self.icon.run() - - except Exception as e: - logger.error(f"Error running system tray icon: {e}") - logger.exception(e) - + logger.error(f"Error opening browser: {e}") + + def _exit_app(self, icon=None, item=None): + logger.info("Exit requested from system tray") + self.stop() + if self._shutdown_callback: + try: + self._shutdown_callback() + except Exception as e: + logger.error(f"Shutdown callback error: {e}") + + # -- Lifecycle -- + def start(self): - """Start the system tray icon in a separate thread""" - try: - logger.info("Starting system tray in background thread...") - self.icon_thread = threading.Thread( - target=self.run, - name="SystemTrayThread", - daemon=True - ) - self.icon_thread.start() - logger.info("System tray thread started") - return True - except Exception as e: - logger.error(f"Error starting system tray thread: {e}") - return False - + """Start the tray icon in a daemon thread. Non-blocking.""" + self._thread = threading.Thread(target=self._run, name="SystemTrayThread", daemon=True) + self._thread.start() + logger.info("System tray thread started") + def stop(self): - """Stop the system tray icon""" + """Stop the tray icon.""" + if self._icon: + try: + self._icon.stop() + except Exception: + pass + logger.info("System tray icon stopped") + + def _run(self): try: - self.running = False - if self.icon: - self.icon.stop() - logger.info("System tray icon stopped") + import pystray + image = _load_icon_image() + menu = pystray.Menu( + pystray.MenuItem("Open Huntarr", self._open_web, default=True), + pystray.Menu.SEPARATOR, + pystray.MenuItem("Exit", self._exit_app), + ) + self._icon = pystray.Icon("Huntarr", image, "Huntarr", menu) + logger.info("System tray icon running") + self._icon.run() # blocking except Exception as e: - logger.error(f"Error stopping system tray: {e}") + logger.error(f"System tray error: {e}", exc_info=True) -def create_system_tray(port=None): - """Create and return a system tray instance - - Args: - port (int): Port number where Huntarr is running - - Returns: - HuntarrSystemTray: System tray instance - """ - return HuntarrSystemTray(port=port) - - -if __name__ == '__main__': - # Test the system tray - logging.basicConfig(level=logging.INFO) - tray = HuntarrSystemTray() - tray.run() # Blocking call for testing +def create_system_tray(port=None, shutdown_callback=None): + """Factory function. Returns a HuntarrSystemTray instance.""" + return HuntarrSystemTray(port=port, shutdown_callback=shutdown_callback) diff --git a/main.py b/main.py index 4f15cfa9..619ae734 100644 --- a/main.py +++ b/main.py @@ -235,9 +235,30 @@ def run_web_server(): web_logger.info(f"Starting web server on {host}:{port} (Debug: {debug_mode})...") - # TODO: System tray implementation temporarily disabled - # Will be re-enabled in a future update after thorough testing - # For now, console=False in PyInstaller spec provides silent background operation + # Start Windows system tray icon (non-debug, Windows-only, frozen builds) + _system_tray = None + if (not debug_mode + and sys.platform == 'win32' + and getattr(sys, 'frozen', False)): + try: + # Import from bundled resources or source tree + try: + from resources.system_tray import create_system_tray + except ImportError: + from distribution.windows.resources.system_tray import create_system_tray + + def _tray_shutdown(): + """Called when user clicks Exit in the tray menu.""" + if not stop_event.is_set(): + stop_event.set() + if not shutdown_requested.is_set(): + shutdown_requested.set() + + _system_tray = create_system_tray(port=port, shutdown_callback=_tray_shutdown) + _system_tray.start() + web_logger.info("Windows system tray icon initialized") + except Exception as e: + web_logger.warning(f"System tray not available: {e}") # Log the current authentication mode once at startup try: @@ -308,6 +329,14 @@ def run_web_server(): # Shutdown sequence web_logger.info("Shutdown signal received. Stopping Waitress server...") + + # Stop system tray icon + if _system_tray: + try: + _system_tray.stop() + except Exception: + pass + try: waitress_server.close() web_logger.info("Waitress server close() called.") diff --git a/requirements.txt b/requirements.txt index 35aec61a..74621146 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,4 +18,4 @@ urllib3>=2.6.0 # CVE-2025-66418, CVE-2025-66471: unbounded decompression DoS idna>=3.7 # CVE-2024-3651: encode() quadratic DoS Pillow>=10.3.0 # CVE-2024-28219: buffer overflow in _imagingcms.c -# pystray==0.19.5; sys_platform == 'win32' # For Windows system tray icon (disabled - needs more testing) +pystray==0.19.5; sys_platform == 'win32' # Windows system tray icon diff --git a/src/primary/utils/windows_integration.py b/src/primary/utils/windows_integration.py index 43e14203..7dcc759f 100644 --- a/src/primary/utils/windows_integration.py +++ b/src/primary/utils/windows_integration.py @@ -72,17 +72,33 @@ def integrate_windows_helpers(app=None): # Ensure paths are properly configured if app: # Basic template and static path detection for Windows - base_dir = Path(sys.executable).parent - template_dir = base_dir / "frontend" / "templates" - static_dir = base_dir / "frontend" / "static" + # PyInstaller 6.x: data files live under _MEIPASS (_internal/) + meipass = getattr(sys, '_MEIPASS', None) + base_dir = Path(meipass) if meipass else Path(sys.executable).parent - if template_dir.exists(): - logger.info(f"Setting Flask template folder to: {template_dir}") - app.template_folder = str(template_dir) + # Check _MEIPASS paths first, then exe_dir fallbacks + template_candidates = [ + base_dir / "frontend" / "templates", + base_dir / "templates", + Path(sys.executable).parent / "frontend" / "templates", + ] + static_candidates = [ + base_dir / "frontend" / "static", + base_dir / "static", + Path(sys.executable).parent / "frontend" / "static", + ] - if static_dir.exists(): - logger.info(f"Setting Flask static folder to: {static_dir}") - app.static_folder = str(static_dir) + for td in template_candidates: + if td.exists(): + logger.info(f"Setting Flask template folder to: {td}") + app.template_folder = str(td) + break + + for sd in static_candidates: + if sd.exists(): + logger.info(f"Setting Flask static folder to: {sd}") + app.static_folder = str(sd) + break else: # When running from source code, check if the helper is in the distribution directory project_root = Path(__file__).parent.parent.parent.parent diff --git a/src/primary/web_server.py b/src/primary/web_server.py index e9ef8461..628a92f1 100644 --- a/src/primary/web_server.py +++ b/src/primary/web_server.py @@ -67,17 +67,24 @@ log.setLevel(logging.DEBUG) # Change to DEBUG to see all Flask/Werkzeug logs # Configure template and static paths with proper PyInstaller support if getattr(sys, 'frozen', False): - # PyInstaller sets this attribute - use paths relative to the executable - base_path = os.path.dirname(sys.executable) - # Path candidates for MacOS app bundles and other PyInstaller formats + # PyInstaller 6.x puts data files under _internal/ (sys._MEIPASS). + # sys.executable points to the .exe itself; data files are NOT next to it. + exe_dir = os.path.dirname(sys.executable) + meipass = getattr(sys, '_MEIPASS', exe_dir) + print(f"PyInstaller mode - exe_dir: {exe_dir}, _MEIPASS: {meipass}") + + # Search candidates in priority order. + # _MEIPASS paths first (PyInstaller 6.x), then exe_dir fallbacks, + # then macOS .app bundle Resources paths. template_candidates = [ - os.path.join(base_path, 'templates'), # Direct templates folder - os.path.join(base_path, '..', 'Resources', 'frontend', 'templates'), # Mac app bundle Resources path - os.path.join(base_path, 'frontend', 'templates'), # Alternate structure - os.path.join(os.path.dirname(base_path), 'Resources', 'frontend', 'templates') # Mac app bundle with different path + os.path.join(meipass, 'frontend', 'templates'), # PyInstaller 6.x: _internal/frontend/templates + os.path.join(meipass, 'templates'), # PyInstaller 6.x: _internal/templates (duplicate datas entry) + os.path.join(exe_dir, 'frontend', 'templates'), # Legacy / manual copy beside .exe + os.path.join(exe_dir, 'templates'), # Legacy flat layout + os.path.join(exe_dir, '..', 'Resources', 'frontend', 'templates'), # macOS .app bundle + os.path.join(os.path.dirname(exe_dir), 'Resources', 'frontend', 'templates'), ] - - # Find the first existing templates directory + template_dir = None for candidate in template_candidates: candidate_path = os.path.abspath(candidate) @@ -90,16 +97,16 @@ if getattr(sys, 'frozen', False): break else: print(f"Warning: setup.html not found in {template_dir}") - - # Similar approach for static files + static_candidates = [ - os.path.join(base_path, 'static'), - os.path.join(base_path, '..', 'Resources', 'frontend', 'static'), - os.path.join(base_path, 'frontend', 'static'), - os.path.join(os.path.dirname(base_path), 'Resources', 'frontend', 'static') + os.path.join(meipass, 'frontend', 'static'), + os.path.join(meipass, 'static'), + os.path.join(exe_dir, 'frontend', 'static'), + os.path.join(exe_dir, 'static'), + os.path.join(exe_dir, '..', 'Resources', 'frontend', 'static'), + os.path.join(os.path.dirname(exe_dir), 'Resources', 'frontend', 'static'), ] - - # Find the first existing static directory + static_dir = None for candidate in static_candidates: candidate_path = os.path.abspath(candidate) @@ -107,16 +114,15 @@ if getattr(sys, 'frozen', False): static_dir = candidate_path print(f"Found valid static directory: {static_dir}") break - - # If no valid directories found, use defaults + if not template_dir: - template_dir = os.path.join(base_path, 'templates') + template_dir = os.path.join(meipass, 'frontend', 'templates') print(f"Warning: Using default template dir: {template_dir}") - + if not static_dir: - static_dir = os.path.join(base_path, 'static') + static_dir = os.path.join(meipass, 'frontend', 'static') print(f"Warning: Using default static dir: {static_dir}") - + print(f"PyInstaller mode - Using template dir: {template_dir}") print(f"PyInstaller mode - Using static dir: {static_dir}") print(f"Template dir exists: {os.path.exists(template_dir)}")