diff --git a/src/damaris/gui/DamarisGUI.py b/src/damaris/gui/DamarisGUI.py index ba409c1..ab06404 100644 --- a/src/damaris/gui/DamarisGUI.py +++ b/src/damaris/gui/DamarisGUI.py @@ -641,6 +641,7 @@ class DamarisGUI: res_code, backend, self.spool_dir, + log_callback=self.log, clear_jobs=actual_config[ "del_jobs_after_execution" ], clear_results=actual_config[ "del_results_after_processing" ] ) logger.debug("ScriptInterface creation took %.3fs", time.time() - script_interface_start) @@ -3957,7 +3958,7 @@ class ScriptInterface: """ def __init__( self, exp_script=None, res_script=None, backend_executable=None, spool_dir="spool", clear_jobs=True, - clear_results=True ): + clear_results=True, log_callback=None ): """ run experiment scripts and result scripts """ @@ -3969,6 +3970,7 @@ class ScriptInterface: self.clear_jobs = clear_jobs self.clear_results = clear_results self.exp_handling = self.res_handling = None + self._log_callback = log_callback self.exp_writer = self.res_reader = self.back_driver = None if self.backend_executable is not None and self.backend_executable != "": @@ -3997,9 +3999,9 @@ class ScriptInterface: # get script engines self.exp_handling = self.res_handling = None if self.exp_script and self.exp_writer: - self.exp_handling = ExperimentHandling.ExperimentHandling( self.exp_script, self.exp_writer, self.data ) + self.exp_handling = ExperimentHandling.ExperimentHandling( self.exp_script, self.exp_writer, self.data, self._log_callback ) if self.res_script and self.res_reader: - self.res_handling = ResultHandling.ResultHandling( self.res_script, self.res_reader, self.data ) + self.res_handling = ResultHandling.ResultHandling( self.res_script, self.res_reader, self.data, self._log_callback ) # start them if self.back_driver is not None: diff --git a/src/damaris/gui/ExperimentHandling.py b/src/damaris/gui/ExperimentHandling.py index 8433eac..6823719 100644 --- a/src/damaris/gui/ExperimentHandling.py +++ b/src/damaris/gui/ExperimentHandling.py @@ -13,17 +13,41 @@ logger = logging.getLogger("damaris.experiment_handling") class StopExperiment(Exception): pass +class _SandboxStdout: + """Redirects print() from sandboxed scripts to the GUI textview.""" + def __init__(self, callback): + self._callback = callback + self._buffer = "" + + def write(self, message): + self._buffer += message + if "\n" in self._buffer: + lines = self._buffer.split("\n") + self._buffer = lines[-1] + for line in lines[:-1]: + self._callback(line + "\n") + + def flush(self): + if self._buffer: + self._callback(self._buffer) + self._buffer = "" + + def isatty(self): + return False + + class ExperimentHandling(threading.Thread): """ runs the experiment script in sandbox """ - def __init__(self, script, exp_writer, data): + def __init__(self, script, exp_writer, data, log_callback=None): threading.Thread.__init__(self, name="experiment handler") self.script=script self.writer=exp_writer self.data=data self.quit_flag = threading.Event() + self._log_callback = log_callback if self.data is not None: self.data["__recentexperiment"]=-1 @@ -53,6 +77,13 @@ class ExperimentHandling(threading.Thread): self.location = None exp_iterator=None + # Redirect stdout/stderr so print() in sandboxed scripts goes to the GUI + _orig_stdout = sys.stdout + _orig_stderr = sys.stderr + if self._log_callback is not None: + sys.stdout = _SandboxStdout(self._log_callback) + sys.stderr = _SandboxStdout(self._log_callback) + # check for time.sleep() try: tree = ast.parse(self.script) @@ -150,3 +181,7 @@ class ExperimentHandling(threading.Thread): dataspace=None self.exp_iterator=None self.writer=None + + # Restore stdout/stderr + sys.stdout = _orig_stdout + sys.stderr = _orig_stderr diff --git a/src/damaris/gui/ResultHandling.py b/src/damaris/gui/ResultHandling.py index 28c1476..7ac08a2 100644 --- a/src/damaris/gui/ResultHandling.py +++ b/src/damaris/gui/ResultHandling.py @@ -7,7 +7,7 @@ import traceback import logging import ast from damaris.data import Resultable -from damaris.gui.ExperimentHandling import StopExperiment +from damaris.gui.ExperimentHandling import StopExperiment, _SandboxStdout logger = logging.getLogger("damaris.result_handling") @@ -16,16 +16,24 @@ class ResultHandling(threading.Thread): runs the result script in sandbox """ - def __init__(self, script_data, result_iterator, data_pool): + def __init__(self, script_data, result_iterator, data_pool, log_callback=None): threading.Thread.__init__(self,name="result handler") self.script=script_data self.results=result_iterator self.data_space=data_pool self.quit_flag=self.results.quit_flag + self._log_callback = log_callback if self.data_space is not None: self.data_space["__recentresult"]=-1 def run(self): + # Redirect stdout/stderr so print() in sandboxed scripts goes to the GUI + _orig_stdout = sys.stdout + _orig_stderr = sys.stderr + if self._log_callback is not None: + sys.stdout = _SandboxStdout(self._log_callback) + sys.stderr = _SandboxStdout(self._log_callback) + # execute it dataspace={} data_classes = __import__('damaris.data', dataspace, dataspace, ['*']) @@ -88,6 +96,10 @@ class ResultHandling(threading.Thread): traceback_file=None dataspace=None + # Restore stdout/stderr + sys.stdout = _orig_stdout + sys.stderr = _orig_stderr + def sleep(self, seconds): self.quit_flag.wait(seconds) if self.quit_flag.isSet(): diff --git a/src/damaris/gui/script_interface.py b/src/damaris/gui/script_interface.py index 029cf1a..19153a9 100644 --- a/src/damaris/gui/script_interface.py +++ b/src/damaris/gui/script_interface.py @@ -29,12 +29,13 @@ def some_listener(event): class ScriptInterface: - def __init__(self, exp_script=None, res_script=None, backend_executable=None, spool_dir="spool"): + def __init__(self, exp_script=None, res_script=None, backend_executable=None, spool_dir="spool", log_callback=None): self.exp_script=exp_script self.res_script=res_script self.backend_executable=backend_executable self.spool_dir=os.path.abspath(spool_dir) self.exp_handling=self.res_handling=None + self._log_callback = log_callback self.exp_writer=self.res_reader=self.back_driver=None if self.backend_executable is not None: @@ -52,9 +53,9 @@ class ScriptInterface: def runScripts(self): # get script engines if self.exp_script and self.exp_writer: - self.exp_handling=ExperimentHandling.ExperimentHandling(self.exp_script, self.exp_writer, self.data) + self.exp_handling=ExperimentHandling.ExperimentHandling(self.exp_script, self.exp_writer, self.data, self._log_callback) if self.res_script and self.res_reader: - self.res_handling=ResultHandling.ResultHandling(self.res_script, self.res_reader, self.data) + self.res_handling=ResultHandling.ResultHandling(self.res_script, self.res_reader, self.data, self._log_callback) # start them if self.exp_handling: self.exp_handling.start()