From 2df374f4ef1e2374c706e3ec7297200075568e9b Mon Sep 17 00:00:00 2001 From: Kerem Date: Sun, 23 Nov 2025 15:35:19 +0100 Subject: [PATCH] Working version for thursdays review --- configure_interface_widget.py | 10 +- configure_session_widget.py | 6 +- database/database_manager_widget.py | 775 +++ database/ehinge.db | Bin 5492736 -> 13664256 bytes database/ehinge_backup_20251123_113445.db | Bin 0 -> 5267456 bytes database/init_database.py | 31 +- graph/graph_kit/graph_core.py | 208 +- graph/graph_kit/graph_core_widget.py | 610 +- graph_table_query.py | 31 +- main.py | 342 +- session.py | 93 +- session_widget.py | 209 +- test.csv | 6615 +++++++++++++++++++++ uart/uart_kit/uart_core.py | 4 +- 14 files changed, 8551 insertions(+), 383 deletions(-) create mode 100644 database/database_manager_widget.py create mode 100644 database/ehinge_backup_20251123_113445.db create mode 100644 test.csv diff --git a/configure_interface_widget.py b/configure_interface_widget.py index 087e447..460920f 100644 --- a/configure_interface_widget.py +++ b/configure_interface_widget.py @@ -46,11 +46,7 @@ class ConfigureInterfaceWidget(QWidget): def _init_ui(self): """Initialize UI.""" layout = QVBoxLayout() - - title = QLabel("Configure Interface Profiles") - title.setStyleSheet("font-size: 16px; font-weight: bold;") - layout.addWidget(title) - + splitter = QSplitter(Qt.Orientation.Horizontal) splitter.addWidget(self._create_list_panel()) splitter.addWidget(self._create_editor_panel()) @@ -122,7 +118,7 @@ class ConfigureInterfaceWidget(QWidget): uart_group = QGroupBox("UART Command Settings") form = QFormLayout() - + # Port with refresh port_layout = QHBoxLayout() self.cmd_port = QComboBox() @@ -186,7 +182,7 @@ class ConfigureInterfaceWidget(QWidget): uart_group = QGroupBox("UART Logger Settings") form = QFormLayout() - + # Port with refresh port_layout = QHBoxLayout() self.log_port = QComboBox() diff --git a/configure_session_widget.py b/configure_session_widget.py index f508de3..b4f06c0 100644 --- a/configure_session_widget.py +++ b/configure_session_widget.py @@ -35,11 +35,7 @@ class ConfigureSessionWidget(QWidget): def _init_ui(self): """Initialize UI.""" layout = QVBoxLayout() - - title = QLabel("Configure Session Profiles") - title.setStyleSheet("font-size: 16px; font-weight: bold;") - layout.addWidget(title) - + splitter = QSplitter(Qt.Orientation.Horizontal) splitter.addWidget(self._create_list_panel()) splitter.addWidget(self._create_editor_panel()) diff --git a/database/database_manager_widget.py b/database/database_manager_widget.py new file mode 100644 index 0000000..51db420 --- /dev/null +++ b/database/database_manager_widget.py @@ -0,0 +1,775 @@ +#!/usr/bin/env python3 +""" +Database Manager Widget - vzug-e-hinge +======================================= +Comprehensive database management interface for inspecting and managing sessions. + +Features: +- Session browser with sort/filter/search +- Session details with editable notes +- Rename, delete, and export sessions +- Delete individual runs +- Session statistics + +Author: Kynsight +Version: 1.0.0 +""" + +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QSplitter, + QTableWidget, QTableWidgetItem, QHeaderView, + QLabel, QPushButton, QLineEdit, QTextEdit, + QGroupBox, QMessageBox, QInputDialog, QFileDialog, + QCheckBox, QListWidget, QListWidgetItem +) +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QFont +from datetime import datetime +import sqlite3 + + +class DatabaseManagerWidget(QWidget): + """ + Database Manager Widget. + + Provides comprehensive session management: + - Browse all sessions + - View session details and runs + - Rename, delete, export sessions + - Add notes to sessions + - Delete individual runs + """ + + def __init__(self, db_manager): + """ + Initialize Database Manager Widget. + + Args: + db_manager: DatabaseManager instance + """ + super().__init__() + self.db_manager = db_manager + self.selected_session_id = None + self._ensure_notes_column() + self._init_ui() + self._load_sessions() + + def _ensure_notes_column(self): + """Ensure notes column exists in sessions table (migration).""" + try: + conn = self.db_manager.get_connection() + + # Check if notes column exists + cursor = conn.execute("PRAGMA table_info(sessions)") + columns = [row[1] for row in cursor.fetchall()] + + if 'notes' not in columns: + # Add notes column + conn.execute("ALTER TABLE sessions ADD COLUMN notes TEXT DEFAULT ''") + conn.commit() + print("[INFO] Added 'notes' column to sessions table") + except Exception as e: + print(f"[WARN] Failed to add notes column: {e}") + + def _init_ui(self): + """Initialize user interface.""" + layout = QVBoxLayout() + self.setLayout(layout) + + # Main splitter (left: session browser, right: details) + splitter = QSplitter(Qt.Orientation.Horizontal) + layout.addWidget(splitter) + + # Left panel: Session Browser + left_panel = self._create_session_browser() + splitter.addWidget(left_panel) + + # Right panel: Session Details + right_panel = self._create_session_details() + splitter.addWidget(right_panel) + + # Set splitter ratio (40% left, 60% right) + splitter.setStretchFactor(0, 4) + splitter.setStretchFactor(1, 6) + + def _create_session_browser(self): + """Create session browser panel (left side).""" + panel = QWidget() + layout = QVBoxLayout() + panel.setLayout(layout) + + # Search and filter + search_layout = QHBoxLayout() + search_layout.addWidget(QLabel("Search:")) + self.search_input = QLineEdit() + self.search_input.setPlaceholderText("Filter sessions by name...") + self.search_input.textChanged.connect(self._filter_sessions) + search_layout.addWidget(self.search_input) + layout.addLayout(search_layout) + + # Session table + self.session_table = QTableWidget() + self.session_table.setColumnCount(5) + self.session_table.setHorizontalHeaderLabels([ + "Select", "Session Name", "Date", "Runs", "Size (MB)" + ]) + + # Configure table + header = self.session_table.horizontalHeader() + header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) + header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch) + header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents) + header.setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents) + header.setSectionResizeMode(4, QHeaderView.ResizeMode.ResizeToContents) + + self.session_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows) + self.session_table.setSelectionMode(QTableWidget.SelectionMode.SingleSelection) + self.session_table.itemSelectionChanged.connect(self._on_session_selected) + self.session_table.setSortingEnabled(True) + + layout.addWidget(self.session_table) + + # Batch action buttons + btn_layout = QHBoxLayout() + + self.btn_select_all = QPushButton("Select All") + self.btn_select_all.clicked.connect(self._select_all_sessions) + btn_layout.addWidget(self.btn_select_all) + + self.btn_clear_selection = QPushButton("Clear") + self.btn_clear_selection.clicked.connect(self._clear_selection) + btn_layout.addWidget(self.btn_clear_selection) + + btn_layout.addStretch() + + self.btn_delete_selected = QPushButton("Delete Selected") + self.btn_delete_selected.clicked.connect(self._delete_selected_sessions) + btn_layout.addWidget(self.btn_delete_selected) + + self.btn_refresh = QPushButton("Refresh") + self.btn_refresh.clicked.connect(self._load_sessions) + btn_layout.addWidget(self.btn_refresh) + + layout.addLayout(btn_layout) + + return panel + + def _create_session_details(self): + """Create session details panel (right side).""" + panel = QWidget() + layout = QVBoxLayout() + panel.setLayout(layout) + + # Session info group + info_group = QGroupBox("Session Information") + info_layout = QVBoxLayout() + info_group.setLayout(info_layout) + + # Session ID + self.label_session_id = QLabel("No session selected") + self.label_session_id.setFont(QFont("Arial", 10, QFont.Weight.Bold)) + info_layout.addWidget(self.label_session_id) + + # Created date + self.label_created = QLabel("") + info_layout.addWidget(self.label_created) + + # Notes + notes_layout = QHBoxLayout() + notes_layout.addWidget(QLabel("Notes:")) + self.text_notes = QTextEdit() + self.text_notes.setMaximumHeight(80) + self.text_notes.setPlaceholderText("Add notes or description for this session...") + self.text_notes.textChanged.connect(self._on_notes_changed) + notes_layout.addWidget(self.text_notes) + info_layout.addLayout(notes_layout) + + # Session actions + action_layout = QHBoxLayout() + + self.btn_rename = QPushButton("Rename Session") + self.btn_rename.clicked.connect(self._rename_session) + action_layout.addWidget(self.btn_rename) + + self.btn_delete = QPushButton("Delete Session") + self.btn_delete.clicked.connect(self._delete_session) + action_layout.addWidget(self.btn_delete) + + self.btn_export = QPushButton("Export CSV") + self.btn_export.clicked.connect(self._export_session) + action_layout.addWidget(self.btn_export) + + action_layout.addStretch() + info_layout.addLayout(action_layout) + + layout.addWidget(info_group) + + # Runs group + runs_group = QGroupBox("Runs in Session") + runs_layout = QVBoxLayout() + runs_group.setLayout(runs_layout) + + self.runs_list = QListWidget() + runs_layout.addWidget(self.runs_list) + + # Run actions + run_action_layout = QHBoxLayout() + + self.btn_delete_runs = QPushButton("Delete Selected Runs") + self.btn_delete_runs.clicked.connect(self._delete_selected_runs) + run_action_layout.addWidget(self.btn_delete_runs) + + run_action_layout.addStretch() + runs_layout.addLayout(run_action_layout) + + layout.addWidget(runs_group) + + # Statistics group + stats_group = QGroupBox("Session Statistics") + stats_layout = QVBoxLayout() + stats_group.setLayout(stats_layout) + + self.label_stats = QLabel("Select a session to view statistics") + self.label_stats.setFont(QFont("Monospace", 9)) + stats_layout.addWidget(self.label_stats) + + layout.addWidget(stats_group) + + # Disable all controls initially + self._set_details_enabled(False) + + return panel + + def _set_details_enabled(self, enabled: bool): + """Enable or disable detail panel controls.""" + self.text_notes.setEnabled(enabled) + self.btn_rename.setEnabled(enabled) + self.btn_delete.setEnabled(enabled) + self.btn_export.setEnabled(enabled) + self.btn_delete_runs.setEnabled(enabled) + + def _load_sessions(self): + """Load all sessions from database.""" + try: + conn = self.db_manager.get_connection() + + cursor = conn.execute(""" + SELECT + session_id, + session_name, + created_at, + total_runs, + notes, + (SELECT COUNT(*) FROM telemetry_decoded WHERE telemetry_decoded.session_id = sessions.session_id) as data_points + FROM sessions + ORDER BY created_at DESC + """) + + sessions = cursor.fetchall() + + # Store all sessions for filtering + self.all_sessions = sessions + + # Display sessions + self._display_sessions(sessions) + + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to load sessions:\n{str(e)}") + + def _display_sessions(self, sessions): + """Display sessions in table.""" + self.session_table.setSortingEnabled(False) + self.session_table.setRowCount(0) + + for row_idx, session in enumerate(sessions): + session_id, session_name, created_at, total_runs, notes, data_points = session + + self.session_table.insertRow(row_idx) + + # Checkbox + checkbox = QCheckBox() + checkbox_widget = QWidget() + checkbox_layout = QHBoxLayout(checkbox_widget) + checkbox_layout.addWidget(checkbox) + checkbox_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) + checkbox_layout.setContentsMargins(0, 0, 0, 0) + self.session_table.setCellWidget(row_idx, 0, checkbox_widget) + + # Session name + name_item = QTableWidgetItem(session_name) + name_item.setData(Qt.ItemDataRole.UserRole, session_id) + self.session_table.setItem(row_idx, 1, name_item) + + # Date + date_item = QTableWidgetItem(created_at) + self.session_table.setItem(row_idx, 2, date_item) + + # Runs + runs_item = QTableWidgetItem(str(total_runs)) + runs_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) + self.session_table.setItem(row_idx, 3, runs_item) + + # Size (estimate: ~100 bytes per data point) + size_mb = (data_points * 100) / (1024 * 1024) + size_item = QTableWidgetItem(f"{size_mb:.2f}") + size_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) + self.session_table.setItem(row_idx, 4, size_item) + + self.session_table.setSortingEnabled(True) + + def _filter_sessions(self): + """Filter sessions based on search text.""" + search_text = self.search_input.text().lower() + + if not search_text: + # Show all sessions + self._display_sessions(self.all_sessions) + else: + # Filter sessions + filtered = [s for s in self.all_sessions if search_text in s[1].lower()] + self._display_sessions(filtered) + + def _on_session_selected(self): + """Handle session selection.""" + selected_items = self.session_table.selectedItems() + + if not selected_items: + self._set_details_enabled(False) + self.selected_session_id = None + self.label_session_id.setText("No session selected") + self.label_created.setText("") + self.text_notes.clear() + self.runs_list.clear() + self.label_stats.setText("Select a session to view statistics") + return + + # Get session ID from selected row + row = selected_items[0].row() + session_id = self.session_table.item(row, 1).data(Qt.ItemDataRole.UserRole) + + self.selected_session_id = session_id + self._load_session_details(session_id) + self._set_details_enabled(True) + + def _load_session_details(self, session_id: str): + """Load details for selected session.""" + try: + conn = self.db_manager.get_connection() + + # Get session info + cursor = conn.execute(""" + SELECT session_id, session_name, created_at, total_runs, notes + FROM sessions + WHERE session_id = ? + """, (session_id,)) + + session = cursor.fetchone() + + if not session: + return + + session_id, session_name, created_at, total_runs, notes = session + + # Update UI + self.label_session_id.setText(f"Session: {session_name}") + self.label_created.setText(f"Created: {created_at}") + + # Block signals while setting notes to avoid triggering save + self.text_notes.blockSignals(True) + self.text_notes.setPlainText(notes or "") + self.text_notes.blockSignals(False) + + # Load runs + self._load_runs(session_id) + + # Load statistics + self._load_statistics(session_id) + + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to load session details:\n{str(e)}") + + def _load_runs(self, session_id: str): + """Load runs for selected session.""" + try: + conn = self.db_manager.get_connection() + + cursor = conn.execute(""" + SELECT DISTINCT + t.run_no, + COALESCE(u.command_name, i.command_name, 'Unknown') as command_name, + COUNT(*) as sample_count + FROM telemetry_decoded t + LEFT JOIN uart_commands u ON t.run_command_id = u.command_id + LEFT JOIN i2c_commands i ON t.run_command_id = i.command_id + WHERE t.session_id = ? + GROUP BY t.run_no + ORDER BY t.run_no + """, (session_id,)) + + runs = cursor.fetchall() + + self.runs_list.clear() + + for run_no, command_name, sample_count in runs: + item = QListWidgetItem(f"Run {run_no}: {command_name} ({sample_count:,} samples)") + item.setData(Qt.ItemDataRole.UserRole, run_no) + item.setCheckState(Qt.CheckState.Unchecked) + self.runs_list.addItem(item) + + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to load runs:\n{str(e)}") + + def _load_statistics(self, session_id: str): + """Load statistics for selected session.""" + try: + conn = self.db_manager.get_connection() + + # Get statistics + cursor = conn.execute(""" + SELECT + COUNT(DISTINCT run_no) as total_runs, + COUNT(*) as total_samples, + MIN(t_ns) as start_time, + MAX(t_ns) as end_time + FROM telemetry_decoded + WHERE session_id = ? + """, (session_id,)) + + stats = cursor.fetchone() + + if stats and stats[0] > 0: + total_runs, total_samples, start_time, end_time = stats + + # Calculate duration + duration_ns = end_time - start_time + duration_s = duration_ns / 1_000_000_000 + + # Format statistics + stats_text = f"• Total Runs: {total_runs}\n" + stats_text += f"• Total Samples: {total_samples:,}\n" + stats_text += f"• Avg Samples/Run: {total_samples // total_runs:,}\n" + stats_text += f"• Duration: {duration_s:.1f} seconds\n" + + self.label_stats.setText(stats_text) + else: + self.label_stats.setText("No data available for this session") + + except Exception as e: + self.label_stats.setText(f"Error loading statistics: {str(e)}") + + def _on_notes_changed(self): + """Handle notes text change - auto-save.""" + if not self.selected_session_id: + return + + notes = self.text_notes.toPlainText() + + try: + conn = self.db_manager.get_connection() + conn.execute(""" + UPDATE sessions + SET notes = ? + WHERE session_id = ? + """, (notes, self.selected_session_id)) + conn.commit() + except Exception as e: + print(f"Failed to save notes: {e}") + + def _rename_session(self): + """Rename selected session.""" + if not self.selected_session_id: + return + + # Get current name + current_name = self.label_session_id.text().replace("Session: ", "") + + # Ask for new name + new_name, ok = QInputDialog.getText( + self, + "Rename Session", + "Enter new session name:", + text=current_name + ) + + if not ok or not new_name.strip(): + return + + try: + conn = self.db_manager.get_connection() + + # Update session name in sessions table + conn.execute(""" + UPDATE sessions + SET session_name = ? + WHERE session_id = ? + """, (new_name.strip(), self.selected_session_id)) + + # Update session name in telemetry_raw table + conn.execute(""" + UPDATE telemetry_raw + SET session_name = ? + WHERE session_id = ? + """, (new_name.strip(), self.selected_session_id)) + + # Update session name in telemetry_decoded table + conn.execute(""" + UPDATE telemetry_decoded + SET session_name = ? + WHERE session_id = ? + """, (new_name.strip(), self.selected_session_id)) + + conn.commit() + + # Reload sessions + self._load_sessions() + + # Reload details + self._load_session_details(self.selected_session_id) + + QMessageBox.information(self, "Success", "Session renamed successfully!") + + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to rename session:\n{str(e)}") + + def _delete_session(self): + """Delete selected session.""" + if not self.selected_session_id: + return + + # Confirm + reply = QMessageBox.question( + self, + "Confirm Delete", + f"Delete session '{self.selected_session_id}'?\n\n" + "This will delete all runs and data for this session.\n" + "This action cannot be undone!", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No + ) + + if reply != QMessageBox.StandardButton.Yes: + return + + try: + conn = self.db_manager.get_connection() + conn.execute("DELETE FROM telemetry_decoded WHERE session_id = ?", (self.selected_session_id,)) + conn.execute("DELETE FROM telemetry_raw WHERE session_id = ?", (self.selected_session_id,)) + conn.execute("DELETE FROM sessions WHERE session_id = ?", (self.selected_session_id,)) + conn.commit() + + self.selected_session_id = None + self._load_sessions() + self._set_details_enabled(False) + + QMessageBox.information(self, "Success", "Session deleted successfully!") + + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to delete session:\n{str(e)}") + + def _export_session(self): + """Export selected session to CSV.""" + if not self.selected_session_id: + return + + # Get save location + filename = f"{self.selected_session_id}_export.csv" + filepath, _ = QFileDialog.getSaveFileName( + self, + "Export Session", + filename, + "CSV Files (*.csv);;All Files (*)" + ) + + if not filepath: + return + + try: + import csv + + conn = self.db_manager.get_connection() + + # Get all data for session + cursor = conn.execute(""" + SELECT + session_id, session_name, run_no, t_ns, time_ms, + motor_current, encoder_value, relative_encoder_value, + v24_pec_diff, pwm, + i2c_raw14, i2c_zero_raw14, i2c_delta_raw14, + i2c_angle_deg, i2c_zero_angle_deg, + angular_velocity, angular_acceleration + FROM telemetry_decoded + WHERE session_id = ? + ORDER BY run_no, t_ns + """, (self.selected_session_id,)) + + rows = cursor.fetchall() + + # Write to CSV + with open(filepath, 'w', newline='') as f: + writer = csv.writer(f) + + # Header + writer.writerow([ + 'session_id', 'session_name', 'run_no', 't_ns', 'time_ms', + 'motor_current', 'encoder_value', 'relative_encoder_value', + 'v24_pec_diff', 'pwm', + 'i2c_raw14', 'i2c_zero_raw14', 'i2c_delta_raw14', + 'i2c_angle_deg', 'i2c_zero_angle_deg', + 'angular_velocity', 'angular_acceleration' + ]) + + # Data + writer.writerows(rows) + + QMessageBox.information( + self, + "Export Complete", + f"Session exported successfully!\n\n" + f"File: {filepath}\n" + f"Rows: {len(rows):,}" + ) + + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to export session:\n{str(e)}") + + def _delete_selected_runs(self): + """Delete selected runs from session.""" + if not self.selected_session_id: + return + + # Get checked runs + selected_runs = [] + for i in range(self.runs_list.count()): + item = self.runs_list.item(i) + if item.checkState() == Qt.CheckState.Checked: + run_no = item.data(Qt.ItemDataRole.UserRole) + selected_runs.append(run_no) + + if not selected_runs: + QMessageBox.warning(self, "No Selection", "Please select runs to delete.") + return + + # Confirm + reply = QMessageBox.question( + self, + "Confirm Delete", + f"Delete {len(selected_runs)} run(s) from session?\n\n" + "This will permanently delete all data for these runs.\n" + "This action cannot be undone!", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No + ) + + if reply != QMessageBox.StandardButton.Yes: + return + + try: + conn = self.db_manager.get_connection() + + for run_no in selected_runs: + conn.execute(""" + DELETE FROM telemetry_decoded + WHERE session_id = ? AND run_no = ? + """, (self.selected_session_id, run_no)) + + conn.execute(""" + DELETE FROM telemetry_raw + WHERE session_id = ? AND run_no = ? + """, (self.selected_session_id, run_no)) + + # Update total_runs in sessions table + cursor = conn.execute(""" + SELECT COUNT(DISTINCT run_no) + FROM telemetry_decoded + WHERE session_id = ? + """, (self.selected_session_id,)) + + new_run_count = cursor.fetchone()[0] + + conn.execute(""" + UPDATE sessions + SET total_runs = ? + WHERE session_id = ? + """, (new_run_count, self.selected_session_id)) + + conn.commit() + + # Reload details + self._load_session_details(self.selected_session_id) + self._load_sessions() + + QMessageBox.information(self, "Success", f"Deleted {len(selected_runs)} run(s) successfully!") + + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to delete runs:\n{str(e)}") + + def _select_all_sessions(self): + """Select all checkboxes in session table.""" + for row in range(self.session_table.rowCount()): + checkbox_widget = self.session_table.cellWidget(row, 0) + checkbox = checkbox_widget.findChild(QCheckBox) + if checkbox: + checkbox.setChecked(True) + + def _clear_selection(self): + """Clear all checkboxes in session table.""" + for row in range(self.session_table.rowCount()): + checkbox_widget = self.session_table.cellWidget(row, 0) + checkbox = checkbox_widget.findChild(QCheckBox) + if checkbox: + checkbox.setChecked(False) + + def _delete_selected_sessions(self): + """Delete all checked sessions.""" + # Get checked sessions + selected_sessions = [] + for row in range(self.session_table.rowCount()): + checkbox_widget = self.session_table.cellWidget(row, 0) + checkbox = checkbox_widget.findChild(QCheckBox) + if checkbox and checkbox.isChecked(): + session_id = self.session_table.item(row, 1).data(Qt.ItemDataRole.UserRole) + session_name = self.session_table.item(row, 1).text() + selected_sessions.append((session_id, session_name)) + + if not selected_sessions: + QMessageBox.warning(self, "No Selection", "Please select sessions to delete.") + return + + # Confirm + session_list = "\n".join([f"• {name}" for _, name in selected_sessions]) + reply = QMessageBox.question( + self, + "Confirm Delete", + f"Delete {len(selected_sessions)} session(s)?\n\n{session_list}\n\n" + "This will permanently delete all data for these sessions.\n" + "This action cannot be undone!", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No + ) + + if reply != QMessageBox.StandardButton.Yes: + return + + try: + conn = self.db_manager.get_connection() + + for session_id, _ in selected_sessions: + conn.execute("DELETE FROM telemetry_decoded WHERE session_id = ?", (session_id,)) + conn.execute("DELETE FROM telemetry_raw WHERE session_id = ?", (session_id,)) + conn.execute("DELETE FROM sessions WHERE session_id = ?", (session_id,)) + + conn.commit() + + # Vacuum database + self.db_manager.vacuum() + + self.selected_session_id = None + self._load_sessions() + self._set_details_enabled(False) + + QMessageBox.information( + self, + "Success", + f"Deleted {len(selected_sessions)} session(s) successfully!\n\n" + "Database vacuumed." + ) + + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to delete sessions:\n{str(e)}") diff --git a/database/ehinge.db b/database/ehinge.db index 7ad84ef90bc5b23bb5fb20ea9da4140ca7c211f6..3a68236a8f71f2d6cdb2b7800ff9903c13a55156 100644 GIT binary patch literal 13664256 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 _DO;7);>S(!k-f2r}nx=TCy*?^m1{HrY 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