|
| 1 | +import sys |
| 2 | +import logging |
| 3 | +from typing import List, Dict, Optional |
| 4 | +from pathlib import Path |
| 5 | + |
| 6 | +from PySide6.QtCore import Slot, QProcess, QStandardPaths |
| 7 | + |
| 8 | +log = logging.getLogger('') |
| 9 | + |
| 10 | + |
| 11 | +class ExternalProcess: |
| 12 | + def __init__(self, proc_name, base_args): |
| 13 | + self.proc_name = proc_name |
| 14 | + self.base_args = base_args |
| 15 | + |
| 16 | + self.stdout_text = '' |
| 17 | + self.stderr_text = '' |
| 18 | + self.state = QProcess.NotRunning |
| 19 | + |
| 20 | + self.qproc: Optional[QProcess] = None |
| 21 | + |
| 22 | + def start(self, extra_args: List[str], finish_signal: Optional): |
| 23 | + self.qproc = QProcess() |
| 24 | + self.qproc.setProgram(self.proc_name) |
| 25 | + self.qproc.setArguments(self.base_args) |
| 26 | + |
| 27 | + # signals |
| 28 | + self.qproc.readyReadStandardOutput.connect(self.handle_stdout) |
| 29 | + self.qproc.readyReadStandardError.connect(self.handle_stderr) |
| 30 | + self.qproc.stateChanged.connect(self.handle_state) |
| 31 | + |
| 32 | + all_args = self.base_args + extra_args |
| 33 | + self.qproc.setArguments(all_args) |
| 34 | + self.qproc.finished.connect(finish_signal) |
| 35 | + log.info(f'Starting {self.proc_name} with args: {all_args}') |
| 36 | + self.qproc.start() |
| 37 | + # Not sure why, but the process doesn't start without these |
| 38 | + proc_started = self.qproc.waitForStarted(10 * 1000) |
| 39 | + if not proc_started: |
| 40 | + log.warning(f'{self.proc_name}:did not start') |
| 41 | + # Basically infinite timeout to wait for the process to finish |
| 42 | + proc_finished = self.qproc.waitForFinished(999 * 60 * 1000) |
| 43 | + if not proc_finished: |
| 44 | + log.warning(f'{self.proc_name}:did not finish') |
| 45 | + self.cleanup() |
| 46 | + |
| 47 | + def cleanup(self): |
| 48 | + self.stdout_text = '' |
| 49 | + self.stderr_text = '' |
| 50 | + self.qproc.deleteLater() |
| 51 | + |
| 52 | + @Slot() |
| 53 | + def handle_stderr(self): |
| 54 | + data = self.qproc.readAllStandardError() |
| 55 | + stderr = bytes(data).decode("utf8") |
| 56 | + self.stderr_text += stderr |
| 57 | + log.error(stderr) |
| 58 | + |
| 59 | + @Slot() |
| 60 | + def handle_stdout(self): |
| 61 | + data = self.qproc.readAllStandardOutput() |
| 62 | + stdout = bytes(data).decode("utf8") |
| 63 | + self.stdout_text += stdout |
| 64 | + log.info(stdout) |
| 65 | + |
| 66 | + @Slot() |
| 67 | + def handle_state(self, state): |
| 68 | + state_name = { |
| 69 | + QProcess.NotRunning: 'Not running', |
| 70 | + QProcess.Starting: 'Starting', |
| 71 | + QProcess.Running: 'Running', |
| 72 | + }.get(state) |
| 73 | + self.state = state |
| 74 | + log.info(f'{self.proc_name}:State changed: {state_name}') |
| 75 | + |
| 76 | + |
| 77 | +# Global dict to be modified after we can verify the external process exists |
| 78 | +external_processes: Dict[str, ExternalProcess] = {} |
| 79 | + |
| 80 | + |
| 81 | +def add_external_process(proc_name: str, prog_name: str, base_args: List[str]): |
| 82 | + log.debug(f'Adding external process {proc_name} ({prog_name}, {base_args})') |
| 83 | + |
| 84 | + exe_path = QStandardPaths.findExecutable(prog_name) |
| 85 | + log.debug(f'Path is {exe_path}') |
| 86 | + if exe_path == '': |
| 87 | + log.fatal(f'Could not find {proc_name}!') |
| 88 | + sys.exit(1) |
| 89 | + |
| 90 | + external_processes[proc_name] = ExternalProcess(prog_name, base_args) |
| 91 | + |
| 92 | + |
| 93 | +main_script_parent_dir: Optional[Path] = None |
| 94 | + |
| 95 | + |
| 96 | +def get_install_dir(cache=True) -> Path: |
| 97 | + global main_script_parent_dir |
| 98 | + if main_script_parent_dir is None or not cache: |
| 99 | + main_script_path = Path(__file__) |
| 100 | + main_script_parent_dir = main_script_path.parent.parent.resolve() |
| 101 | + log.debug(f'Install directory is {main_script_parent_dir}') |
| 102 | + return main_script_parent_dir |
0 commit comments