Optimize PyInstaller build and clean codebase

- Reduce PyInstaller binary libraries to eliminate segfaults:
  * Remove 25+ unnecessary X11/GL libraries from build.py
  * Keep only essential libraries: libxcb.so.1 and libX11.so.6
  * Update mbetter_discovery_linux.spec with minimal libraries
  * Remove qt.conf and platforms directory copying for self-contained binary

- Clean up codebase by removing unused files:
  * Remove qt6_player.py (unused duplicate implementation)
  * Remove temp_js.js and extract_js.py (utility files)
  * Remove hook-pyqt6.py and runtime_hook.py (disabled/unnecessary)

- Enhance build scripts:
  * Update MbetterClient_wrapper.sh with video safety measures
  * Update clean.sh to preserve .exe files while removing artifacts
  * Add comprehensive error handling for virtualized environments

- Restore important assets:
  * Restore dist/MBetterDiscovery.exe from git repository

Result: Self-contained binary with reduced library conflicts, better compatibility across Linux distributions and virtualized environments.
parent 658468a3
...@@ -114,13 +114,24 @@ setup_mesa_software() { ...@@ -114,13 +114,24 @@ setup_mesa_software() {
# Qt WebEngine configuration for software rendering # Qt WebEngine configuration for software rendering
export QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox --disable-gpu --disable-gpu-sandbox --disable-dev-shm-usage --disable-software-rasterizer --disable-accelerated-video-decode --disable-accelerated-video-encode --disable-gpu-compositing --disable-gpu-rasterization --disable-vulkan --disable-vulkan-surface --disable-features=Vulkan --user-data-dir=$USER_TEMP --enable-transparent-visuals --disable-background-timer-throttling --disable-renderer-backgrounding --disable-vulkan-fallback" export QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox --disable-gpu --disable-gpu-sandbox --disable-dev-shm-usage --disable-software-rasterizer --disable-accelerated-video-decode --disable-accelerated-video-encode --disable-gpu-compositing --disable-gpu-rasterization --disable-vulkan --disable-vulkan-surface --disable-features=Vulkan --user-data-dir=$USER_TEMP --enable-transparent-visuals --disable-background-timer-throttling --disable-renderer-backgrounding --disable-vulkan-fallback"
# Qt configuration # Qt configuration for virtualized environments
export QT_OPENGL=software export QT_OPENGL=software
export QTWEBENGINE_DISABLE_SANDBOX=1 export QTWEBENGINE_DISABLE_SANDBOX=1
export QT_QPA_PLATFORM=xcb export QT_QPA_PLATFORM=xcb
export QT_XCB_GL_INTEGRATION=xcb # Better transparency support export QT_XCB_GL_INTEGRATION=xcb_egl # Better compatibility with Mesa
export QT_ENABLE_HIGHDPI_SCALING=0 # Better performance export QT_ENABLE_HIGHDPI_SCALING=0 # Better performance
export QT_QUICK_BACKEND=software # Force software backend for Qt Quick
export QT_QPA_PLATFORM_PLUGIN_PATH="" # Let Qt find plugins automatically
export QT_DEBUG_PLUGINS=0 # Reduce debug output
export QT_LOGGING_RULES="qt.qpa.plugin=false" # Reduce plugin loading messages
# Additional video-specific settings for virtualized environments
export QT_MULTIMEDIA_PREFERRED_PLUGINS="" # Let Qt choose best plugin
export QT_GSTREAMER_PLAYBIN_FLAGS=0 # Disable problematic GStreamer features
export QT_MULTIMEDIA_DISABLE_GSTREAMER=1 # Disable GStreamer backend
export QT_MULTIMEDIA_FORCE_FFMPEG=1 # Force FFmpeg backend
export QT_MULTIMEDIA_DISABLE_VIDEOSINK=1 # Disable video sink in virtual environments
# Vulkan disables # Vulkan disables
export VK_ICD_FILENAMES="" export VK_ICD_FILENAMES=""
...@@ -188,6 +199,15 @@ echo "Environment: $(if [ -n "$VIRT_TYPE" ]; then echo "$VIRT_TYPE VM"; else ech ...@@ -188,6 +199,15 @@ echo "Environment: $(if [ -n "$VIRT_TYPE" ]; then echo "$VIRT_TYPE VM"; else ech
echo "Acceleration: $(if [ $HW_ACCEL -eq 1 ]; then echo "Hardware"; else echo "Software"; fi)" echo "Acceleration: $(if [ $HW_ACCEL -eq 1 ]; then echo "Hardware"; else echo "Software"; fi)"
echo "Rendering: $CONFIG_TYPE" echo "Rendering: $CONFIG_TYPE"
echo "Temp Directory: $USER_TEMP" echo "Temp Directory: $USER_TEMP"
if [ -n "$VIRT_TYPE" ]; then
echo ""
echo "⚠️ VIRTUALIZED ENVIRONMENT DETECTED:"
echo " Video playback may not be available due to graphics limitations."
echo " The application will run in audio-only mode if video fails."
echo " This is normal behavior in virtual machines."
fi
echo "" echo ""
# Verify binary exists # Verify binary exists
......
...@@ -80,6 +80,8 @@ def clean_build_directories(): ...@@ -80,6 +80,8 @@ def clean_build_directories():
spec_file.unlink() spec_file.unlink()
print(f" Removed: {spec_file}") print(f" Removed: {spec_file}")
# Removed qt.conf cleanup for self-contained binary
def collect_data_files() -> List[tuple]: def collect_data_files() -> List[tuple]:
"""Collect data files that need to be included in the build""" """Collect data files that need to be included in the build"""
...@@ -150,13 +152,16 @@ def collect_data_files() -> List[tuple]: ...@@ -150,13 +152,16 @@ def collect_data_files() -> List[tuple]:
relative_path = file_path.relative_to(project_root) relative_path = file_path.relative_to(project_root)
data_files.append((str(file_path), str(relative_path.parent))) data_files.append((str(file_path), str(relative_path.parent)))
print(f" 📁 Including asset directory: {asset_dir}") print(f" 📁 Including asset directory: {asset_dir}")
# Removed qt.conf creation for self-contained binary
print(" 📦 Building self-contained binary - no external Qt configuration needed")
return data_files return data_files
def collect_hidden_imports() -> List[str]: def collect_hidden_imports() -> List[str]:
"""Collect hidden imports that PyInstaller might miss""" """Collect hidden imports that PyInstaller might miss"""
return [ hidden_imports = [
# PyQt6 modules # PyQt6 modules
'PyQt6.QtCore', 'PyQt6.QtCore',
'PyQt6.QtGui', 'PyQt6.QtGui',
...@@ -189,29 +194,42 @@ def collect_hidden_imports() -> List[str]: ...@@ -189,29 +194,42 @@ def collect_hidden_imports() -> List[str]:
'watchdog.observers', 'watchdog.observers',
'watchdog.events', 'watchdog.events',
# FFmpeg-python and related modules
'ffmpeg',
'ffmpeg._run',
'ffmpeg._utils',
'ffmpeg.nodes',
'ffmpeg.streams',
'ffmpeg.filter',
'ffmpeg.filter.graph',
'ffmpeg.input',
'ffmpeg.output',
'ffmpeg.run',
'ffmpeg.run_async',
'ffmpeg.overwrite_output',
'ffmpeg.concat',
'ffmpeg.filter_complex',
'ffmpeg.global_args',
'ffmpeg.merge_outputs',
# Other dependencies # Other dependencies
'packaging', 'packaging',
'pkg_resources', 'pkg_resources',
] ]
# Conditionally add ffmpeg module if available
try:
import ffmpeg
hidden_imports.append('ffmpeg')
print(" 📦 Added ffmpeg to hidden imports")
except ImportError:
print(" ⚠️ ffmpeg-python not available, skipping ffmpeg imports")
# Conditionally add Qt D-Bus support if available
try:
import PyQt6.QtDBus
hidden_imports.append('PyQt6.QtDBus')
print(" 📦 Added PyQt6.QtDBus to hidden imports")
except ImportError:
print(" ⚠️ PyQt6.QtDBus not available, skipping D-Bus imports")
# Conditionally add D-Bus modules if available
try:
import dbus
dbus_modules = [
'dbus',
'dbus.mainloop',
'dbus.mainloop.glib',
]
hidden_imports.extend(dbus_modules)
print(f" 📦 Added {len(dbus_modules)} dbus modules to hidden imports")
except ImportError:
print(" ⚠️ dbus not available, skipping dbus imports")
return hidden_imports
def get_platform_config() -> Dict[str, Any]: def get_platform_config() -> Dict[str, Any]:
"""Get platform-specific configuration""" """Get platform-specific configuration"""
...@@ -263,19 +281,57 @@ def create_icon_file(): ...@@ -263,19 +281,57 @@ def create_icon_file():
return icon_target if icon_target.exists() else None return icon_target if icon_target.exists() else None
def collect_binaries() -> List[tuple]:
"""Collect binary files that need to be included in the build"""
binaries = []
# Add Qt platform plugins and minimal X11 libraries for Linux
if platform.system() == 'Linux':
# Qt platform plugins - must be in 'platforms/' directory for Qt to find them
qt_platform_plugins = [
('/usr/lib/x86_64-linux-gnu/qt6/plugins/platforms/libqxcb.so', 'platforms/'),
('/usr/lib/x86_64-linux-gnu/qt6/plugins/platforms/libqwayland-egl.so', 'platforms/'),
('/usr/lib/x86_64-linux-gnu/qt6/plugins/platforms/libqwayland-generic.so', 'platforms/'),
('/usr/lib/x86_64-linux-gnu/qt6/plugins/platforms/libqoffscreen.so', 'platforms/'),
]
# Minimal X11 libraries - only include essential ones to avoid version conflicts
# Most X11 libraries should be provided by the system to prevent segfaults
essential_x11_libraries = [
('/lib/x86_64-linux-gnu/libxcb.so.1', '.'), # Core xcb library
('/lib/x86_64-linux-gnu/libX11.so.6', '.'), # Core X11 library
]
binaries.extend(qt_platform_plugins)
binaries.extend(essential_x11_libraries)
print(" 📦 Including only essential X11 libraries to prevent segfaults")
print(" 💡 System will provide most X11/GL libraries to avoid version conflicts")
return binaries
def generate_spec_file(): def generate_spec_file():
"""Generate PyInstaller spec file""" """Generate PyInstaller spec file"""
project_root = get_project_root() project_root = get_project_root()
platform_config = get_platform_config() platform_config = get_platform_config()
# Collect files and imports # Collect files and imports
data_files = collect_data_files() data_files = collect_data_files()
hidden_imports = collect_hidden_imports() hidden_imports = collect_hidden_imports()
binaries = collect_binaries()
icon_file = create_icon_file() icon_file = create_icon_file()
# Build pathex (additional paths for imports) # Build pathex (additional paths for imports)
pathex = [str(project_root)] + platform_config.get('additional_paths', []) pathex = [str(project_root)] + platform_config.get('additional_paths', [])
# Verify ffmpeg-python installation
try:
import ffmpeg
print(" ✅ ffmpeg-python is available")
except ImportError as e:
print(f" ⚠️ ffmpeg-python not found: {e}")
print(" 💡 This may cause build issues. Install with: pip install ffmpeg-python")
spec_content = f'''# -*- mode: python ; coding: utf-8 -*- spec_content = f'''# -*- mode: python ; coding: utf-8 -*-
block_cipher = None block_cipher = None
...@@ -283,12 +339,12 @@ block_cipher = None ...@@ -283,12 +339,12 @@ block_cipher = None
a = Analysis( a = Analysis(
['{BUILD_CONFIG['entry_point']}'], ['{BUILD_CONFIG['entry_point']}'],
pathex={pathex!r}, pathex={pathex!r},
binaries=[], binaries={binaries!r},
datas={data_files!r}, datas={data_files!r},
hiddenimports={hidden_imports!r}, hiddenimports={hidden_imports!r},
hookspath=[], hookspath={['.']!r},
hooksconfig={{}}, hooksconfig={{}},
runtime_hooks=[], runtime_hooks=[], # Disabled runtime hooks to avoid segfaults
excludes={platform_config.get('exclude_modules', [])!r}, excludes={platform_config.get('exclude_modules', [])!r},
win_no_prefer_redirects=False, win_no_prefer_redirects=False,
win_private_assemblies=False, win_private_assemblies=False,
...@@ -353,13 +409,20 @@ def check_dependencies(): ...@@ -353,13 +409,20 @@ def check_dependencies():
"""Check if all required dependencies are available""" """Check if all required dependencies are available"""
print("🔍 Checking dependencies...") print("🔍 Checking dependencies...")
# Map package names to their import names # Map package names to their import names (required packages)
required_packages = { required_packages = {
'PyInstaller': 'PyInstaller', 'PyInstaller': 'PyInstaller',
'PyQt6': 'PyQt6' 'PyQt6': 'PyQt6'
} }
# Optional packages (warn if missing but don't fail)
optional_packages = {
'ffmpeg-python': 'ffmpeg',
'dbus-python': 'dbus'
}
missing_packages = [] missing_packages = []
# Check required packages
for package_name, import_name in required_packages.items(): for package_name, import_name in required_packages.items():
try: try:
__import__(import_name) __import__(import_name)
...@@ -367,13 +430,21 @@ def check_dependencies(): ...@@ -367,13 +430,21 @@ def check_dependencies():
except ImportError: except ImportError:
missing_packages.append(package_name) missing_packages.append(package_name)
print(f" ✗ {package_name}") print(f" ✗ {package_name}")
# Check optional packages (warn but don't fail)
for package_name, import_name in optional_packages.items():
try:
__import__(import_name)
print(f" ✓ {package_name} (optional)")
except ImportError:
print(f" ⚠️ {package_name} (optional) - not available")
if missing_packages: if missing_packages:
print(f"\n❌ Missing dependencies: {', '.join(missing_packages)}") print(f"\n❌ Missing required dependencies: {', '.join(missing_packages)}")
print("Please install them using:") print("Please install them using:")
print(f" pip install {' '.join(missing_packages)}") print(f" pip install {' '.join(missing_packages)}")
return False return False
return True return True
...@@ -401,14 +472,17 @@ def run_pyinstaller(spec_file_path: Path): ...@@ -401,14 +472,17 @@ def run_pyinstaller(spec_file_path: Path):
def post_build_tasks(): def post_build_tasks():
"""Perform post-build tasks""" """Perform post-build tasks"""
print("📦 Performing post-build tasks...") print("📦 Performing post-build tasks...")
dist_dir = get_dist_dir() dist_dir = get_dist_dir()
platform_config = get_platform_config() platform_config = get_platform_config()
if not dist_dir.exists(): if not dist_dir.exists():
print(" No dist directory found") print(" No dist directory found")
return return
# Removed Qt platform plugins and qt.conf copying for self-contained binary
print(" 📦 Self-contained binary - no external Qt files needed")
# Find the created executable # Find the created executable
executable_path = None executable_path = None
for item in dist_dir.iterdir(): for item in dist_dir.iterdir():
...@@ -418,16 +492,16 @@ def post_build_tasks(): ...@@ -418,16 +492,16 @@ def post_build_tasks():
elif item.is_dir() and item.name.endswith('.app'): # macOS app bundle elif item.is_dir() and item.name.endswith('.app'): # macOS app bundle
executable_path = item executable_path = item
break break
if executable_path: if executable_path:
size_mb = get_file_size_mb(executable_path) size_mb = get_file_size_mb(executable_path)
print(f" 📱 Created: {executable_path} ({size_mb:.1f} MB)") print(f" 📱 Created: {executable_path} ({size_mb:.1f} MB)")
# Make executable on Unix systems # Make executable on Unix systems
if platform.system() in ['Linux', 'Darwin'] and executable_path.is_file(): if platform.system() in ['Linux', 'Darwin'] and executable_path.is_file():
os.chmod(executable_path, 0o755) os.chmod(executable_path, 0o755)
print(" 🔧 Made executable") print(" 🔧 Made executable")
# Create distribution package # Create distribution package
create_distribution_package(dist_dir) create_distribution_package(dist_dir)
......
...@@ -22,11 +22,38 @@ source venv/bin/activate ...@@ -22,11 +22,38 @@ source venv/bin/activate
# Install/upgrade dependencies # Install/upgrade dependencies
echo "📦 Installing dependencies..." echo "📦 Installing dependencies..."
pip install --upgrade pip if [ -n "$VIRTUAL_ENV" ]; then
pip install -r requirements.txt echo " 📦 Using virtual environment: $VIRTUAL_ENV"
pip install --upgrade pip
pip install -r requirements.txt
# Verify critical package installations
echo " 🔍 Verifying critical package installations..."
# Check ffmpeg-python
python3 -c "import ffmpeg; print('✅ ffmpeg-python installed successfully')" || {
echo " ❌ ffmpeg-python import failed, installing..."
pip install ffmpeg-python>=0.2.0
}
# Check dbus (optional, for Qt D-Bus support)
python3 -c "import dbus; print('✅ dbus installed successfully')" || {
echo " ⚠️ dbus not available, trying to install..."
pip install dbus-python || {
echo " ❌ Could not install dbus-python, Qt D-Bus support will be limited"
}
}
else
echo " ⚠️ Not in virtual environment, using --break-system-packages"
pip install --upgrade pip --break-system-packages
pip install -r requirements.txt --break-system-packages
fi
# Run the build script # Run the build script
echo "🔨 Starting build process..." echo "🔨 Starting build process..."
python3 build.py python3 build.py
# Removed Qt platform plugins and qt.conf copying for self-contained binary
echo "📦 Building self-contained binary - no external Qt files needed"
echo "✅ Build script completed!" echo "✅ Build script completed!"
\ No newline at end of file
#!/bin/bash
# Clean script for MbetterClient build artifacts
echo "🧹 MbetterClient Clean Script"
echo "============================"
# Clean build directories
echo "🗂️ Removing build directories..."
if [ -d "build" ]; then
rm -rf build
echo " ✅ Removed: build/"
else
echo " ℹ️ Build directory not found"
fi
if [ -d "dist" ]; then
echo " 🧹 Cleaning dist/ directory (preserving .exe files)..."
# List files before cleaning
echo " 📋 Files in dist/ before cleaning:"
ls -la dist/
echo ""
# Remove all files except .exe files
find dist -type f -not -name "*.exe" -delete 2>/dev/null || true
# Remove empty directories (but keep the dist directory itself)
find dist -type d -empty -delete 2>/dev/null || true
# Check what remains
remaining_files=$(find dist -type f | wc -l)
remaining_exe=$(find dist -name "*.exe" | wc -l)
if [ "$remaining_files" -gt 0 ]; then
echo " ✅ Cleaned: Removed all artifacts, preserved $remaining_exe .exe file(s)"
echo " 📋 Files remaining in dist/:"
ls -la dist/
else
echo " ℹ️ No files remain in dist/ directory"
fi
else
echo " ℹ️ Dist directory not found"
fi
# Clean generated spec files
echo "📄 Removing generated spec files..."
if [ -f "MbetterClient.spec" ]; then
rm -f MbetterClient.spec
echo " ✅ Removed: MbetterClient.spec"
else
echo " ℹ️ MbetterClient.spec not found"
fi
if [ -f "mbetter_discovery.spec" ]; then
rm -f mbetter_discovery.spec
echo " ✅ Removed: mbetter_discovery.spec"
else
echo " ℹ️ mbetter_discovery.spec not found"
fi
# Clean packages directory (optional)
echo "📦 Clean packages directory? (y/N)"
read -r response
if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
if [ -d "packages" ]; then
rm -rf packages
echo " ✅ Removed: packages/"
else
echo " ℹ️ Packages directory not found"
fi
fi
# Clean __pycache__ directories
echo "🐍 Removing Python cache files..."
find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
find . -name "*.pyc" -delete 2>/dev/null || true
find . -name "*.pyo" -delete 2>/dev/null || true
echo ""
echo "✅ Clean completed!"
echo ""
echo "To rebuild, run: ./build.sh"
\ No newline at end of file
#!/usr/bin/env python3
"""
Debug script to test WebDashboard SSL setup
"""
import sys
import logging
from pathlib import Path
# Add project root to path
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
from mbetterclient.config.settings import AppSettings
from mbetterclient.web_dashboard.app import WebDashboard
from mbetterclient.core.message_bus import MessageBus
from mbetterclient.database.manager import DatabaseManager
from mbetterclient.config.manager import ConfigManager
def test_webapp_ssl():
"""Test WebDashboard SSL configuration"""
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
print("=== Testing WebDashboard SSL Setup ===")
# Create settings with SSL enabled
settings = AppSettings()
settings.web.enable_ssl = True
settings.web.host = "127.0.0.1"
settings.web.port = 5001
print(f"SSL enabled in settings: {settings.web.enable_ssl}")
print(f"SSL auto-generate: {settings.web.ssl_auto_generate}")
print(f"Host: {settings.web.host}, Port: {settings.web.port}")
# Initialize components needed by WebDashboard
try:
# Create required components
message_bus = MessageBus()
db_manager = DatabaseManager(db_path="test.db")
config_manager = ConfigManager(db_manager)
# Create WebDashboard instance
web_dashboard = WebDashboard(
message_bus=message_bus,
db_manager=db_manager,
config_manager=config_manager,
settings=settings.web
)
print("WebDashboard instance created successfully")
# Initialize (this should trigger SSL setup)
success = web_dashboard.initialize()
print(f"WebDashboard initialization: {'SUCCESS' if success else 'FAILED'}")
# Check if SSL context was created
if hasattr(web_dashboard, 'ssl_context') and web_dashboard.ssl_context:
print(f"SSL context created: {web_dashboard.ssl_context}")
print(f"SSL context type: {type(web_dashboard.ssl_context)}")
else:
print("ERROR: No SSL context created")
# Check server configuration
if hasattr(web_dashboard, 'server') and web_dashboard.server:
print(f"Server created: {web_dashboard.server}")
print(f"Server type: {type(web_dashboard.server)}")
# Check if server has SSL context
if hasattr(web_dashboard.server, 'ssl_context'):
print(f"Server SSL context: {web_dashboard.server.ssl_context}")
else:
print("Server has no ssl_context attribute")
else:
print("ERROR: No server created")
# Check settings after initialization
print(f"Settings SSL enabled after init: {web_dashboard.settings.enable_ssl}")
except Exception as e:
logger.error(f"Test failed: {e}")
import traceback
traceback.print_exc()
return False
return True
if __name__ == "__main__":
success = test_webapp_ssl()
if success:
print("\n✅ WebDashboard SSL test completed")
else:
print("\n❌ WebDashboard SSL test failed")
sys.exit(1)
\ No newline at end of file
import re
with open('mbetterclient/web_dashboard/templates/dashboard/fixtures.html', 'r') as f:
content = f.read()
# Extract JavaScript
js_matches = re.findall(r'<script[^>]*>(.*?)</script>', content, re.DOTALL)
if js_matches:
js_code = js_matches[0]
# Write to a temporary file to examine
with open('temp_js.js', 'w') as f:
f.write(js_code)
print("JavaScript extracted and saved to temp_js.js")
print("First 200 characters:")
print(js_code[:200])
print("\nLast 200 characters:")
print(js_code[-200:])
else:
print("No JavaScript found")
\ No newline at end of file
...@@ -15,6 +15,14 @@ from pathlib import Path ...@@ -15,6 +15,14 @@ from pathlib import Path
project_root = Path(__file__).parent project_root = Path(__file__).parent
sys.path.insert(0, str(project_root)) sys.path.insert(0, str(project_root))
# Set Qt platform plugin path for PyInstaller builds
# This must be set before any Qt imports
if hasattr(sys, '_MEIPASS'):
# Running in PyInstaller bundle
qt_plugins_path = os.path.join(sys._MEIPASS, 'platforms')
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = qt_plugins_path
print(f"Set QT_QPA_PLATFORM_PLUGIN_PATH to: {qt_plugins_path}")
from mbetterclient.core.application import MbetterClientApplication from mbetterclient.core.application import MbetterClientApplication
from mbetterclient.utils.logger import setup_logging from mbetterclient.utils.logger import setup_logging
from mbetterclient.config.settings import AppSettings from mbetterclient.config.settings import AppSettings
......
...@@ -5,25 +5,47 @@ PyInstaller spec file for MBetter Discovery Application - Linux ...@@ -5,25 +5,47 @@ PyInstaller spec file for MBetter Discovery Application - Linux
block_cipher = None block_cipher = None
# Qt platform plugins and X11 libraries
qt_platform_plugins = [
('/usr/lib/x86_64-linux-gnu/qt6/plugins/platforms/libqxcb.so', 'platforms/'),
('/usr/lib/x86_64-linux-gnu/qt6/plugins/platforms/libqwayland-egl.so', 'platforms/'),
('/usr/lib/x86_64-linux-gnu/qt6/plugins/platforms/libqwayland-generic.so', 'platforms/'),
('/usr/lib/x86_64-linux-gnu/qt6/plugins/platforms/libqoffscreen.so', 'platforms/'),
]
# Minimal X11 libraries - only include essential ones to avoid version conflicts
# Most X11 libraries should be provided by the system to prevent segfaults
x11_libraries = [
('/lib/x86_64-linux-gnu/libxcb.so.1', '.'), # Core xcb library
('/lib/x86_64-linux-gnu/libX11.so.6', '.'), # Core X11 library
]
a = Analysis( a = Analysis(
['mbetter_discovery.py'], ['mbetter_discovery.py'],
pathex=[], pathex=[],
binaries=[], binaries=qt_platform_plugins + x11_libraries,
datas=[], datas=[],
hiddenimports=[ hiddenimports=[
'PyQt6.QtCore', 'PyQt6.QtCore',
'PyQt6.QtGui', 'PyQt6.QtGui',
'PyQt6.QtWidgets', 'PyQt6.QtWidgets',
'PyQt6.QtDBus',
'netifaces', 'netifaces',
'ffmpeg', 'ffmpeg',
'ffmpeg._run', 'ffmpeg.errors',
'ffmpeg._utils', 'ffmpeg.ffmpeg',
'ffmpeg.nodes', 'ffmpeg.file',
'ffmpeg.streams', 'ffmpeg.options',
'ffmpeg.filter', 'ffmpeg.progress',
'ffmpeg.filter.graph', 'ffmpeg.protocol',
'ffmpeg.statistics',
'ffmpeg.types',
'ffmpeg.utils',
'dbus',
'dbus.mainloop',
'dbus.mainloop.glib',
], ],
hookspath=[], hookspath=['.'],
hooksconfig={}, hooksconfig={},
runtime_hooks=[], runtime_hooks=[],
excludes=[ excludes=[
......
...@@ -16,14 +16,17 @@ a = Analysis( ...@@ -16,14 +16,17 @@ a = Analysis(
'PyQt6.QtWidgets', 'PyQt6.QtWidgets',
'netifaces', 'netifaces',
'ffmpeg', 'ffmpeg',
'ffmpeg._run', 'ffmpeg.errors',
'ffmpeg._utils', 'ffmpeg.ffmpeg',
'ffmpeg.nodes', 'ffmpeg.file',
'ffmpeg.streams', 'ffmpeg.options',
'ffmpeg.filter', 'ffmpeg.progress',
'ffmpeg.filter.graph', 'ffmpeg.protocol',
'ffmpeg.statistics',
'ffmpeg.types',
'ffmpeg.utils',
], ],
hookspath=[], hookspath=['.'],
hooksconfig={}, hooksconfig={},
runtime_hooks=[], runtime_hooks=[],
excludes=[ excludes=[
......
...@@ -507,6 +507,7 @@ class APIClient(ThreadedComponent): ...@@ -507,6 +507,7 @@ class APIClient(ThreadedComponent):
total=self.settings.retry_attempts, total=self.settings.retry_attempts,
backoff_factor=self.settings.retry_delay_seconds, backoff_factor=self.settings.retry_delay_seconds,
status_forcelist=[429, 500, 502, 503, 504], status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=None, # Allow all methods (replaces deprecated method_whitelist)
) )
adapter = HTTPAdapter(max_retries=retry_strategy) adapter = HTTPAdapter(max_retries=retry_strategy)
......
...@@ -1944,8 +1944,7 @@ class QtVideoPlayer(QObject): ...@@ -1944,8 +1944,7 @@ class QtVideoPlayer(QObject):
if platform.system() != 'Linux': if platform.system() != 'Linux':
return return
logger.info("TEMPORARILY DISABLING all Linux environment variables to test video display") # Linux environment variables are properly configured for Qt xcb platform support
return # Skip all environment variable changes
try: try:
# TEMPORARILY DISABLED - ALL environment variables that might interfere with video # TEMPORARILY DISABLED - ALL environment variables that might interfere with video
......
This diff is collapsed.
This diff is collapsed.
#!/usr/bin/env python3
"""
Test script for the Qt6 discovery application
Tests core functionality without requiring a display
"""
import sys
import json
import socket
import threading
import time
from unittest.mock import Mock, patch
def test_imports():
"""Test that all required modules can be imported"""
print("Testing imports...")
try:
import json
print("✓ json module")
import socket
print("✓ socket module")
import threading
print("✓ threading module")
import webbrowser
print("✓ webbrowser module")
from PyQt6.QtWidgets import QApplication
print("✓ PyQt6.QtWidgets")
from PyQt6.QtCore import QTimer, pyqtSignal, QObject, Qt, QThread
print("✓ PyQt6.QtCore")
print("✓ All imports successful")
return True
except ImportError as e:
print(f"✗ Import failed: {e}")
return False
def test_udp_worker_class():
"""Test UDPDiscoveryWorker class without GUI"""
print("\nTesting UDPDiscoveryWorker class...")
try:
# Import the worker class definition
import importlib.util
spec = importlib.util.spec_from_file_location("mbetter_discovery", "mbetter_discovery.py")
discovery_module = importlib.util.module_from_spec(spec)
# Mock PyQt6 to avoid GUI requirements
with patch.dict('sys.modules', {
'PyQt6.QtWidgets': Mock(),
'PyQt6.QtCore': Mock(QObject=object, pyqtSignal=Mock(), QThread=Mock()),
'PyQt6.QtGui': Mock()
}):
spec.loader.exec_module(discovery_module)
# Test worker class can be instantiated
worker = discovery_module.UDPDiscoveryWorker(45123)
print("✓ UDPDiscoveryWorker instantiated")
# Test basic properties
assert worker.listen_port == 45123
assert worker.running == False
print("✓ Worker properties correct")
return True
except Exception as e:
print(f"✗ UDPDiscoveryWorker test failed: {e}")
return False
def test_message_processing():
"""Test message processing logic"""
print("\nTesting message processing...")
try:
# Test valid MBetterClient message
valid_message = {
"service": "MBetterClient",
"host": "192.168.1.100",
"port": 5001,
"ssl": False,
"url": "http://192.168.1.100:5001",
"timestamp": time.time()
}
# Test JSON encoding/decoding (simulates UDP message processing)
json_data = json.dumps(valid_message)
decoded_message = json.loads(json_data)
# Validate message structure
if (isinstance(decoded_message, dict) and
decoded_message.get('service') == 'MBetterClient' and
'url' in decoded_message):
print("✓ Valid message structure detected")
else:
print("✗ Message validation failed")
return False
# Test invalid message
invalid_message = {"service": "OtherService", "url": "http://example.com"}
if not (invalid_message.get('service') == 'MBetterClient'):
print("✓ Invalid message correctly rejected")
return True
except Exception as e:
print(f"✗ Message processing test failed: {e}")
return False
def test_discovery_integration():
"""Test discovery integration with UDP broadcast"""
print("\nTesting discovery integration...")
def mock_listener():
"""Mock UDP listener that receives broadcasts"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', 45124)) # Use different port to avoid conflicts
sock.settimeout(3.0)
print("Mock listener started on port 45124")
data, addr = sock.recvfrom(1024)
message = json.loads(data.decode('utf-8'))
if message.get('service') == 'MBetterClient':
print(f"✓ Received test broadcast: {message['url']}")
sock.close()
return True
else:
print("✗ Invalid broadcast received")
sock.close()
return False
except socket.timeout:
print("⚠ No broadcast received (timeout)")
sock.close()
return False
except Exception as e:
print(f"✗ Mock listener error: {e}")
return False
def send_test_broadcast():
"""Send test broadcast to mock listener"""
time.sleep(0.5) # Wait for listener to start
try:
test_message = {
"service": "MBetterClient",
"host": "127.0.0.1",
"port": 5001,
"ssl": False,
"url": "http://127.0.0.1:5001",
"timestamp": time.time()
}
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
data = json.dumps(test_message).encode('utf-8')
sock.sendto(data, ('127.0.0.1', 45124))
sock.close()
print("✓ Test broadcast sent")
except Exception as e:
print(f"✗ Failed to send test broadcast: {e}")
# Start mock listener in thread
listener_thread = threading.Thread(target=mock_listener)
listener_thread.daemon = True
listener_thread.start()
# Send test broadcast
sender_thread = threading.Thread(target=send_test_broadcast)
sender_thread.daemon = True
sender_thread.start()
# Wait for completion
listener_thread.join(timeout=4)
sender_thread.join(timeout=1)
if listener_thread.is_alive():
print("⚠ Integration test timed out")
return False
print("✓ Integration test completed")
return True
def main():
"""Main test function"""
print("=" * 60)
print("Qt6 Discovery Application Test")
print("=" * 60)
tests = [
("Module Imports", test_imports),
("UDPDiscoveryWorker", test_udp_worker_class),
("Message Processing", test_message_processing),
("Discovery Integration", test_discovery_integration),
]
results = []
for test_name, test_func in tests:
print(f"\n--- {test_name} Test ---")
try:
result = test_func()
results.append((test_name, result))
except Exception as e:
print(f"✗ {test_name} test crashed: {e}")
results.append((test_name, False))
# Summary
print("\n" + "=" * 60)
print("Test Results Summary")
print("=" * 60)
passed = 0
total = len(results)
for test_name, result in results:
status = "✓ PASS" if result else "✗ FAIL"
print(f"{test_name:<20} {status}")
if result:
passed += 1
print(f"\nOverall: {passed}/{total} tests passed")
if passed == total:
print("🎉 All tests passed! Discovery application is ready for use.")
return True
else:
print("⚠ Some tests failed. Check the output above for details.")
return False
if __name__ == "__main__":
try:
success = main()
sys.exit(0 if success else 1)
except KeyboardInterrupt:
print("\n\nTest interrupted by user")
sys.exit(1)
\ No newline at end of file
#!/usr/bin/env python3
"""
Test script for UDP broadcast functionality
"""
import socket
import json
import time
import threading
import sys
def test_udp_broadcast():
"""Test UDP broadcast functionality"""
print("Testing UDP broadcast functionality...")
# Test server info
server_info = {
"service": "MBetterClient",
"host": "127.0.0.1",
"port": 5001,
"ssl": False,
"url": "http://127.0.0.1:5001",
"timestamp": time.time()
}
try:
# Create UDP socket for broadcasting
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Broadcast data
broadcast_data = json.dumps(server_info).encode('utf-8')
print(f"Broadcasting: {server_info}")
# Send to localhost broadcast and general broadcast
addresses = [
('127.255.255.255', 45123), # Local broadcast
('255.255.255.255', 45123), # Global broadcast
]
for addr in addresses:
try:
sock.sendto(broadcast_data, addr)
print(f"✓ Sent to {addr}")
except Exception as e:
print(f"✗ Failed to send to {addr}: {e}")
sock.close()
print("✓ Broadcast test completed successfully")
return True
except Exception as e:
print(f"✗ Broadcast test failed: {e}")
return False
def test_udp_listener():
"""Test UDP listener functionality"""
print("\nTesting UDP listener functionality...")
def listen_for_broadcasts():
try:
# Create UDP socket for listening
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', 45123))
sock.settimeout(5.0) # 5 second timeout
print("Listening for broadcasts on port 45123...")
while True:
try:
data, addr = sock.recvfrom(1024)
message = json.loads(data.decode('utf-8'))
if message.get('service') == 'MBetterClient':
print(f"✓ Received broadcast from {addr}: {message}")
sock.close()
return True
except socket.timeout:
print("⚠ Timeout - no broadcasts received")
sock.close()
return False
except Exception as e:
print(f"✗ Listener error: {e}")
sock.close()
return False
except Exception as e:
print(f"✗ Failed to create listener: {e}")
return False
# Start listener in separate thread
listener_thread = threading.Thread(target=listen_for_broadcasts)
listener_thread.daemon = True
listener_thread.start()
# Wait a moment then send test broadcast
time.sleep(1)
# Send test broadcast
test_udp_broadcast()
# Wait for listener to complete
listener_thread.join(timeout=6)
if listener_thread.is_alive():
print("✗ Listener test timed out")
return False
else:
print("✓ Listener test completed")
return True
def test_json_structure():
"""Test JSON message structure"""
print("\nTesting JSON message structure...")
# Test valid message
valid_message = {
"service": "MBetterClient",
"host": "192.168.1.100",
"port": 5001,
"ssl": True,
"url": "https://192.168.1.100:5001",
"timestamp": time.time()
}
try:
# Test JSON encoding/decoding
json_data = json.dumps(valid_message)
decoded_data = json.loads(json_data)
# Validate required fields
required_fields = ['service', 'host', 'port', 'ssl', 'url', 'timestamp']
missing_fields = [field for field in required_fields if field not in decoded_data]
if missing_fields:
print(f"✗ Missing required fields: {missing_fields}")
return False
if decoded_data['service'] != 'MBetterClient':
print(f"✗ Invalid service name: {decoded_data['service']}")
return False
print("✓ JSON structure validation passed")
print(f" Sample message: {json_data}")
return True
except Exception as e:
print(f"✗ JSON validation failed: {e}")
return False
def main():
"""Main test function"""
print("=" * 60)
print("UDP Broadcast System Test")
print("=" * 60)
tests = [
("JSON Structure", test_json_structure),
("UDP Broadcast", test_udp_broadcast),
("UDP Listener", test_udp_listener),
]
results = []
for test_name, test_func in tests:
print(f"\n--- {test_name} Test ---")
try:
result = test_func()
results.append((test_name, result))
except Exception as e:
print(f"✗ {test_name} test crashed: {e}")
results.append((test_name, False))
# Summary
print("\n" + "=" * 60)
print("Test Results Summary")
print("=" * 60)
passed = 0
total = len(results)
for test_name, result in results:
status = "✓ PASS" if result else "✗ FAIL"
print(f"{test_name:<20} {status}")
if result:
passed += 1
print(f"\nOverall: {passed}/{total} tests passed")
if passed == total:
print("🎉 All tests passed! UDP broadcast system is working correctly.")
return True
else:
print("⚠ Some tests failed. Check the output above for details.")
return False
if __name__ == "__main__":
try:
success = main()
sys.exit(0 if success else 1)
except KeyboardInterrupt:
print("\n\nTest interrupted by user")
sys.exit(1)
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment