Update

parent 4ae8c528
No preview for this file type
...@@ -88,13 +88,15 @@ def collect_data_files() -> List[tuple]: ...@@ -88,13 +88,15 @@ def collect_data_files() -> List[tuple]:
project_root = get_project_root() project_root = get_project_root()
data_files = [] data_files = []
# Include assets directory # Include assets directory - ensure it's at the root level in the bundle
assets_dir = project_root / 'assets' assets_dir = project_root / 'assets'
if assets_dir.exists(): if assets_dir.exists():
for file_path in assets_dir.rglob('*'): for file_path in assets_dir.rglob('*'):
if file_path.is_file(): if file_path.is_file():
relative_path = file_path.relative_to(project_root) # For assets, we want them at the root level, not nested
data_files.append((str(file_path), str(relative_path.parent))) # So instead of relative_path.parent, we use just 'assets'
data_files.append((str(file_path), 'assets'))
print(f" 📁 Including asset file: {file_path.name} ({file_path.stat().st_size} bytes) -> assets/")
# Include web dashboard templates and static files # Include web dashboard templates and static files
web_templates = project_root / 'mbetterclient' / 'web_dashboard' / 'templates' web_templates = project_root / 'mbetterclient' / 'web_dashboard' / 'templates'
...@@ -153,8 +155,37 @@ def collect_data_files() -> List[tuple]: ...@@ -153,8 +155,37 @@ def collect_data_files() -> List[tuple]:
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 # Include Qt WebEngine data files and resources
print(" 📦 Building self-contained binary - no external Qt configuration needed") webengine_data_dirs = [
'/usr/share/qt6/resources',
'/usr/share/qt6/translations/qtwebengine_locales',
'/usr/lib/x86_64-linux-gnu/qt6/libexec',
]
for data_dir in webengine_data_dirs:
if os.path.exists(data_dir):
for root, dirs, files in os.walk(data_dir):
for file in files:
full_path = os.path.join(root, file)
rel_path = os.path.relpath(root, '/usr/share/qt6') if 'share' in data_dir else os.path.relpath(root, '/usr/lib/x86_64-linux-gnu/qt6')
data_files.append((full_path, rel_path))
print(f" 📁 Including Qt WebEngine data: {data_dir}")
# Create qt.conf to ensure Qt uses bundled libraries
qt_conf_content = """[Paths]
Prefix = .
Libraries = .
Plugins = plugins
"""
qt_conf_path = project_root / 'qt.conf'
with open(qt_conf_path, 'w') as f:
f.write(qt_conf_content)
# Include qt.conf in the build
data_files.append((str(qt_conf_path), '.'))
print(" 📦 Created qt.conf for self-contained Qt configuration")
print(" 📦 Building self-contained binary with all Qt libraries included")
return data_files return data_files
...@@ -202,10 +233,7 @@ def collect_hidden_imports() -> List[str]: ...@@ -202,10 +233,7 @@ def collect_hidden_imports() -> List[str]:
'barcode', 'barcode',
'barcode.codex', 'barcode.codex',
'barcode.ean', 'barcode.ean',
'barcode.isbn', 'barcode.isxn',
'barcode.code39',
'barcode.code128',
'barcode.pzn',
] ]
# Conditionally add ffmpeg module if available # Conditionally add ffmpeg module if available
...@@ -224,6 +252,14 @@ def collect_hidden_imports() -> List[str]: ...@@ -224,6 +252,14 @@ def collect_hidden_imports() -> List[str]:
except ImportError: except ImportError:
print(" ⚠️ PyQt6.QtDBus not available, skipping D-Bus imports") print(" ⚠️ PyQt6.QtDBus not available, skipping D-Bus imports")
# Add barcode submodules conditionally
try:
import barcode.isbn
hidden_imports.extend(['barcode.isbn', 'barcode.code39', 'barcode.code128', 'barcode.pzn'])
print(" 📦 Added barcode submodules to hidden imports")
except ImportError:
print(" ⚠️ Some barcode submodules not available, skipping")
# Conditionally add D-Bus modules if available # Conditionally add D-Bus modules if available
try: try:
import dbus import dbus
...@@ -304,18 +340,50 @@ def collect_binaries() -> List[tuple]: ...@@ -304,18 +340,50 @@ def collect_binaries() -> List[tuple]:
('/usr/lib/x86_64-linux-gnu/qt6/plugins/platforms/libqoffscreen.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 # Essential Qt6 libraries that need to be bundled - use venv libraries instead of system ones
# Most X11 libraries should be provided by the system to prevent segfaults qt6_libraries = [
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6Core.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6Gui.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6Widgets.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6Multimedia.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6MultimediaWidgets.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6WebEngineCore.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6WebEngineWidgets.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6WebChannel.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6Network.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6DBus.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6OpenGL.so.6', '.'),
]
# Minimal X11 libraries - include more to ensure compatibility
essential_x11_libraries = [ essential_x11_libraries = [
('/lib/x86_64-linux-gnu/libxcb.so.1', '.'), # Core xcb library ('/lib/x86_64-linux-gnu/libxcb.so.1', '.'), # Core xcb library
('/lib/x86_64-linux-gnu/libX11.so.6', '.'), # Core X11 library ('/lib/x86_64-linux-gnu/libX11.so.6', '.'), # Core X11 library
('/lib/x86_64-linux-gnu/libX11-xcb.so.1', '.'), # X11-xcb bridge
('/lib/x86_64-linux-gnu/libxcb-shm.so.0', '.'), # Shared memory
('/lib/x86_64-linux-gnu/libxcb-render.so.0', '.'), # Render extension
('/lib/x86_64-linux-gnu/libxcb-image.so.0', '.'), # Image utilities
('/lib/x86_64-linux-gnu/libxcb-keysyms.so.1', '.'), # Key symbols
('/lib/x86_64-linux-gnu/libxcb-randr.so.0', '.'), # RandR extension
('/lib/x86_64-linux-gnu/libxcb-xfixes.so.0', '.'), # XFixes extension
]
# GStreamer libraries for multimedia support
gstreamer_libraries = [
('/usr/lib/x86_64-linux-gnu/libgstreamer-1.0.so.0', '.'),
('/usr/lib/x86_64-linux-gnu/libgstbase-1.0.so.0', '.'),
('/usr/lib/x86_64-linux-gnu/libgstvideo-1.0.so.0', '.'),
('/usr/lib/x86_64-linux-gnu/libgstaudio-1.0.so.0', '.'),
('/usr/lib/x86_64-linux-gnu/libgstpbutils-1.0.so.0', '.'),
] ]
binaries.extend(qt_platform_plugins) binaries.extend(qt_platform_plugins)
binaries.extend(qt6_libraries)
binaries.extend(essential_x11_libraries) binaries.extend(essential_x11_libraries)
binaries.extend(gstreamer_libraries)
print(" 📦 Including only essential X11 libraries to prevent segfaults") print(" 📦 Including Qt6, X11, and GStreamer libraries for self-contained binary")
print(" 💡 System will provide most X11/GL libraries to avoid version conflicts") print(" 💡 This ensures the binary works on systems without Qt6/GStreamer installed")
return binaries return binaries
......
...@@ -42,6 +42,7 @@ class GamesThread(ThreadedComponent): ...@@ -42,6 +42,7 @@ class GamesThread(ThreadedComponent):
self.message_bus.subscribe(self.name, MessageType.MATCH_START, self._handle_match_start) self.message_bus.subscribe(self.name, MessageType.MATCH_START, self._handle_match_start)
self.message_bus.subscribe(self.name, MessageType.PLAY_VIDEO_MATCH_DONE, self._handle_play_video_match_done) self.message_bus.subscribe(self.name, MessageType.PLAY_VIDEO_MATCH_DONE, self._handle_play_video_match_done)
self.message_bus.subscribe(self.name, MessageType.MATCH_DONE, self._handle_match_done) self.message_bus.subscribe(self.name, MessageType.MATCH_DONE, self._handle_match_done)
self.message_bus.subscribe(self.name, MessageType.GAME_STATUS, self._handle_game_status_request)
self.message_bus.subscribe(self.name, MessageType.SYSTEM_STATUS, self._handle_system_status) self.message_bus.subscribe(self.name, MessageType.SYSTEM_STATUS, self._handle_system_status)
# Send ready status # Send ready status
...@@ -265,6 +266,34 @@ class GamesThread(ThreadedComponent): ...@@ -265,6 +266,34 @@ class GamesThread(ThreadedComponent):
except Exception as fallback_e: except Exception as fallback_e:
logger.error(f"Fallback betting calculation also failed: {fallback_e}") logger.error(f"Fallback betting calculation also failed: {fallback_e}")
def _handle_game_status_request(self, message: Message):
"""Handle GAME_STATUS requests from Qt player"""
try:
logger.info(f"Received GAME_STATUS request from {message.sender}")
# Determine current game status
game_status = self._determine_game_status()
# Send GAME_STATUS response back to the requester
response = Message(
type=MessageType.GAME_STATUS,
sender=self.name,
recipient=message.sender,
data={
"status": game_status,
"fixture_id": self.current_fixture_id,
"game_active": self.game_active,
"timestamp": time.time()
},
correlation_id=message.correlation_id
)
# Broadcast the response instead of sending to specific recipient
self.message_bus.publish(response, broadcast=True)
logger.info(f"Broadcast game status response: {game_status}")
except Exception as e:
logger.error(f"Failed to handle game status request: {e}")
def _process_message(self, message: Message): def _process_message(self, message: Message):
"""Process incoming messages""" """Process incoming messages"""
try: try:
...@@ -277,12 +306,16 @@ class GamesThread(ThreadedComponent): ...@@ -277,12 +306,16 @@ class GamesThread(ThreadedComponent):
self._handle_schedule_games(message) self._handle_schedule_games(message)
elif message.type == MessageType.SYSTEM_SHUTDOWN: elif message.type == MessageType.SYSTEM_SHUTDOWN:
self._handle_shutdown_message(message) self._handle_shutdown_message(message)
elif message.type == MessageType.SYSTEM_STATUS:
self._handle_system_status(message)
elif message.type == MessageType.GAME_UPDATE: elif message.type == MessageType.GAME_UPDATE:
self._handle_game_update(message) self._handle_game_update(message)
elif message.type == MessageType.PLAY_VIDEO_MATCH_DONE: elif message.type == MessageType.PLAY_VIDEO_MATCH_DONE:
self._handle_play_video_match_done(message) self._handle_play_video_match_done(message)
elif message.type == MessageType.MATCH_DONE: elif message.type == MessageType.MATCH_DONE:
self._handle_match_done(message) self._handle_match_done(message)
elif message.type == MessageType.GAME_STATUS:
self._handle_game_status_request(message)
except Exception as e: except Exception as e:
logger.error(f"Failed to process message: {e}") logger.error(f"Failed to process message: {e}")
...@@ -1315,9 +1348,12 @@ class GamesThread(ThreadedComponent): ...@@ -1315,9 +1348,12 @@ class GamesThread(ThreadedComponent):
logger.error(f"Failed to handle MATCH_DONE message: {e}") logger.error(f"Failed to handle MATCH_DONE message: {e}")
def _handle_system_status(self, message: Message): def _handle_system_status(self, message: Message):
"""Handle SYSTEM_STATUS messages, particularly fixture update completion""" """Handle SYSTEM_STATUS messages, particularly fixture update completion and status requests"""
try: try:
logger.debug(f"GamesThread handling SYSTEM_STATUS message from {message.sender}: {message.data}")
status = message.data.get("status") status = message.data.get("status")
details = message.data.get("details", {})
if status == "fixture_update_completed": if status == "fixture_update_completed":
synchronized_matches = message.data.get("synchronized_matches", 0) synchronized_matches = message.data.get("synchronized_matches", 0)
downloaded_zips = message.data.get("downloaded_zips", 0) downloaded_zips = message.data.get("downloaded_zips", 0)
...@@ -1338,8 +1374,43 @@ class GamesThread(ThreadedComponent): ...@@ -1338,8 +1374,43 @@ class GamesThread(ThreadedComponent):
) )
self.message_bus.publish(start_game_message) self.message_bus.publish(start_game_message)
elif status == "status_request":
# Handle status requests from Qt player
request_type = details.get("request_type")
logger.debug(f"Status request type: {request_type}, details: {details}")
if request_type == "game_status":
logger.info(f"Received game status request from {message.sender}")
# Determine current game status
game_status = self._determine_game_status()
logger.debug(f"Determined game status: {game_status}")
# Send GAME_STATUS response back to the requester (broadcast to ensure delivery)
response = Message(
type=MessageType.GAME_STATUS,
sender=self.name,
recipient=message.sender,
data={
"status": game_status,
"fixture_id": self.current_fixture_id,
"game_active": self.game_active,
"timestamp": time.time()
},
correlation_id=message.correlation_id
)
logger.debug(f"About to publish GAME_STATUS response: {response.data}")
# Broadcast the response to ensure it reaches the Qt player
self.message_bus.publish(response, broadcast=True)
logger.info(f"Sent game status response to {message.sender}: {game_status}")
else:
logger.debug(f"Ignoring status_request with unknown request_type: {request_type}")
else:
logger.debug(f"Ignoring SYSTEM_STATUS message with unknown status: {status}")
except Exception as e: except Exception as e:
logger.error(f"Failed to handle system status message: {e}") logger.error(f"Failed to handle system status message: {e}")
import traceback
logger.error(f"Full traceback: {traceback.format_exc()}")
def _set_match_status(self, match_id: int, status: str): def _set_match_status(self, match_id: int, status: str):
"""Set match status in database""" """Set match status in database"""
...@@ -1869,6 +1940,53 @@ class GamesThread(ThreadedComponent): ...@@ -1869,6 +1940,53 @@ class GamesThread(ThreadedComponent):
session.rollback() session.rollback()
return None return None
def _determine_game_status(self) -> str:
"""Determine the current game status for status requests"""
try:
# If a game is currently active, return "started"
if self.game_active and self.current_fixture_id:
return "started"
# Check if there are any active fixtures (matches in non-terminal states)
session = self.db_manager.get_session()
try:
# Get today's date
today = datetime.now().date()
# Check for active matches today
active_matches = session.query(MatchModel).filter(
MatchModel.start_time.isnot(None),
MatchModel.start_time >= datetime.combine(today, datetime.min.time()),
MatchModel.start_time < datetime.combine(today, datetime.max.time()),
MatchModel.status.notin_(['done', 'cancelled', 'failed', 'paused']),
MatchModel.active_status == True
).all()
if active_matches:
return "already_active"
# Check if all today's fixtures are in terminal states
if self._has_today_fixtures_all_terminal():
return "completed_no_old_matches"
# Check if there are any fixtures at all (even if not today)
any_fixtures = session.query(MatchModel).filter(
MatchModel.active_status == True
).count()
if any_fixtures > 0:
return "ready" # Fixtures exist but no active game
# No fixtures at all
return "shutdown"
finally:
session.close()
except Exception as e:
logger.error(f"Failed to determine game status: {e}")
return "ready" # Default fallback
def _cleanup(self): def _cleanup(self):
"""Perform cleanup operations""" """Perform cleanup operations"""
try: try:
......
...@@ -99,6 +99,8 @@ class ThreadedComponent(ABC): ...@@ -99,6 +99,8 @@ class ThreadedComponent(ABC):
except Exception as e: except Exception as e:
logger.error(f"Failed to stop component {self.name}: {e}") logger.error(f"Failed to stop component {self.name}: {e}")
import traceback
logger.error(f"Full traceback: {traceback.format_exc()}")
return False return False
def _thread_wrapper(self): def _thread_wrapper(self):
...@@ -237,11 +239,15 @@ class ThreadManager: ...@@ -237,11 +239,15 @@ class ThreadManager:
with self._lock: with self._lock:
for name, component in self.components.items(): for name, component in self.components.items():
try:
if not component.stop(stop_timeout): if not component.stop(stop_timeout):
logger.error(f"Failed to stop component {name}") logger.error(f"Failed to stop component {name}")
success = False success = False
else: else:
logger.info(f"Component {name} stopped") logger.info(f"Component {name} stopped")
except Exception as e:
logger.error(f"Exception stopping component {name}: {e}")
success = False
if success: if success:
logger.info("All components stopped successfully") logger.info("All components stopped successfully")
......
This diff is collapsed.
...@@ -222,9 +222,9 @@ ...@@ -222,9 +222,9 @@
<script> <script>
// Global variables for overlay data handling // Global variables for overlay data handling
let overlayData = {}; let overlayData = {};
let currentTitle = 'Announcement'; let currentTitle = 'Mbetter system:';
let currentMessage = 'This is a custom message from the system.'; let currentMessage = 'Waiting for game to start...';
let currentIcon = '📢'; let currentIcon = '🥊';
// Function to update overlay data (called by Qt WebChannel) // Function to update overlay data (called by Qt WebChannel)
function updateOverlayData(data) { function updateOverlayData(data) {
......
...@@ -3702,8 +3702,8 @@ def create_result_options_migration(): ...@@ -3702,8 +3702,8 @@ def create_result_options_migration():
from ..database.migrations import DatabaseMigration from ..database.migrations import DatabaseMigration
class Migration_023(DatabaseMigration): class Migration_023(DatabaseMigration):
version = "023" def __init__(self):
description = "Add result_options table for extraction system results area" super().__init__("023", "Add result_options table for extraction system results area")
def up(self, db_manager): def up(self, db_manager):
"""Apply migration""" """Apply migration"""
......
...@@ -713,6 +713,17 @@ function updateAvailableMatchesDisplay(data, container) { ...@@ -713,6 +713,17 @@ function updateAvailableMatchesDisplay(data, container) {
// Add event listeners for amount inputs only // Add event listeners for amount inputs only
container.querySelectorAll('.amount-input').forEach(input => { container.querySelectorAll('.amount-input').forEach(input => {
input.addEventListener('input', function() { input.addEventListener('input', function() {
// Clear other outcomes for this match when entering an amount
const matchId = this.getAttribute('data-match-id');
const currentOutcome = this.getAttribute('data-outcome');
// Clear all other amount inputs for this match
container.querySelectorAll(`.amount-input[data-match-id="${matchId}"]`).forEach(otherInput => {
if (otherInput !== this) {
otherInput.value = '';
}
});
updateBetSummary(); updateBetSummary();
}); });
}); });
......
[Paths]
Prefix = .
Libraries = .
Plugins = plugins
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