import inspect
from traceback import format_exc
-from qtsalome import QApplication, pyqtSignal, QThread, Qt, QTimer
+from qtsalome import QApplication, Qt, QTimer
from .geomrepairadv_logger import logger
+from .geomrepairadv_worker_messages import MessageHandlerFactory
+from multiprocessing import Process, Pipe
+import CORBA
-class Worker(QThread):
+
+class Worker(Process):
"""
- Creates a tread to run a given target function with a progress dialog as a parent.
+ Creates a process to run a given target function with a progress dialog as a parent.
"""
- progress_update = pyqtSignal(int)
- thread_failed = pyqtSignal()
- work_completed = pyqtSignal()
-
def __init__(self, parent=None, target=None, args=None):
- super().__init__(parent)
+ super().__init__()
- # Set target function and it's arguments
+ # Set target function and its arguments
self.target = target
self.args = args
- # Update a progress bar each time we receive an update signal
- self.progress_update.connect(parent.setValue)
- self.thread_failed.connect(parent.on_failed)
- self.work_completed.connect(parent.on_completed)
+ # Create a pipe for communication
+ self.parent_conn, self.child_conn = Pipe()
+
+ # Set a timer to handle signals non-blocking way
+ self.signals_timer = QTimer()
+ self.signals_timer.timeout.connect(self.handle_signals)
+ self.signals_timer.start(1000)
+
+ # Create a message handler factory
+ self.message_handler_factory = MessageHandlerFactory()
+
+ # Store parent for signal handling
+ self.parent = parent
# Calculate total amount of lines in executed function
source_lines = inspect.getsourcelines(target)
first_line = source_lines[1]
# Set a progress emitter to update the progress from the target
- self.progress_emitter = ProgressEmitter(self.progress_update, total_lines, first_line)
+ self.progress_emitter = ProgressEmitter(self.child_conn, total_lines, first_line)
# Set a variable for result
self.result = None
"""
try:
- # Wait mode cursor
- QApplication.setOverrideCursor(Qt.WaitCursor)
-
self.result = self.target(self.args, self.progress_emitter)
# Reset the progress when finished
- self.progress_update.emit(100)
+ self.child_conn.send(('progress', 100))
if not self.is_canceled():
- self.work_completed.emit()
+ self.child_conn.send(('completed', self.result))
except Exception:
logger.error(format_exc())
- self.thread_failed.emit()
-
- finally:
- QApplication.restoreOverrideCursor()
+ self.child_conn.send(('failed',))
def is_canceled(self):
# can return from the running function gracefully.
self.progress_emitter.stop()
- # Termination doesn't call a final block inside run()
- QApplication.restoreOverrideCursor()
-
# Set a timer for a case when the running function
# doesn't use emitter or does so heavy job that it would take
# a lot of time for waiting and we need to terminate it.
"""
# Check if the job is still running and need to be terminated
- if self.isRunning():
- logger.warning('Thread will be terminated!')
+ if self.is_alive():
+ logger.warning('Process will be terminated!')
+
super().terminate()
+ self.on_finished()
+
+
+ def on_finished(self):
+ """
+ Called when the execution is finished.
+ """
+
+ logger.debug('on_finished called')
+
+ QApplication.restoreOverrideCursor()
+ self.signals_timer.stop()
+
+ self.join()
+
def get_result(self):
"""
return self.result
+ def start(self):
+ """
+ Starts the process and sets up the communication with the parent.
+ """
+ super().start()
+ self.handle_signals()
+
+ QApplication.setOverrideCursor(Qt.WaitCursor)
+
+
+ def handle_signals(self):
+ """
+ Handles signals from the child process.
+ """
+
+ logger.debug('handle_signals called')
+
+ if not self.parent_conn.poll():
+ return
+
+ msg = self.parent_conn.recv()
+ logger.debug('msg: %s', msg)
+
+ # Get a handler for the message type
+ handler = self.message_handler_factory.get_handler(msg[0])
+ if handler:
+ handler.handle(msg, self)
+ else:
+ logger.error('No handler for message type: %s', msg[0])
+
+
class ProgressEmitter():
"""
Helper class to reduce code repetition while update progress
progress_value = (line - self.first_line) / self.progress_percent
logger.debug('progress_value: %d', progress_value)
- self.progress_update.emit(int(progress_value))
+ self.progress_update.send(['progress', int(progress_value)])
return self.is_running
--- /dev/null
+# -*- coding: utf-8 -*-
+# Copyright (C) 2014-2024 EDF
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# See https://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+# Author : Konstantin Leontev (OpenCascade S.A.S)
+
+class MessageHandler:
+ """
+ MessageHandler is a base class for handling messages
+ between child and worker processes in the progress bar Worker class.
+ """
+
+ def handle(self, msg, worker):
+ """
+ Handle the given message.
+
+ This method should be implemented by subclasses to define how to process
+ the given message.
+
+ Args:
+ msg: The message to be handled.
+ worker: The worker context or object related to the message.
+
+ Raises:
+ NotImplementedError: If the method is not implemented by a subclass.
+ """
+
+ raise NotImplementedError("Subclasses should implement this method")
+
+
+class ProgressHandler(MessageHandler):
+ """
+ ProgressHandler updates progress bar with a value from a message.
+ """
+
+ def handle(self, msg, worker):
+ """
+ Sets a new value for a worker progress bar.
+
+ Args:
+ msg (tuple): A tuple where the second element is the value to be set.
+ worker (object): The worker object that has a setValue method.
+ """
+
+ worker.parent.setValue(msg[1])
+
+
+class CompletedHandler(MessageHandler):
+ """
+ CompletedHandler handles the completion of the worker process.
+ """
+
+ def handle(self, msg, worker):
+ """
+ Handles the given message by performing actions on the worker object.
+
+ Args:
+ msg: The message to handle.
+ worker: The worker object that contains the methods to be called.
+ """
+
+ worker.result = msg[1]
+
+ worker.parent.on_completed()
+ worker.on_finished()
+
+
+class FailedHandler(MessageHandler):
+ """
+ FailedHandler handles the failure of the worker process.
+ """
+
+ def handle(self, msg, worker):
+ """
+ Handles the given message by invoking the worker's failure handler,
+ stopping the timer, and joining the worker thread.
+
+ Args:
+ msg: The message to handle.
+ worker: The worker object that contains the failure handler, timer, and worker thread.
+ """
+
+ worker.parent.on_failed()
+ worker.on_finished()
+
+
+class MessageHandlerFactory:
+ """
+ MessageHandlerFactory creates a handler for a given message type.
+ """
+
+ def __init__(self):
+ self.handlers = {
+ 'progress': ProgressHandler(),
+ 'completed': CompletedHandler(),
+ 'failed': FailedHandler()
+ }
+
+ def get_handler(self, msg_type):
+ """
+ Returns a handler for the given message type.
+ """
+
+ return self.handlers.get(msg_type, None)