From 64c0e462769dae570ee2f41d01c0f2d75997cfb1 Mon Sep 17 00:00:00 2001 From: Boris Pozdena Date: Fri, 21 Nov 2025 09:48:46 +0100 Subject: [PATCH] Improved error handling and messaging #255 --- src/main_window.py | 15 +++++++ src/ui/process_status_page.ui | 16 +++++++ src/ui/ui_process_status_page.py | 16 +++++-- src/workers.py | 73 ++++++++++++++++++++++++++++++-- 4 files changed, 113 insertions(+), 7 deletions(-) diff --git a/src/main_window.py b/src/main_window.py index 3774b12..ba21cfc 100644 --- a/src/main_window.py +++ b/src/main_window.py @@ -400,6 +400,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.start_onedrive_monitor(profile_name) def start_onedrive_monitor(self, profile_name, options=""): + # Clear any previous error messages when starting sync + self.profile_status_pages[profile_name].label_error_icon.clear() + self.profile_status_pages[profile_name].label_error_icon.setToolTip("") + if profile_name not in workers: workers[profile_name] = WorkerThread(profile_name, options) workers[profile_name].start() @@ -497,6 +501,17 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.profile_status_pages[profile].label_free_space.setText(data["free_space"]) self.profile_status_pages[profile].label_account_type.setText(data["account_type"]) + # Handle error message display + if "error_message" in data and data["error_message"]: + # Show error icon with full error message in tooltip + pixmap_warning = QPixmap(DIR_PATH + "/resources/images/warning.png").scaled(20, 20, Qt.KeepAspectRatio) + self.profile_status_pages[profile].label_error_icon.setPixmap(pixmap_warning) + self.profile_status_pages[profile].label_error_icon.setToolTip(data["error_message"]) + else: + # Clear error icon + self.profile_status_pages[profile].label_error_icon.clear() + self.profile_status_pages[profile].label_error_icon.setToolTip("") + def event_update_progress(self, data, profile): """ data: diff --git a/src/ui/process_status_page.ui b/src/ui/process_status_page.ui index 1f44e1a..93be8bb 100644 --- a/src/ui/process_status_page.ui +++ b/src/ui/process_status_page.ui @@ -300,6 +300,22 @@ QPushButton:pressed {background-color: rgba(0, 0, 0, 0.1)} + + + + + 20 + 16777215 + + + + QToolTip { color: white; } + + + + + + diff --git a/src/ui/ui_process_status_page.py b/src/ui/ui_process_status_page.py index 0cd1021..18a3248 100644 --- a/src/ui/ui_process_status_page.py +++ b/src/ui/ui_process_status_page.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'process_status_page.ui' ## -## Created by: Qt User Interface Compiler version 6.2.2 +## Created by: Qt User Interface Compiler version 6.10.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -54,7 +54,7 @@ class Ui_status_page(object): self.horizontalLayout_3.addWidget(self.pushButton_quit) - self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) self.horizontalLayout_3.addItem(self.horizontalSpacer) @@ -130,7 +130,14 @@ class Ui_status_page(object): self.horizontalLayout_2.addWidget(self.label_version_check) - self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.label_error_icon = QLabel(self.frame) + self.label_error_icon.setObjectName(u"label_error_icon") + self.label_error_icon.setMaximumSize(QSize(20, 16777215)) + self.label_error_icon.setStyleSheet(u"QToolTip { color: white; }") + + self.horizontalLayout_2.addWidget(self.label_error_icon) + + self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) self.horizontalLayout_2.addItem(self.horizontalSpacer_2) @@ -202,7 +209,7 @@ class Ui_status_page(object): self.horizontalLayout.setContentsMargins(-1, 6, -1, 6) self.pushButton_open_dir = QPushButton(status_page) self.pushButton_open_dir.setObjectName(u"pushButton_open_dir") - sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.pushButton_open_dir.sizePolicy().hasHeightForWidth()) @@ -264,6 +271,7 @@ class Ui_status_page(object): #endif // QT_CONFIG(tooltip) self.pushButton_stop.setText(QCoreApplication.translate("status_page", u"Stop", None)) self.label_version_check.setText("") + self.label_error_icon.setText("") #if QT_CONFIG(tooltip) self.label_free_space.setToolTip(QCoreApplication.translate("status_page", u"Free Space", None)) #endif // QT_CONFIG(tooltip) diff --git a/src/workers.py b/src/workers.py index 7f36caf..59e4a19 100644 --- a/src/workers.py +++ b/src/workers.py @@ -92,6 +92,9 @@ class WorkerThread(QThread): "account_type": "", } + # Track pending error for multi-line error messages + self.pending_error = None + self.profile_status["status_message"] = "OneDrive sync is starting..." self.update_profile_status.emit(self.profile_status, self.profile_name) @@ -119,11 +122,54 @@ class WorkerThread(QThread): if time.time() > timeout: break + def _emit_error_status(self, full_error_message): + """Format and emit error message with truncation and line splitting.""" + # Format for status message: max 50 chars per line, up to 3 lines (150 chars total) + max_chars_per_line = 50 + max_lines = 3 + max_total_chars = max_chars_per_line * max_lines + + if len(full_error_message) > max_total_chars: + # Truncate to 150 chars total + display_text = full_error_message[: max_total_chars - 3] + "..." + else: + display_text = full_error_message + + # Split into lines of max 50 characters + lines = [] + words = display_text.split() + current_line = "" + + for word in words: + if len(current_line) + len(word) + 1 <= max_chars_per_line: + current_line += word + " " + else: + if current_line: + lines.append(current_line.strip()) + current_line = word + " " + if len(lines) >= max_lines: + break + + if current_line and len(lines) < max_lines: + lines.append(current_line.strip()) + + truncated_error = "\n".join(lines) + + self.profile_status["status_message"] = f"Error: {truncated_error}" + self.profile_status["error_message"] = full_error_message # Full error for tooltip + self.update_profile_status.emit(self.profile_status, self.profile_name) + def read_stdout(self): stdout = self.onedrive_process.stdout.readline().strip() if stdout != "": logging.info(f"[{self.profile_name}] " + stdout) + # Check if we have a pending error from previous line that wasn't followed by "Error Message:" + if self.pending_error and "Error Message:" not in stdout: + # Emit the pending error since the next line didn't continue it + self._emit_error_status(self.pending_error) + self.pending_error = None + if "Calling Function: testNetwork()" in stdout: self.profile_status["status_message"] = "Cannot connect to Microsoft OneDrive Service" self.update_profile_status.emit(self.profile_status, self.profile_name) @@ -274,9 +320,9 @@ class WorkerThread(QThread): self.profile_status["status_message"] = "Cannot connect to Microsoft OneDrive Service." self.update_profile_status.emit(self.profile_status, self.profile_name) - elif "application is already running" in stdout: - self.profile_status["status_message"] = """OneDrive is already running outside OneDriveGUI!\nPlease stop it first.""" - self.update_profile_status.emit(self.profile_status, self.profile_name) + # elif "application is already running" in stdout: + # self.profile_status["status_message"] = """OneDrive is already running outside OneDriveGUI!\nPlease stop it first.""" + # self.update_profile_status.emit(self.profile_status, self.profile_name) elif "command not found" in stdout: logging.error( @@ -298,6 +344,27 @@ class WorkerThread(QThread): self.update_profile_status.emit(self.profile_status, self.profile_name) self.update_credentials.emit(self.profile_name) + elif "Error Message:" in stdout: + # Check if this is a continuation of a previous ERROR: line + if self.pending_error: + error_parts = stdout.split("Error Message:", 1) + if len(error_parts) > 1: + additional_error = error_parts[1].strip() + # Combine with pending error + full_error_message = f"{self.pending_error} {additional_error}" + else: + full_error_message = self.pending_error + self.pending_error = None # Clear pending error + self._emit_error_status(full_error_message) + + elif "ERROR:" in stdout: + # Extract error message after "ERROR:" prefix + error_parts = stdout.split("ERROR:", 1) + if len(error_parts) > 1: + error_text = error_parts[1].strip() + # Store as pending error to check if next line has "Error Message:" + self.pending_error = error_text + else: # logging.debug(f"No rule matched: {stdout}") pass