예제 #1
0
파일: scans2pdfgui.py 프로젝트: Unrud/djpdf
    def save(self, url):
        self._saving = True
        self.savingChanged.emit()
        self._savingProgress = 0
        self.savingProgressChanged.emit()
        p = QProcess(self)
        p.setProcessChannelMode(QProcess.ForwardedErrorChannel)

        stdout_buffer = b""

        def ready_read_stdout():
            nonlocal stdout_buffer
            stdout_buffer += p.readAllStandardOutput().data()
            *messages, stdout_buffer = stdout_buffer.split(b"\n")
            for message in messages:
                progress = json.loads(messages[-1].decode())
                self._savingProgress = progress["fraction"]
                self.savingProgressChanged.emit()
        p.readyReadStandardOutput.connect(ready_read_stdout)
        p.finished.connect(self._process_finished)
        args = ["-c", "from djpdf.scans2pdf import main; main()",
                os.path.abspath(url.toLocalFile())]
        if self._verbose:
            args.append("--verbose")
        p.start(sys.executable, args)
        self._process = p
        p.write(json.dumps([p._data for p in self._pages]).encode())
        p.closeWriteChannel()
    def execute(self, command, parsers=no_parsers):
        """
        Enqueue a worker to run (at some point) by passing it to the QThreadPool.
        """

        job_id = uuid.uuid4().hex

        # By default, the signals do not have access to any information about
        # the process that sent it. So we use this constructor to annotate
        #  each signal with a job_id.

        def fwd_signal(target):
            return lambda *args: target(job_id, *args)

        self._parsers[job_id] = parsers

        # Set default status to waiting, 0 progress.
        self._state[job_id] = DEFAULT_STATE.copy()

        p = QProcess()
        p.readyReadStandardOutput.connect(fwd_signal(self.handle_output))
        p.readyReadStandardError.connect(fwd_signal(self.handle_output))
        p.stateChanged.connect(fwd_signal(self.handle_state))
        p.finished.connect(fwd_signal(self.done))

        self._jobs[job_id] = p

        p.start(command)

        self.layoutChanged.emit()
예제 #3
0
class EmcExe_Qt(EmcExe):
    """ PySide2 implementation of the EmcExec """
    def __init__(self, *args, **kargs):
        super().__init__(*args, **kargs)

        self._proc = QProcess()
        self._proc.errorOccurred.connect(self._error_cb)

        if self._done_cb:
            self._proc.finished.connect(self._finished_cb)

        if self._grab_output:
            self._proc.readyReadStandardOutput.connect(self._stdout_cb)

        if self._params:
            self._proc.start(self._cmd, self._params)
        else:
            self._proc.start(self._cmd)

    def delete(self) -> None:
        super().delete()
        if self._proc and self._proc.state() == QProcess.Running:
            self._proc.kill()
        self._proc = None

    def _finished_cb(self, exit_code):
        self._call_user_callback(exit_code)

    def _error_cb(self, error):
        if self._proc and not self.deleted:
            self._call_user_callback(-1)

    def _stdout_cb(self):
        if self._proc and not self.deleted:
            self._out_buffer.append(self._proc.readAllStandardOutput().data())
예제 #4
0
class Installer:
    def __init__(self, output_widget: QTextEdit = None):

        # create install process
        self._output_widget = None
        self.process = QProcess()
        self.process.setProgram(sys.executable)
        self.process.setProcessChannelMode(QProcess.MergedChannels)
        self.process.readyReadStandardOutput.connect(self._on_stdout_ready)
        # setup process path
        env = QProcessEnvironment()
        combined_paths = os.pathsep.join(
            [site.getsitepackages()[0], env.systemEnvironment().value("PYTHONPATH")]
        )
        env.insert("PATH", QProcessEnvironment.systemEnvironment().value("PATH"))
        env.insert("PYTHONPATH", combined_paths)
        self.process.setProcessEnvironment(env)
        self.set_output_widget(output_widget)

    def set_output_widget(self, output_widget: QTextEdit):
        if output_widget:
            self._output_widget = output_widget
            self.process.setParent(output_widget)

    def _on_stdout_ready(self):
        if self._output_widget:
            text = self.process.readAllStandardOutput().data().decode()
            self._output_widget.append(text)

    def install(self, pkg_list):
        cmd = ["-m", "pip", "install", "--upgrade"]
        self.process.setArguments(cmd + pkg_list)
        if self._output_widget:
            self._output_widget.clear()
        self.process.start()
예제 #5
0
    def run_command(self, cmd: str):
        log.info(
            'run_command',
            program=self.program,
            args=self.args,
            cmd=cmd
        )
        self.output.append(f'> {cmd}\n')
        self.input.clear()

        process = QProcess()
        process.setProgram(self.program)
        process.setCurrentReadChannel(0)

        # noinspection PyUnresolvedReferences
        process.readyReadStandardError.connect(
            lambda: self.handle_error(process)
        )
        # noinspection PyUnresolvedReferences
        process.readyReadStandardOutput.connect(
            lambda: self.handle_output(process)
        )

        connect_args = list(self.args)

        args = cmd.split(' ')
        if args[0] == self.program.split('/')[-1]:
            args.pop(0)
        process.setArguments(connect_args + args)
        process.start()
예제 #6
0
파일: scans2pdfgui.py 프로젝트: Unrud/djpdf
    def save(self, url):
        self._saving = True
        self.savingChanged.emit()
        self._savingProgress = 0
        self.savingProgressChanged.emit()
        p = QProcess(self)
        p.setProcessChannelMode(QProcess.ForwardedErrorChannel)

        stdout_buffer = b""

        def ready_read_stdout():
            nonlocal stdout_buffer
            stdout_buffer += p.readAllStandardOutput().data()
            *messages, stdout_buffer = stdout_buffer.split(b"\n")
            for message in messages:
                progress = json.loads(messages[-1].decode())
                self._savingProgress = progress["fraction"]
                self.savingProgressChanged.emit()
        p.readyReadStandardOutput.connect(ready_read_stdout)

        def finished(status):
            self._saving = False
            self.savingChanged.emit()
            if status != 0:
                self.savingError.emit()
        p.finished.connect(finished)
        args = ["-c", "from djpdf.scans2pdf import main; main()",
                os.path.abspath(url.toLocalFile())]
        if self._verbose:
            args.append("--verbose")
        p.start(sys.executable, args)
        p.write(json.dumps([p._data for p in self._pages]).encode())
        p.closeWriteChannel()
예제 #7
0
def runProcess(command, arguments):
    process = QProcess()
    process.start(command, arguments)
    process.waitForFinished()
    result = []
    for line in str(process.readAllStandardOutput()).split(os.linesep):
        result.append(line)
    return result
예제 #8
0
 def testNoArgs(self):
     '''Connecting a lambda to a signal without arguments'''
     proc = QProcess()
     dummy = Dummy()
     QObject.connect(proc, SIGNAL('started()'),
                     lambda: setattr(dummy, 'called', True))
     proc.start(sys.executable, ['-c', '""'])
     proc.waitForFinished()
     self.assert_(dummy.called)
예제 #9
0
 def testWithArgs(self):
     '''Connecting a lambda to a signal with arguments'''
     proc = QProcess()
     dummy = Dummy()
     QObject.connect(proc, SIGNAL('finished(int)'),
                     lambda x: setattr(dummy, 'called', x))
     proc.start(sys.executable, ['-c', '""'])
     proc.waitForFinished()
     self.assertEqual(dummy.called, proc.exitCode())
예제 #10
0
 def testNoArgs(self):
     '''Connecting a lambda to a signal without arguments'''
     proc = QProcess()
     dummy = Dummy()
     QObject.connect(proc, SIGNAL('started()'),
                     lambda: setattr(dummy, 'called', True))
     proc.start(sys.executable, ['-c', '""'])
     proc.waitForFinished()
     self.assertTrue(dummy.called)
예제 #11
0
 def testWithArgs(self):
     '''Connecting a lambda to a signal with arguments'''
     proc = QProcess()
     dummy = Dummy()
     QObject.connect(proc, SIGNAL('finished(int)'),
                     lambda x: setattr(dummy, 'called', x))
     proc.start(sys.executable, ['-c', '""'])
     proc.waitForFinished()
     self.assertEqual(dummy.called, proc.exitCode())
예제 #12
0
class MotivoAccess:

    motivoDirectory = MOTIVO_DIR

    def __init__(self):

        self.motivoProcess = QProcess()

        self.buildProcess = QProcess()
        self.mergeProcess = QProcess()
        self.sampleProcess = QProcess()

        self.graphProcess = QProcess()

        # TODO check motivo directory and produce an error message if needed
        self.motivoProcess.setWorkingDirectory(MotivoAccess.motivoDirectory +
                                               '/scripts/')
        self.motivoProcess.setProgram('motivo.sh')
        # Qproces emit readReady signal when data is ready to be read
        # Connect the handler for read the data

        self.buildProcess.setWorkingDirectory(MotivoAccess.motivoDirectory +
                                              '/build/bin/')
        self.buildProcess.setProgram('motivo-build')

        self.mergeProcess.setWorkingDirectory(MotivoAccess.motivoDirectory +
                                              '/build/bin/')
        self.mergeProcess.setProgram('motivo-merge')

        self.sampleProcess.setWorkingDirectory(MotivoAccess.motivoDirectory +
                                               '/build/bin/')
        self.sampleProcess.setProgram('motivo-sample')

        self.graphProcess.setWorkingDirectory(MotivoAccess.motivoDirectory +
                                              '/build/bin/')
        self.graphProcess.setProgram('motivo-graph')

    def runMotivo(self, motivoArguments):
        self.motivoProcess.setArguments(motivoArguments)
        self.motivoProcess.start()

    def runBuild(self, buildArguments):
        self.buildProcess.setArguments(buildArguments)
        self.buildProcess.start()

    def runMerge(self, mergeArguments):
        self.mergeProcess.setArguments(mergeArguments)
        self.mergeProcess.start()

    def runSample(self, sampleArguments):
        self.sampleProcess.setArguments(sampleArguments)
        self.sampleProcess.start()

    def runConvertTxtToBinary(self, convertTxtToBinaryArguments):
        self.graphProcess.setArguments(convertTxtToBinaryArguments)
        self.graphProcess.start()
예제 #13
0
 def start_qt_process(self, cmd):
     """
     启动子进程执行耗时命令
     :param cmd:
     :return:
     """
     process = QProcess(self.ui)
     process.start(cmd)
     result = process.waitForStarted()
     return result
예제 #14
0
    def exit(self):
        self.vivadoProcess.kill()
        vivado_command_to_kill = "cmd.exe /C Taskkill /IM vivado.exe /F"
        process = QProcess()
        process.start(vivado_command_to_kill)
        process.waitForFinished(5000)

        tcp_command_to_kill = "cmd.exe /C netstat -ano | find '0.0.0.0:9955'"

        return
예제 #15
0
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        # Hold process reference.
        self.p = None

        layout = QVBoxLayout()

        self.text = QPlainTextEdit()
        layout.addWidget(self.text)

        self.progress = QProgressBar()
        layout.addWidget(self.progress)

        btn_run = QPushButton("Execute")
        btn_run.clicked.connect(self.start)

        layout.addWidget(btn_run)

        w = QWidget()
        w.setLayout(layout)
        self.setCentralWidget(w)

        self.show()

    def start(self):
        if self.p is not None:
            return

        self.p = QProcess()
        self.p.readyReadStandardOutput.connect(self.handle_stdout)
        self.p.readyReadStandardError.connect(self.handle_stderr)
        self.p.stateChanged.connect(self.handle_state)
        self.p.finished.connect(self.cleanup)
        self.p.start("python", ["dummy_script.py"])

    def handle_stderr(self):
        result = bytes(self.p.readAllStandardError()).decode("utf8")
        progress = simple_percent_parser(result)

        self.progress.setValue(progress)

    def handle_stdout(self):
        result = bytes(self.p.readAllStandardOutput()).decode("utf8")
        data = extract_vars(result)

        self.text.appendPlainText(str(data))

    def handle_state(self, state):
        self.statusBar().showMessage(STATES[state])

    def cleanup(self):
        self.p = None
예제 #16
0
class Form(QPlainTextEdit):
    processFinished = Signal()

    def __init__(self, fileName, parent=None):
        super(Form, self).__init__(parent)
        self._fileName = fileName

        self.setStyleSheet("font-family: Source Code Pro; font-size: 16px; ")

        self._process = QProcess()
        self._process.readyReadStandardOutput.connect(self._processStdOut)
        self._process.readyReadStandardError.connect(self._processStdErr)
        self._process.finished.connect(self._processFinished)

    def run(self, args):
        self._append("> " + shlex.join(args.args) + "\n")
        self._lastPos = 0
        self._process.setWorkingDirectory(args.dir)
        self._process.setProgram(args.args[0])
        self._process.setArguments(args.args[1:])
        self._process.start()

    @Slot()
    def putTextToFile(self):
        open(self._fileName, "w").write(self.toPlainText())

    def keyPressEvent(self, event):
        k = event.key()
        if not self.textCursor().position() < self._lastPos:
            super().keyPressEvent(event)
        if k == Qt.Key_Return or k == Qt.Key_Enter:
            self._process.write(
                bytes(self.toPlainText()[self._lastPos:], "utf-8"))
            self._lastPos = self.textCursor().position()

    def _processStdOut(self):
        self._append(str(self._process.readAllStandardOutput(), "utf-8"))

    def _processStdErr(self):
        self._append(str(self._process.readAllStandardError(), "utf-8"))

    def _append(self, output):
        self.moveCursor(QTextCursor.End)
        self.insertPlainText(output)
        self.moveCursor(QTextCursor.End)
        self._lastPos = self.textCursor().position()

    def _processFinished(self, exitCode):
        self._append("\nProcess Finished with exit code: " + str(exitCode) +
                     "\n")
        self.processFinished.emit()
예제 #17
0
class Example(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.setWindowTitle('QProcess')
        self.setGeometry(100, 100, 600, 300)
        self.show()

    def initUI(self):
        tool_run = QToolButton()
        tool_run.setText('Run')
        tool_run.clicked.connect(self.callProgram)

        toolbar = QToolBar()
        self.addToolBar(toolbar)
        toolbar.addWidget(tool_run)

        self.output = QTextEdit()
        self.setCentralWidget(self.output)

        # QProcess object for external app
        self.process = QProcess(self)
        # QProcess emits `readyRead` when there is data to be read
        self.process.readyRead.connect(self.dataReady)

        # Just to prevent accidentally running multiple times
        # Disable the button when process starts, and enable it when it finishes
        self.process.started.connect(lambda: tool_run.setEnabled(False))
        self.process.finished.connect(lambda: tool_run.setEnabled(True))

    def dataReady(self):
        cursor = self.output.textCursor()
        cursor.movePosition(cursor.End)
        content = self.process.readAllStandardOutput()
        cursor.insertText(str(content, 'utf-8'))
        self.output.ensureCursorVisible()

    def callProgram(self):
        # run the process
        # `start` takes the exec and a list of arguments
        if os.name == 'nt':
            self.process.start('ping', ['127.0.0.1'])
        else:
            self.process.start('ping', ['-c', '4', '127.0.0.1'])
예제 #18
0
    def testWithoutArgs(self):
        '''Connect QProcess.started() to QTimeLine.togglePaused()'''
        process = QProcess()
        timeline = QTimeLine()

        QObject.connect(process, SIGNAL('finished(int, QProcess::ExitStatus)'),
                        timeline, SLOT('toggleDirection()'))

        orig_dir = timeline.direction()

        process.start(sys.executable, ['-c', '"print 42"'])
        process.waitForFinished()

        new_dir = timeline.direction()

        if orig_dir == QTimeLine.Forward:
            self.assertEqual(new_dir, QTimeLine.Backward)
        else:
            self.assertEqual(new_dir, QTimeLine.Forward)
예제 #19
0
    def testWithoutArgs(self):
        '''Connect QProcess.started() to QTimeLine.togglePaused()'''
        process = QProcess()
        timeline = QTimeLine()

        QObject.connect(process, SIGNAL('finished(int, QProcess::ExitStatus)'),
                        timeline, SLOT('toggleDirection()'))

        orig_dir = timeline.direction()

        process.start(sys.executable, ['-c', '"print 42"'])
        process.waitForFinished()

        new_dir = timeline.direction()

        if orig_dir == QTimeLine.Forward:
            self.assertEqual(new_dir, QTimeLine.Backward)
        else:
            self.assertEqual(new_dir, QTimeLine.Forward)
예제 #20
0
 def run_handler(self):
     self.py_ = G.config.installed_apps[self.pack_name].get('py_')
     if not self.py_:
         QMessageBox.warning(self.parent, "提示", "未选择Python解释器")
         self.act_setting_slot()
         return
     if not os.path.exists(self.py_) or not os.path.isfile(self.py_):
         QMessageBox.warning(self.parent, "提示", f"{self.py_} 不存在")
         return
     try:
         self.entry_, requirement_ = self.get_build()
     except Exception as e:
         QMessageBox.warning(self.parent, "提示", str(e))
         return
     ##检测依赖
     p = QProcess()
     p.start(' '.join(([self.py_, "-m", 'pip', "freeze"])))
     p.waitForFinished()
     out = p.readAllStandardOutput().data().decode()
     output = out.splitlines()
     with open(requirement_, 'r') as f:
         requirement = f.read().splitlines()
     dissatisfy, version_less = diff_pip(output, requirement)
     if dissatisfy:
         msgbox = QMessageBox(self.parent)
         msgbox.setWindowTitle("缺少依赖")
         msgbox.setText("\n".join(dissatisfy[:15]) + "\n...")
         yes = msgbox.addButton('立即安装', QMessageBox.AcceptRole)
         no = msgbox.addButton('稍后', QMessageBox.RejectRole)
         msgbox.setDefaultButton(yes)
         reply = msgbox.exec_()
         if reply == QMessageBox.AcceptRole:
             self.requirement_ = dissatisfy
             self.install_handler()
         return
     # run
     TipDialog("正在启动...")
     cmd = ' '.join([self.py_, self.entry_])
     QProcess().startDetached(cmd)
예제 #21
0
    def run_command(self, command):
        try:
            if self.cli is None or self.cli_args is None:
                self.output_area.append(
                    'Node starting up, please try again later...')
                return False

            self.output_area.append(f'> {command}\n')

            process = QProcess()
            process.setProgram(self.cli)
            process.setCurrentReadChannel(0)

            # noinspection PyUnresolvedReferences
            process.readyReadStandardError.connect(
                lambda: self.handle_cli_error_output(process))

            # noinspection PyUnresolvedReferences
            process.readyReadStandardOutput.connect(
                lambda: self.handle_cli_output(process))

            args = command.split(' ')
            if args[0] == self.cli.split('/')[-1]:
                args.pop(0)
            process.setArguments(self.cli_args + args)
            process.start()

            log.info('run_command',
                     program=self.cli,
                     args=self.cli_args,
                     cmd=command)

            return True
        except Exception:
            self.output_area.append(
                'Node starting up, please try again later...')
            return False
예제 #22
0
    def start_convertion(self):
        """implement conversion task"""
        # check if output dir is already defined else get outdir
        if not self.conversion_started:
            self.get_output_dir()
            self.progressBarTotal.setMaximum(len(self.selected_files))
            self.btnConvert.setEnabled(False)
            self.conversion_started = True

        # setting up arguments
        current_file_name = self.selected_files[self.current_file_idx]
        current_outfile_name = Path(self.output_dir).joinpath(
            self.get_file_name(self.current_file_idx, ".avi"))
        self.process_argument = " -i {} {}".format(current_file_name,
                                                   current_outfile_name)

        # create a process everytime it's called
        # NOTE: this is a local instance of the process so it changes after every call
        process = QProcess()
        process.readyReadStandardError.connect(
            lambda: self.parse_output(process))
        process.finished.connect(self.recursion_handler)
        process.started.connect(lambda: self.ref_process(process))
        process.start("ffmpeg", self.process_argument.split())
class QProcessExecutionManager(ExecutionManager):
    """Class to manage tool instance execution using a PySide2 QProcess."""

    def __init__(self, logger, program=None, args=None, silent=False, semisilent=False):
        """Class constructor.

        Args:
            logger (LoggerInterface): a logger instance
            program (str): Path to program to run in the subprocess (e.g. julia.exe)
            args (list): List of argument for the program (e.g. path to script file)
            silent (bool): Whether or not to emit logger msg signals
        """
        super().__init__(logger)
        self._program = program
        self._args = args
        self._silent = silent  # Do not show Event Log nor Process Log messages
        self._semisilent = semisilent  # Do not show Event Log messages but show Process Log messages
        self.process_failed = False
        self.process_failed_to_start = False
        self._user_stopped = False
        self._process = QProcess(self)
        self.process_output = None  # stdout when running silent
        self.error_output = None  # stderr when running silent

    def program(self):
        """Program getter method."""
        return self._program

    def args(self):
        """Program argument getter method."""
        return self._args

    # noinspection PyUnresolvedReferences
    def start_execution(self, workdir=None):
        """Starts the execution of a command in a QProcess.

        Args:
            workdir (str): Work directory
        """
        if workdir is not None:
            self._process.setWorkingDirectory(workdir)
        self._process.started.connect(self.process_started)
        self._process.finished.connect(self.on_process_finished)
        if not self._silent and not self._semisilent:  # Loud
            self._process.readyReadStandardOutput.connect(self.on_ready_stdout)
            self._process.readyReadStandardError.connect(self.on_ready_stderr)
            self._process.error.connect(self.on_process_error)  # errorOccurred available in Qt 5.6
            self._process.stateChanged.connect(self.on_state_changed)
        elif self._semisilent:  # semi-silent
            self._process.readyReadStandardOutput.connect(self.on_ready_stdout)
            self._process.readyReadStandardError.connect(self.on_ready_stderr)
        self._process.start(self._program, self._args)
        if not self._process.waitForStarted(msecs=10000):  # This blocks until process starts or timeout happens
            self.process_failed = True
            self.process_failed_to_start = True
            self._process.deleteLater()
            self._process = None
            self.execution_finished.emit(-9998)

    def wait_for_process_finished(self, msecs=30000):
        """Wait for subprocess to finish.

        Return:
            True if process finished successfully, False otherwise
        """
        if not self._process:
            return False
        if self.process_failed or self.process_failed_to_start:
            return False
        if not self._process.waitForFinished(msecs):
            self.process_failed = True
            self._process.close()
            self._process = None
            return False
        return True

    @Slot(name="process_started")
    def process_started(self):
        """Run when subprocess has started."""

    @Slot("QProcess::ProcessState", name="on_state_changed")
    def on_state_changed(self, new_state):
        """Runs when QProcess state changes.

        Args:
            new_state (QProcess::ProcessState): Process state number
        """
        if new_state == QProcess.Starting:
            self._logger.msg.emit("\tStarting program <b>{0}</b>".format(self._program))
            arg_str = " ".join(self._args)
            self._logger.msg.emit("\tArguments: <b>{0}</b>".format(arg_str))
        elif new_state == QProcess.Running:
            self._logger.msg_warning.emit(
                "\tExecution is in progress. See Process Log for messages " "(stdout&stderr)"
            )
        elif new_state == QProcess.NotRunning:
            # logging.debug("QProcess is not running")
            pass
        else:
            self._logger.msg_error.emit("Process is in an unspecified state")
            logging.error("QProcess unspecified state: %s", new_state)

    @Slot("QProcess::ProcessError", name="'on_process_error")
    def on_process_error(self, process_error):
        """Run if there is an error in the running QProcess.

        Args:
            process_error (QProcess::ProcessError): Process error number
        """
        if process_error == QProcess.FailedToStart:
            self.process_failed = True
            self.process_failed_to_start = True
        elif process_error == QProcess.Timedout:
            self.process_failed = True
            self._logger.msg_error.emit("Timed out")
        elif process_error == QProcess.Crashed:
            self.process_failed = True
            if not self._user_stopped:
                self._logger.msg_error.emit("Process crashed")
        elif process_error == QProcess.WriteError:
            self._logger.msg_error.emit("Process WriteError")
        elif process_error == QProcess.ReadError:
            self._logger.msg_error.emit("Process ReadError")
        elif process_error == QProcess.UnknownError:
            self._logger.msg_error.emit("Unknown error in process")
        else:
            self._logger.msg_error.emit("Unspecified error in process: {0}".format(process_error))

    def stop_execution(self):
        """See base class."""
        self._logger.msg_error.emit("Terminating process")
        self._user_stopped = True
        self.process_failed = True
        if not self._process:
            return
        try:
            self._process.terminate()
        except Exception as ex:  # pylint: disable=broad-except
            self._logger.msg_error.emit("[{0}] exception when terminating process".format(ex))
            logging.exception("Exception in closing QProcess: %s", ex)
        finally:
            self._process.deleteLater()
            self._process = None

    @Slot(int)
    def on_process_finished(self, exit_code):
        """Runs when subprocess has finished.

        Args:
            exit_code (int): Return code from external program (only valid for normal exits)
        """
        # logging.debug("Error that occurred last: {0}".format(self._process.error()))
        if not self._process:
            return
        exit_status = self._process.exitStatus()  # Normal or crash exit
        if exit_status == QProcess.CrashExit:
            if not self._silent:
                self._logger.msg_error.emit("\tProcess crashed")
            exit_code = -1
        elif exit_status == QProcess.NormalExit:
            pass
        else:
            if not self._silent:
                self._logger.msg_error.emit("Unknown QProcess exit status [{0}]".format(exit_status))
            exit_code = -1
        if not exit_code == 0:
            self.process_failed = True
        if not self._user_stopped:
            out = str(self._process.readAllStandardOutput().data(), "utf-8")
            errout = str(self._process.readAllStandardError().data(), "utf-8")
            if out is not None:
                if not self._silent:
                    self._logger.msg_proc.emit(out.strip())
                else:
                    self.process_output = out.strip()
                    self.error_output = errout.strip()
        else:
            self._logger.msg.emit("*** Terminating process ***")
        # Delete QProcess
        self._process.deleteLater()
        self._process = None
        self.execution_finished.emit(exit_code)

    @Slot(name="on_ready_stdout")
    def on_ready_stdout(self):
        """Emit data from stdout."""
        if not self._process:
            return
        out = str(self._process.readAllStandardOutput().data(), "utf-8")
        self._logger.msg_proc.emit(out.strip())

    @Slot(name="on_ready_stderr")
    def on_ready_stderr(self):
        """Emit data from stderr."""
        if not self._process:
            return
        err = str(self._process.readAllStandardError().data(), "utf-8")
        self._logger.msg_proc_error.emit(err.strip())
예제 #24
0
class Litecoin(object):
    file: ConfigurationFile
    hard_drives: HardDrives
    process: QProcess
    software: LitecoinSoftware
    zmq_block_port: int
    zmq_tx_port: int

    def __init__(self, configuration_file_path: str):
        self.hard_drives = HardDrives()
        self.software = LitecoinSoftware()
        self.file = ConfigurationFile(configuration_file_path)
        self.running = False
        self.process = None

        if self.file['datadir'] is None:
            self.autoconfigure_datadir()

        if 'litecoin.conf' in os.listdir(self.file['datadir']):
            actual_conf_file = os.path.join(self.file['datadir'], 'litecoin.conf')
            log.info(
                'datadir_redirect',
                configuration_file_path=configuration_file_path,
                actual_conf_file=actual_conf_file
            )
            self.file = ConfigurationFile(actual_conf_file)
            if self.file['datadir'] is None:
                self.autoconfigure_datadir()

        if os.path.exists(os.path.join(self.file['datadir'], 'blocks')):
            if self.file['prune'] is None:
                self.set_prune(False)

        self.wallet_paths = self.get_wallet_paths()

        if self.file['server'] is None:
            self.file['server'] = True

        if self.file['disablewallet'] is None and not self.wallet_paths:
            self.file['disablewallet'] = True
        elif self.file['disablewallet'] is None and self.wallet_paths:
            self.file['disablewallet'] = False

        if self.file['timeout'] is None:
            self.file['timeout'] = 6000

        if self.file['rpcuser'] is None:
            self.file['rpcuser'] = '******'

        if self.file['rpcpassword'] is None:
            self.file['rpcpassword'] = get_random_password()

        if self.file['prune'] is None:
            should_prune = self.hard_drives.should_prune(self.file['datadir'], has_litecoin=True)
            self.set_prune(should_prune)

        self.zmq_block_port = get_zmq_port()
        self.zmq_tx_port = get_zmq_port()

        self.file['zmqpubrawblock'] = f'tcp://127.0.0.1:{self.zmq_block_port}'
        self.file['zmqpubrawtx'] = f'tcp://127.0.0.1:{self.zmq_tx_port}'

        # noinspection PyBroadException
        try:
            memory = psutil.virtual_memory()
            free_mb = round(memory.available / 1000000)
            free_mb -= int(free_mb * .3)
            self.file['dbcache'] = free_mb
        except:
            log.warning(
                'dbcache psutil.virtual_memory',
                exc_info=True
            )
            self.file['dbcache'] = 1000

        self.config_snapshot = self.file.snapshot.copy()
        self.file.file_watcher.fileChanged.connect(self.config_file_changed)

        self.process = QProcess()
        self.process.setProgram(self.software.litecoind)
        self.process.setCurrentReadChannel(0)
        self.process.setArguments(self.args)
        self.process.start()

    @property
    def network(self):
        if self.file['testnet']:
            return TESTNET
        return MAINNET

    def get_wallet_paths(self):
        exclude_files = {
            'addr.dat',
            'banlist.dat',
            'fee_estimates.dat',
            'mempool.dat',
            'peers.dat'
        }
        candidate_paths = []
        if self.file['testnet']:
            datadir = os.path.join(self.file['datadir'], 'testnet4')
            wallet_dir = self.file['test.walletdir']
            wallets = self.file['test.wallet']
        else:
            datadir = self.file['datadir']
            wallet_dir = self.file['main.walletdir']
            wallets = self.file['main.wallet']
        for file in os.listdir(datadir):
            if file not in exclude_files:
                path = os.path.join(datadir, file)
                candidate_paths.append(path)
        default_walletdir = os.path.join(datadir, 'wallets')
        if os.path.exists(default_walletdir):
            for file in os.listdir(default_walletdir):
                if file not in exclude_files:
                    candidate_paths.append(
                        os.path.join(default_walletdir, file))
        if wallet_dir is not None:
            for file in os.listdir(wallet_dir):
                if file not in exclude_files:
                    candidate_paths += os.path.join(
                        os.path.join(wallet_dir, file))
        dat_files = [f for f in candidate_paths if f.endswith('.dat')
                     and not f.startswith('blk')]
        dat_files = set(dat_files)
        wallet_paths = set(dat_files - exclude_files)
        if wallets is not None:
            if isinstance(wallets, list):
                for wallet in wallets:
                    wallet_paths.add(wallet)
            else:
                wallet_paths.add(wallets)
        return wallet_paths

    @property
    def node_port(self):
        if self.file['testnet']:
            custom_port = self.file['test.port']
        else:
            custom_port = self.file['main.port']
        if custom_port is not None:
            return custom_port
        if self.file['testnet']:
            return LITECOIN_TESTNET_PEER_PORT
        return LITECOIN_MAINNET_PEER_PORT

    @property
    def rpc_port(self):
        if self.file['testnet']:
            custom_port = self.file['test.rpcport']
        else:
            custom_port = self.file['main.rpcport']
        if custom_port is not None:
            return custom_port
        if self.file['testnet']:
            return LITECOIN_TESTNET_RPC_PORT
        return LITECOIN_MAINNET_RPC_PORT

    def set_prune(self, should_prune: bool = None):

        if should_prune is None:
            should_prune = self.hard_drives.should_prune(self.file['datadir'],
                                                         has_litecoin=True)
        if should_prune:
            if self.file['testnet']:
                prune = TESTNET_PRUNE
            else:
                prune = MAINNET_PRUNE
            self.file['prune'] = prune
        else:
            self.file['prune'] = 0
        self.file['txindex'] = not should_prune

    def autoconfigure_datadir(self):
        default_datadir = LITECOIN_DATA_PATH[OPERATING_SYSTEM]
        big_drive = self.hard_drives.get_big_drive()
        default_is_big_enough = not self.hard_drives.should_prune(
            input_directory=default_datadir,
            has_litecoin=True
        )
        default_is_biggest = self.hard_drives.is_default_partition(big_drive)
        log.info(
            'autoconfigure_datadir',
            default_is_big_enough=default_is_big_enough,
            default_is_biggest=default_is_biggest
        )
        if default_is_big_enough or default_is_biggest:
            self.file['datadir'] = default_datadir
            log.info(
                'autoconfigure_datadir',
                datadir=default_datadir
            )
            return

        if not self.hard_drives.should_prune(big_drive.mountpoint, False):
            datadir = os.path.join(big_drive.mountpoint, 'Litecoin')
            self.file['datadir'] = datadir
            log.info(
                'autoconfigure_datadir',
                datadir=datadir
            )
            if not os.path.exists(self.file['datadir']):
                os.mkdir(self.file['datadir'])
        else:
            self.file['datadir'] = default_datadir
            log.info(
                'autoconfigure_datadir',
                datadir=default_datadir
            )

    @property
    def args(self) -> List[str]:
        return [f'-conf={self.file.path}']

    @property
    def litecoin_cli(self) -> str:
        command = [
            f'"{self.software.litecoin_cli}"',
            f'-conf="{self.file.path}"',
        ]
        return ' '.join(command)

    def config_file_changed(self):
        # Refresh config file
        self.file.file_watcher.blockSignals(True)
        self.file.populate_cache()
        self.file.file_watcher.blockSignals(False)
        self.zmq_block_port = int(self.file['zmqpubrawblock'].split(':')[-1])
        self.zmq_tx_port = int(self.file['zmqpubrawtx'].split(':')[-1])
        # Some text editors do not modify the file, they delete and replace the file
        # Check if file is still in file_watcher list of files, if not add back
        files_watched = self.file.file_watcher.files()
        if len(files_watched) == 0:
            self.file.file_watcher.addPath(self.file.path)

    @property
    def restart_required(self):
        old_config = self.config_snapshot.copy()
        new_config = self.file.snapshot

        # First check that both config files are still on the same network
        old_config_network = 'testnet' in old_config.keys()
        new_config_network = 'testnet' in new_config.keys()

        if (old_config_network == new_config_network) and self.running:
            common_fields = [
                'rpcuser', 'rpcpassword', 'disablewallet', 'datadir', 'disablewallet',
                'zmqpubrawblock', 'zmqpubrawtx', 'prune', 'txindex', 'timeout'
            ]

            for field in common_fields:

                # First check if field is found in both configs
                found_in_old_config = field in old_config.keys()
                found_in_new_config = field in new_config.keys()
                if found_in_old_config != found_in_new_config:
                    return True

                # Now check that values are the same
                if found_in_old_config:
                    if old_config[field] != new_config[field]:
                        return True

            if self.file['testnet']:
                # Only check testnet fields if currently running testnet
                testnet_fields = [
                    'test.rpcport', 'test.port', 'test.wallet', 'test.walletdir'
                ]
                for field in testnet_fields:
                    # First check if field is found in both configs
                    found_in_old_config = field in old_config.keys()
                    found_in_new_config = field in new_config.keys()

                    if found_in_old_config != found_in_new_config:
                        return True

                    # Now check that values are the same
                    if found_in_old_config:
                        if old_config[field] != new_config[field]:
                            return True

            else:
                # Only check mainnet fields if currently running mainnet
                mainnet_fields = [
                    'rpcport', 'port'
                ]

                for field in mainnet_fields:
                    # First check if field is found in both configs
                    found_in_old_config = field in old_config.keys()
                    found_in_new_config = field in new_config.keys()
                    if found_in_old_config != found_in_new_config:
                        return True

                    # Now check that values are the same
                    if found_in_old_config:
                        if old_config[field] != new_config[field]:
                            return True

            return False
        elif self.running:
            # Network has changed and the node is running - Restart is required
            return True

        return False
예제 #25
0
def runProcess(command, arguments):
    process = QProcess()
    process.start(command, arguments)
    process.waitForFinished()
    std_output = process.readAllStandardOutput().data().decode('utf-8')
    return std_output.split('\n')
예제 #26
0
class MainWindow(QDialog):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle(APP_NAME)
        self.setWindowIcon(QIcon(get_resource_path(os.path.join('resources', 'noun_Plant.ico'))))
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        self.ui.lineEdit_invoiceNumber.setValidator(QIntValidator())
        self.ui.progressBar.setMaximum(1)
        self.proc = QProcess()

        self.ui.tableWidget_invoiceContent.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
        self.savePath = create_path_it_not_exist(os.path.join(get_config_path(APP_CONFIG_FOLDER), 'save.json'))
        self.saveData = SaveData(self.savePath)
        self.outputPath = os.path.join(get_exe_path(), create_path_it_not_exist(os.path.join(get_exe_path(), 'output')))
        self.texGenerator = LatexTemplateGenerator(get_resource_path('resources').replace('\\', '/'))

        self.currentGeneralInfo = GeneralInfo()
        self.currentClientInfo = ClientInfo()
        self.currentInvoiceInfo = InvoiceInfo()

        self.ui.pushButton_saveQuickRecallInvoice.clicked.connect(self.save_invoice_info)
        self.ui.pushButton_saveQuickRecallClient.clicked.connect(self.save_client_info)
        self.ui.pushButton_saveQuickRecallGeneral.clicked.connect(self.save_general_info)

        self.ui.comboBox_quickRecallInvoice.activated.connect(self.on_combo_box_invoice_changed)
        self.ui.comboBox_quickRecallClient.activated.connect(self.on_combo_box_client_changed)
        self.ui.comboBox_quickRecallGeneral.activated.connect(self.on_combo_box_general_changed)

        self.ui.pushButton_generateInvoice.clicked.connect(self.generate_invoice)

        self.proc.finished.connect(functools.partial(self._handleProcFinished, self.proc))

        self.ui.toolButton_add.clicked.connect(self.add_row)
        self.ui.toolButton_delete.clicked.connect(self.delete_row)
        self.update_ui()

    def _handleProcFinished(self, process, exitCode):
        stdOut = process.readAllStandardOutput()
        stdErr = process.readAllStandardError()
        print("Standard Out:")
        print(stdOut)
        print("Standard Error:")
        print(stdErr)
        if(exitCode == 0):
            self.success_message('Invoice Generated successfully')
        self.ui.progressBar.setMaximum(1)

    def make_pdf(self, input_folder, input_file):
        if self.proc.isOpen():
            print('cancelled running process')
            self.proc.close()
        self.proc.setWorkingDirectory(input_folder)
        self.proc.start("xelatex", [os.path.join(input_folder, input_file)])
        self.ui.progressBar.setMaximum(0)

    def save_data(self):
        self.save_invoice_info()
        self.save_client_info()
        self.save_general_info()

        if self.saveData.save_client(self.currentClientInfo) and \
                self.saveData.save_invoice(self.currentInvoiceInfo) and \
                self.saveData.save_general(self.currentGeneralInfo):
            self.saveData.save_to_file()
        else:
            self.warning_message("Invalid invoice formatting\n Check for empty or incorrect fields")

    def load_data(self):
        pass

    def list_client(self):
        self.ui.comboBox_quickRecallClient.clear()
        for client in self.saveData.clients:
            self.ui.comboBox_quickRecallClient.addItem(client.name)

    def list_general(self):
        self.ui.comboBox_quickRecallGeneral.clear()
        for general in self.saveData.generals:
            self.ui.comboBox_quickRecallGeneral.addItem(general.company_name)
        pass

    def list_invoice(self):
        self.ui.comboBox_quickRecallInvoice.clear()
        for invoice in self.saveData.invoices:
            self.ui.comboBox_quickRecallInvoice.addItem(invoice.invoice_number)

    def generate_invoice(self):
        print("generating invoice")
        self.save_data()
        filename = ''.join(e for e in unidecode.unidecode(self.currentInvoiceInfo.client.name) if e.isalnum())
        self.texGenerator.render(SaveData.asflatdict(self.currentInvoiceInfo), create_path_it_not_exist(
            os.path.join(self.outputPath, self.currentInvoiceInfo.invoice_number,
                         'Facture_' + self.currentInvoiceInfo.invoice_number + '_' + filename + '.tex')))
        self.make_pdf(os.path.join(self.outputPath, self.currentInvoiceInfo.invoice_number), 'Facture_' + self.currentInvoiceInfo.invoice_number + '_' + filename + '.tex')
        self.update_ui()

    def recall_general_info(self, company_name):
        newGeneral = self.saveData.get_general(company_name)
        self.ui.lineEdit_companyName.setText(newGeneral.company_name)
        self.ui.lineEdit_firstName.setText(newGeneral.first_name)
        self.ui.lineEdit_lastName.setText(newGeneral.last_name)
        self.ui.lineEdit_fullAddress.setText(newGeneral.full_address)
        self.ui.lineEdit_companySIRET.setText(newGeneral.company_siret)
        self.ui.lineEdit_companySIREN.setText(newGeneral.company_siren)
        self.ui.lineEdit_companyAPE.setText(newGeneral.company_ape)
        self.ui.lineEdit_companyEmail.setText(newGeneral.company_email)
        self.ui.lineEdit_companyTelephone.setText(newGeneral.company_phone)
        self.ui.lineEdit_bankIBAN.setText(newGeneral.bank_iban)
        self.ui.lineEdit_bankBIC.setText(newGeneral.bank_bic)

    def recall_client_info(self, name):
        newClient = self.saveData.get_client(name)
        self.ui.lineEdit_clientName.setText(newClient.name)
        self.ui.lineEdit_clientAddressFirst.setText(newClient.address_first_line)
        self.ui.lineEdit_clientAdressSecond.setText(newClient.address_second_line)
        self.update_infos_from_UI()

    def recall_invoice_info(self, invoice_number):
        newInvoice = self.saveData.get_invoice(invoice_number)

        self.ui.lineEdit_invoiceNumber.setText(newInvoice.invoice_number)
        self.ui.lineEdit_invoiceDate.setText(newInvoice.invoice_date)
        self.ui.lineEdit_invoiceName.setText(newInvoice.invoice_name)

        self.ui.tableWidget_invoiceContent.clearContents()
        for item in newInvoice.items:
            row_position = self.ui.tableWidget_invoiceContent.rowCount()
            self.ui.tableWidget_invoiceContent.insertRow(row_position)
            self.ui.tableWidget_invoiceContent.setItem(row_position, 0, QTableWidgetItem(str(item.product_name)))
            self.ui.tableWidget_invoiceContent.setItem(row_position, 1, QTableWidgetItem(str(item.quantity)))
            self.ui.tableWidget_invoiceContent.setItem(row_position, 2, QTableWidgetItem(str(item.price)))

        self.recall_client_info(newInvoice.client.name)
        self.recall_general_info(newInvoice.general.company_name)
        self.update_infos_from_UI()

    def on_combo_box_client_changed(self, index):
        self.recall_client_info(self.ui.comboBox_quickRecallClient.itemText(index))

    def on_combo_box_general_changed(self, index):
        self.recall_general_info(self.ui.comboBox_quickRecallGeneral.itemText(index))

    def on_combo_box_invoice_changed(self, index):
        self.recall_invoice_info(self.ui.comboBox_quickRecallInvoice.itemText(index))

    def save_invoice_info(self):
        self.update_invoice_infos_from_UI()
        if not self.saveData.save_invoice(self.currentInvoiceInfo):
            self.warning_message("Couldn't save new Invoice")
        self.update_ui()

    def save_general_info(self):
        self.update_general_infos_from_UI()
        if not self.saveData.save_general(self.currentGeneralInfo):
            self.warning_message("Couldn't save new General")
        self.update_ui()

    def save_client_info(self):
        self.update_client_infos_from_UI()
        if not self.saveData.save_client(self.currentClientInfo):
            self.warning_message("Couldn't save new Client")
        self.update_ui()

    def add_row(self):
        self.ui.tableWidget_invoiceContent.insertRow(self.ui.tableWidget_invoiceContent.rowCount())

    def delete_row(self):
        self.ui.tableWidget_invoiceContent.removeRow(self.ui.tableWidget_invoiceContent.currentRow())

    def ask_validation(self, text, informative_text, title="Validation Dialog"):
        msgBox = QMessageBox()
        msgBox.setWindowTitle("hey")
        msgBox.setText(text)
        msgBox.setInformativeText(informative_text)
        msgBox.setStandardButtons(QMessageBox.Apply | QMessageBox.Cancel)
        msgBox.setDefaultButton(QMessageBox.Cancel)
        ret = msgBox.exec_()
        return True if ret == QMessageBox.Apply else False

    def success_message(self, text):
        ret = QMessageBox.information(self, self.tr("Success"),
                                  self.tr(text),
                                  QMessageBox.Ok,
                                  QMessageBox.Ok)
    def warning_message(self, text):
        ret = QMessageBox.warning(self, self.tr("Warning"),
                                  self.tr(text),
                                  QMessageBox.Ok,
                                  QMessageBox.Ok)

    def update_client_infos_from_UI(self):
        currentClientInfo = ClientInfo()
        currentClientInfo.name = self.ui.lineEdit_clientName.text()
        currentClientInfo.address_first_line = self.ui.lineEdit_clientAddressFirst.text()
        currentClientInfo.address_second_line = self.ui.lineEdit_clientAdressSecond.text()

        self.currentClientInfo = currentClientInfo

    def update_general_infos_from_UI(self):
        currentGeneralInfo = GeneralInfo()
        currentGeneralInfo.company_name = self.ui.lineEdit_companyName.text()
        currentGeneralInfo.first_name = self.ui.lineEdit_firstName.text()
        currentGeneralInfo.last_name = self.ui.lineEdit_lastName.text()
        currentGeneralInfo.full_address = self.ui.lineEdit_fullAddress.text()
        currentGeneralInfo.company_siret = self.ui.lineEdit_companySIRET.text()
        currentGeneralInfo.company_siren = self.ui.lineEdit_companySIREN.text()
        currentGeneralInfo.company_ape = self.ui.lineEdit_companyAPE.text()
        currentGeneralInfo.company_email = self.ui.lineEdit_companyEmail.text()
        currentGeneralInfo.company_phone = self.ui.lineEdit_companyTelephone.text()
        currentGeneralInfo.bank_iban = self.ui.lineEdit_bankIBAN.text()
        currentGeneralInfo.bank_bic = self.ui.lineEdit_bankBIC.text()

        self.currentGeneralInfo = currentGeneralInfo

    def update_invoice_infos_from_UI(self):
        currentInvoiceInfo = InvoiceInfo()
        currentInvoiceInfo.invoice_number = self.ui.lineEdit_invoiceNumber.text()
        currentInvoiceInfo.invoice_date = self.ui.lineEdit_invoiceDate.text()
        currentInvoiceInfo.invoice_name = self.ui.lineEdit_invoiceName.text()
        currentInvoiceInfo.general = self.currentGeneralInfo
        currentInvoiceInfo.client = self.currentClientInfo

        currentInvoiceInfo.items = []
        try:
            for row in range(self.ui.tableWidget_invoiceContent.rowCount()):
                newItem = InvoiceItem()
                newItem.product_name = self.ui.tableWidget_invoiceContent.item(row, 0).text() \
                    if self.ui.tableWidget_invoiceContent.item(row, 0) is not None else ""
                newItem.quantity = int(self.ui.tableWidget_invoiceContent.item(row, 1).text()) \
                    if self.ui.tableWidget_invoiceContent.item(row, 1) is not None else 0
                newItem.price = float(self.ui.tableWidget_invoiceContent.item(row, 2).text()) \
                    if self.ui.tableWidget_invoiceContent.item(row, 2) is not None else 0
                currentInvoiceInfo.items.append(newItem)
        except ValueError:
            self.warning_message("oops Something went wrong, make sure you entered appropriate values")

        self.currentInvoiceInfo = currentInvoiceInfo

    def update_infos_from_UI(self):
        self.update_general_infos_from_UI()
        self.update_client_infos_from_UI()
        self.update_invoice_infos_from_UI()

    def update_ui(self):
        self.list_invoice()
        self.list_client()
        self.list_general()
예제 #27
0
class Lnd(object):
    litecoin: Litecoin
    file: ConfigurationFile
    software: LndSoftware
    process: QProcess

    def __init__(self, configuration_file_path: str, litecoin: Litecoin):
        self.running = False
        self.is_unlocked = False
        self.litecoin = litecoin
        self.file = ConfigurationFile(configuration_file_path)
        self.software = LndSoftware()

        self.lnddir = LND_DIR_PATH[OPERATING_SYSTEM]

        # Previous versions of the launcher set lnddir in the config file,
        # but it is not a valid key so this helps old users upgrading
        if self.file['lnddir'] is not None:
            self.file['lnddir'] = None

        if self.file['debuglevel'] is None:
            self.file['debuglevel'] = 'info'

        self.file['litecoin.active'] = True
        self.file['litecoin.node'] = 'litecoind'
        self.file['litecoind.rpchost'] = f'127.0.0.1:{self.litecoin.rpc_port}'
        self.file['litecoind.rpcuser'] = self.litecoin.file['rpcuser']
        self.file['litecoind.rpcpass'] = self.litecoin.file['rpcpassword']
        self.file['litecoind.zmqpubrawblock'] = self.litecoin.file[
            'zmqpubrawblock']
        self.file['litecoind.zmqpubrawtx'] = self.litecoin.file['zmqpubrawtx']

        if self.file['restlisten'] is None:
            if self.litecoin.file['testnet']:
                self.rest_port = get_port(LND_DEFAULT_REST_PORT + 1)
            else:
                self.rest_port = get_port(LND_DEFAULT_REST_PORT)
            self.file['restlisten'] = f'127.0.0.1:{self.rest_port}'
        else:
            self.rest_port = self.file['restlisten'].split(':')[-1]

        if not self.file['rpclisten']:
            if self.litecoin.file['testnet']:
                self.grpc_port = get_port(LND_DEFAULT_GRPC_PORT + 1)
            else:
                self.grpc_port = get_port(LND_DEFAULT_GRPC_PORT)
            self.file['rpclisten'] = f'127.0.0.1:{self.grpc_port}'
        else:
            self.grpc_port = int(self.file['rpclisten'].split(':')[-1])

        if not self.file['tlsextraip']:
            self.file['tlsextraip'] = '127.0.0.1'

        if self.file['color'] is None:
            self.file['color'] = '#000000'

        self.macaroon_path = os.path.join(
            self.lnddir,
            'data',
            'chain',
            'litecoin',
            str(self.litecoin.network)
        )
        self.config_snapshot = self.file.snapshot.copy()
        self.file.file_watcher.fileChanged.connect(self.config_file_changed)
        self.litecoin.file.file_watcher.fileChanged.connect(self.litecoin_config_file_changed)

        self.process = QProcess()
        self.process.setProgram(self.software.lnd)
        self.process.setCurrentReadChannel(0)
        self.process.setArguments(self.args)
        self.process.start()

    @property
    def args(self):
        if IS_WINDOWS:
            arg_list = [
                f'--configfile={self.file.path}',
            ]
        else:
            arg_list = [
                f'--configfile="{self.file.path}"',
            ]

        if self.litecoin.file['testnet']:
            arg_list += [
                '--litecoin.testnet'
            ]
        else:
            arg_list += [
                '--litecoin.mainnet'
            ]
        return arg_list

    @property
    def node_port(self) -> str:
        if self.file['listen'] is None:
            if self.litecoin.file['testnet']:
                port = get_port(LND_DEFAULT_PEER_PORT + 1)
            else:
                port = get_port(LND_DEFAULT_PEER_PORT)
            self.file['listen'] = f'127.0.0.1:{port}'
        else:
            if not isinstance(self.file['listen'], list):
                port = self.file['listen'].split(':')[-1]
            else:
                port = self.file['listen'][0].split(':')[-1]
        return port

    def test_tls_cert(self):
        context = ssl.create_default_context()
        context.load_verify_locations(cafile=self.tls_cert_path)
        conn = context.wrap_socket(socket.socket(socket.AF_INET),
                                   server_hostname='127.0.0.1')
        conn.connect(('127.0.0.1', int(self.rest_port)))
        cert = conn.getpeercert()
        return cert

    @property
    def admin_macaroon_path(self) -> str:
        path = os.path.join(self.macaroon_path, 'admin.macaroon')
        return path

    @property
    def wallet_path(self) -> str:
        wallet_path = os.path.join(self.macaroon_path, 'wallet.db')
        return wallet_path

    @property
    def has_wallet(self) -> bool:
        return os.path.isfile(self.wallet_path)

    @property
    def tls_cert_path(self) -> str:
        tls_cert_path = os.path.join(self.lnddir, 'tls.cert')
        return tls_cert_path

    def lncli_arguments(self) -> List[str]:
        args = []
        if self.grpc_port != LND_DEFAULT_GRPC_PORT:
            args.append(f'--rpcserver=127.0.0.1:{self.grpc_port}')
        if self.litecoin.file['testnet']:
            args.append(f'--network={self.litecoin.network}')
        if self.lnddir != LND_DIR_PATH[OPERATING_SYSTEM]:
            args.append(f'''--lnddir="{self.lnddir}"''')
            args.append(f'--macaroonpath="{self.macaroon_path}"')
            args.append(f'--tlscertpath="{self.tls_cert_path}"')
        return args

    @property
    def lncli(self) -> str:
        base_command = [
            f'"{self.software.lncli}"',
        ]
        base_command += self.lncli_arguments()
        return ' '.join(base_command)

    @property
    def rest_url(self) -> str:
        return f'https://127.0.0.1:{self.rest_port}'

    @property
    def grpc_url(self) -> str:
        return f'127.0.0.1:{self.grpc_port}'

    def config_file_changed(self):
        # Refresh config file
        self.file.file_watcher.blockSignals(True)
        self.file.populate_cache()
        self.file.file_watcher.blockSignals(False)
        self.rest_port = int(self.file['restlisten'].split(':')[-1])
        self.grpc_port = int(self.file['rpclisten'].split(':')[-1])

        # Some text editors do not modify the file, they delete and replace the file
        # Check if file is still in file_watcher list of files, if not add back
        files_watched = self.file.file_watcher.files()
        if len(files_watched) == 0:
            self.file.file_watcher.addPath(self.file.path)

    def litecoin_config_file_changed(self):
        # Refresh config file
        self.file.file_watcher.blockSignals(True)
        self.file.populate_cache()
        self.file.file_watcher.blockSignals(False)
        self.file['litecoind.rpchost'] = f'127.0.0.1:{self.litecoin.rpc_port}'
        self.file['litecoind.rpcuser'] = self.litecoin.file['rpcuser']
        self.file['litecoind.rpcpass'] = self.litecoin.file['rpcpassword']
        self.file['litecoind.zmqpubrawblock'] = self.litecoin.file['zmqpubrawblock']
        self.file['litecoind.zmqpubrawtx'] = self.litecoin.file['zmqpubrawtx']

    @property
    def restart_required(self):
        if self.running:
            # Did litecoin details change
            if self.litecoin.restart_required:
                return True and self.running

            old_config = self.config_snapshot.copy()
            new_config = self.file.snapshot

            fields = [
                'restlisten', 'listen', 'rpclisten'
            ]

            for field in fields:
                # First check if field is found in both configs
                found_in_old_config = field in old_config.keys()
                found_in_new_config = field in new_config.keys()
                if found_in_old_config != found_in_new_config:
                    return True

                # Now check that values are the same
                if found_in_old_config:
                    if old_config[field] != new_config[field]:
                        return True

        return False
예제 #28
0
def runProcess(command, arguments):
    process = QProcess()
    process.start(command, arguments)
    process.waitForFinished()
    std_output = process.readAllStandardOutput().data().decode('utf-8')
    return std_output.split('\n')
예제 #29
0
class TclInterfaceHandler(QObject):
    # The TCP server IP address
    HOST = "127.0.0.1"
    # TCP server port
    PORT = 9955

    commandExecutionFinished = Signal(str)
    faultInjectionFinished = Signal(str)

    #---------------------------------------------------------------------------------
    # @brief Class constructor.
    #---------------------------------------------------------------------------------
    def __init__(self):
        super(TclInterfaceHandler, self).__init__()
        self.operation = Operation.NONE
        self.frame_address = "00000000"
        self.frames = "1"
        self.tcpClientSocket = QtNetwork.QTcpSocket()
        self.tcpClientSocketStatus = SocketStatus.DISCONNECTED
        self.startVivado()
        return

    #---------------------------------------------------------------------------------
    # @brief This function should be called when the application terminates.
    #---------------------------------------------------------------------------------
    def exit(self):
        self.vivadoProcess.kill()
        vivado_command_to_kill = "cmd.exe /C Taskkill /IM vivado.exe /F"
        process = QProcess()
        process.start(vivado_command_to_kill)
        process.waitForFinished(5000)

        tcp_command_to_kill = "cmd.exe /C netstat -ano | find '0.0.0.0:9955'"

        return

    #**********************************************************************************
    # TCP Client
    #**********************************************************************************
    #---------------------------------------------------------------------------------
    # @brief This function starts the TCP client.
    #---------------------------------------------------------------------------------
    def clientStart(self):
        if self.tcpClientSocketStatus == SocketStatus.CONNECTED:
            self.tcpClientSocket.abort()
        self.tcpClientSocket.connectToHost(TclInterfaceHandler.HOST,
                                           TclInterfaceHandler.PORT)
        self.tcpClientSocket.waitForConnected(3000)
        self.tcpClientSocket.readyRead.connect(self.clientReadReady)
        self.tcpClientSocket.error.connect(self.clientError)

        self.tcpClientSocketStatus = SocketStatus.CONNECTED
        return

    #---------------------------------------------------------------------------------
    # @brief This function is called by the TCP client when it has data ready to be read.
    #---------------------------------------------------------------------------------
    def clientReadReady(self):
        message = QTextStream(self.tcpClientSocket).readAll()

        if Operation.READBACK_CAPTURE.name in message:
            XtclLog.writeLine(
                "================ FPGA readback capture finished ================ ",
                XtclCommon.blue)
        elif Operation.READBACK_VERIFY.name in message:
            XtclLog.writeLine(
                "================ FPGA readback verify finished ================ ",
                XtclCommon.blue)
            #verify = filecmp.cmp(XtclSettings.readbackFilePathGolden, self.readbackFile, False)
        elif Operation.READBACK.name in message:
            XtclLog.writeLine(
                "================ FPGA readback finished ================ ",
                XtclCommon.blue)
        elif Operation.CONFIGURATION.name in message:
            XtclLog.writeLine(
                "================ FPGA configuration finished ================ ",
                XtclCommon.blue)
        elif Operation.FRAMES_READ.name in message or Operation.FAULT_INJECTION_READ.name in message:
            XtclLog.writeLine(
                "================ FPGA frame readback finished ================ ",
                XtclCommon.blue)
        elif Operation.FRAMES_WRITE.name in message or Operation.FAULT_INJECTION_WRITE.name in message:
            XtclLog.writeLine(
                "================ FPGA frame write finished ================ ",
                XtclCommon.blue)
        elif Operation.READ_FIFO.name in message:
            XtclLog.writeLine(
                "================ Reading internal FIFO finished ================ ",
                XtclCommon.blue)
        elif Operation.RESET_FIFO.name in message:
            XtclLog.writeLine(
                "================ Reseting internal FIFO finished ================ ",
                XtclCommon.blue)
        elif Operation.READ_HEARTBEAT.name in message:
            XtclLog.writeLine(
                "================ Reading heartbeat finished ================ ",
                XtclCommon.blue)
        elif Operation.LOGIC_STATUS.name in message:
            XtclLog.writeLine(
                "================ Reading active logic status finished ================ ",
                XtclCommon.blue)
        elif Operation.REGISTER_READ.name in message:
            XtclLog.writeLine(
                "================ Reading configuration register finished ================ ",
                XtclCommon.blue)
        elif Operation.REGISTER_WRITE.name in message:
            XtclLog.writeLine(
                "================ Writing configuration register finished ================ ",
                XtclCommon.blue)
        else:
            XtclLog.writeLine(message, XtclCommon.red)

        self.operation = Operation.NONE

        if "FAULT_INJECTION" in message:
            self.faultInjectionFinished.emit(message)
        else:
            self.commandExecutionFinished.emit(message)
        return

    #---------------------------------------------------------------------------------
    # @brief Callback function for the client error.
    #---------------------------------------------------------------------------------
    def clientError(self, socketError):
        if socketError == QtNetwork.QAbstractSocket.RemoteHostClosedError:
            pass
        elif socketError == QtNetwork.QAbstractSocket.HostNotFoundError:
            XtclLog.writeLine(
                "The host was not found. Please check the host name and port settings",
                XtclCommon.red)
        elif socketError == QtNetwork.QAbstractSocket.ConnectionRefusedError:
            XtclLog.writeLine(
                "The connection was refused by the peer. Make sure the "
                "server is running, and check that the host name "
                "and port settings are correct.", TclInterfaceHandler.red)
        else:
            XtclLog.writeLine(
                "The following error occurred: %s." %
                self.tcpSocket.errorString(), XtclCommon.red)
        return

    #---------------------------------------------------------------------------------
    # @brief Send data to the TCP server
    #---------------------------------------------------------------------------------
    def clientSend(self, data):
        bytesArray = bytes(data, 'utf-8')
        message = QByteArray.fromRawData(bytesArray)
        self.tcpClientSocket.write(message)
        self.tcpClientSocket.flush()
        return

    #**********************************************************************************
    # Vivado interface
    #**********************************************************************************
    #---------------------------------------------------------------------------------
    # @brief This function starts the Vivado instanse. Please replace the command
    #        with the appropriate command for the running platform
    #---------------------------------------------------------------------------------
    def startVivado(self):
        self.operation = Operation.NONE
        TclInterfaceHandler.isOperationFinished = False
        XtclLog.writeLine(
            "================ Starting Vivado process ================ ",
            XtclCommon.blue)
        XtclLog.write("PLEASE WAIT UNTIL YOU SEE \"# vwait forever\" MESSAGE!",
                      XtclCommon.red)
        command = XtclSettings.vivadoDirectory + "/vivado.bat -nojournal -nolog -mode batch -source jtag_configuration_engine.tcl"
        # Create runner
        self.vivadoProcess = QProcess()
        self.vivadoProcess.readyReadStandardError.connect(self.errorInfo)
        self.vivadoProcess.readyReadStandardOutput.connect(
            self.readAllStandardOutput)
        self.vivadoProcess.finished.connect(self.finished)
        self.vivadoProcess.start(command)
        return

    #---------------------------------------------------------------------------------
    # @brief Callback function for the error of the Vivado process
    #---------------------------------------------------------------------------------
    def errorInfo(self):
        info = self.vivadoProcess.readAllStandardError()
        info_text = QTextStream(info).readAll()
        XtclLog.write(info_text)
        return

    #---------------------------------------------------------------------------------
    # @brief Callback function to rediarect the output of the Vivado process
    #---------------------------------------------------------------------------------
    def readAllStandardOutput(self):
        info = self.vivadoProcess.readAllStandardOutput()
        info_text = QTextStream(info).readAll()
        XtclLog.write(info_text)
        return

    #---------------------------------------------------------------------------------
    # @brief Callback function for the termination event of the Vivado process
    #---------------------------------------------------------------------------------
    def finished(self, exitCode, exitStatus):
        return

    #**********************************************************************************
    # Interface commands
    #**********************************************************************************
    #---------------------------------------------------------------------------------
    # @brief This function configures the FPGA
    # @param bitstream_filepath: The full path of the bitstream file
    # @param mask_filepath: The full path of the mask file
    #---------------------------------------------------------------------------------
    def configure(self, bitstream_filepath, mask_filepath):
        if self.tcpClientSocketStatus != SocketStatus.CONNECTED:
            self.clientStart()
        XtclLog.writeLine(
            "================ Starting FPGA configuration ================ ",
            XtclCommon.blue)
        self.clientSend(Operation.CONFIGURATION.name + "%" +
                        bitstream_filepath + "%" + mask_filepath)
        self.operation = Operation.CONFIGURATION
        return

    #---------------------------------------------------------------------------------
    # @brief This function reads-back the FPGA
    # @param filename: If provided should be the full path of the file where the readback
    #                  data will be saved. Othervise the readback data is saved in a
    #                  timestamp-based file name inside the "readback-files" of the
    #                  working folder.
    #---------------------------------------------------------------------------------
    def readback(self, filename=None):
        if self.tcpClientSocketStatus != SocketStatus.CONNECTED:
            self.clientStart()
        if filename == None:
            readbackFileName = "readback-" + str(
                datetime.datetime.now().timestamp()) + ".rbd"
        else:
            readbackFileName = filename
        self.readbackFile = XtclSettings.workingDirectory + '/readback-files/' + readbackFileName
        XtclLog.writeLine(
            "================ Starting FPGA readback ================ ",
            XtclCommon.blue)
        XtclLog.writeLine(readbackFileName, XtclCommon.blue)
        self.clientSend(Operation.READBACK.name + "%" + self.readbackFile)
        self.operation = Operation.READBACK
        return

    #---------------------------------------------------------------------------------
    # @brief This function reads-back the FPGA using the capture mode
    # @note The readback data is saved in a timestamp-based file name inside the "readback-capture-files"
    #       of the working folder.
    #---------------------------------------------------------------------------------
    def readbackCapture(self):
        if self.tcpClientSocketStatus != SocketStatus.CONNECTED:
            self.clientStart()
        readbackFileName = "readbackCapture-" + str(
            datetime.datetime.now().timestamp()) + ".rbd"
        self.readbackFile = XtclSettings.workingDirectory + '/readback-capture-files/' + readbackFileName
        XtclLog.writeLine(
            "================ Starting FPGA readback capture ================ ",
            XtclCommon.blue)
        XtclLog.writeLine(readbackFileName, XtclCommon.blue)
        self.clientSend(Operation.READBACK_CAPTURE.name + "%" +
                        self.readbackFile)
        self.operation = Operation.READBACK_CAPTURE
        return

    #---------------------------------------------------------------------------------
    # @brief This function verifies the FPGA.
    # @note The FPGA device should be configured before issuing this command.
    #---------------------------------------------------------------------------------
    def readbackVerify(self):
        if self.tcpClientSocketStatus != SocketStatus.CONNECTED:
            self.clientStart()
        XtclLog.writeLine(
            "================ Starting FPGA readback verify ================ ",
            XtclCommon.blue)
        self.clientSend(Operation.READBACK_VERIFY.name)
        self.operation = Operation.READBACK_VERIFY
        return

    #---------------------------------------------------------------------------------
    # @brief This function reads the internal FIFO of the interface logic.
    #---------------------------------------------------------------------------------
    def readInternalFifo(self):
        if self.tcpClientSocketStatus != SocketStatus.CONNECTED:
            self.clientStart()
        XtclLog.writeLine(
            "================ Reading internal FIFO ================ ",
            XtclCommon.blue)
        self.clientSend(Operation.READ_FIFO.name)
        self.operation = Operation.READ_FIFO
        return

    #---------------------------------------------------------------------------------
    # @brief This function resets the internal FIFO of the interface logic.
    #---------------------------------------------------------------------------------
    def resetInternalFifo(self):
        if self.tcpClientSocketStatus != SocketStatus.CONNECTED:
            self.clientStart()
        XtclLog.writeLine(
            "================ Reseting internal FIFO ================ ",
            XtclCommon.blue)
        self.clientSend(Operation.RESET_FIFO.name)
        self.operation = Operation.RESET_FIFO
        return

    #---------------------------------------------------------------------------------
    # @brief This function reads the heartbeat signal the interface logic.
    #---------------------------------------------------------------------------------
    def readHeartbeat(self):
        if self.tcpClientSocketStatus != SocketStatus.CONNECTED:
            self.clientStart()
        XtclLog.writeLine(
            "================ Reading heartbeat ================ ",
            XtclCommon.blue)
        self.clientSend(Operation.READ_HEARTBEAT.name)
        self.operation = Operation.READ_HEARTBEAT
        return

    #---------------------------------------------------------------------------------
    # @brief This function writes frames in configuration memory of the FPGA
    # @param frame_address: The frame address in HEX format (i.e. 00001002).
    # @param frame_file: The full path of the file which holds the frame data to be written.
    # @param append_dummy_frame: True to append a dummy frame after writing the frame data.
    #                            Set it to false if the file contains also the dummy frame.
    # @param is_frame_data_in_hex_format: Trus if the content of the @ref frame_file is in HEX foramt.
    # @param frames: The number of frames to be written. This should be the same as the frames
    #                inside the @ref frame_file (do not include the dummy frame).
    # @param reset_fifo: True to reset the FIFO at the end of writing.
    # @param is_fault_injection: Trus if this write is for fault injection experiment.
    #---------------------------------------------------------------------------------
    def writeFrames(self,
                    frame_address,
                    frame_file,
                    append_dummy_frame=True,
                    is_frame_data_in_hex_format=True,
                    frames=1,
                    reset_fifo=False,
                    is_fault_injection=False):
        self.frame_address = str(frame_address)
        self.frames = str(frames)
        XtclLog.writeLine(
            "================ Starting FPGA frame write ================ ",
            XtclCommon.blue)
        append_dummy_frame_ = str(int(append_dummy_frame))
        is_frame_data_in_hex_format_ = str(int(is_frame_data_in_hex_format))
        reset_fifo_ = str(int(reset_fifo))

        if is_fault_injection == True:
            command = Operation.FAULT_INJECTION_WRITE.name + "%" + frame_file + "%" + self.frame_address + "%" + self.frames + "%" + append_dummy_frame_ + "%" + is_frame_data_in_hex_format_ + "%" + reset_fifo_
        else:
            command = Operation.FRAMES_WRITE.name + "%" + frame_file + "%" + self.frame_address + "%" + self.frames + "%" + append_dummy_frame_ + "%" + is_frame_data_in_hex_format_ + "%" + reset_fifo_
        self.clientSend(command)
        self.operation = Operation.FRAMES_WRITE
        return

    #---------------------------------------------------------------------------------
    # @brief This function reads frames from the configuration memory of the FPGA
    # @param frame_address: The frame address in HEX format (i.e. 00001002).
    # @param frames: The number of frames to be written. This should be the same as the frames
    #                inside the @ref frame_file (do not include the dummy frame).
    # @param readback_file: If provided should be the full path of the file where the readback
    #                  data will be saved. Othervise the readback data is saved in a
    #                  timestamp-based file name inside the "readback-frame-files" of the
    #                  working folder.
    # @param is_fault_injection: Trus if this write is for fault injection experiment.
    #---------------------------------------------------------------------------------
    def readFrames(self,
                   frame_address,
                   frames=1,
                   readback_file=None,
                   is_fault_injection=False):
        if self.tcpClientSocketStatus != SocketStatus.CONNECTED:
            self.clientStart()
        self.frame_address = str(frame_address)
        self.frames = str(frames)
        XtclLog.writeLine(
            "================ Starting FPGA frame read ================ ",
            XtclCommon.blue)
        if readback_file == None:
            readbackFileName = "readbackBlock-0x" + str(
                self.frame_address) + "-" + str(
                    datetime.datetime.now().timestamp()) + ".rbd"
            self.readbackFile = XtclSettings.workingDirectory + '/readback-frame-files/' + readbackFileName
        else:
            self.readbackFile = XtclSettings.workingDirectory + '/readback-frame-files/' + readback_file

        if is_fault_injection == True:
            command = Operation.FAULT_INJECTION_READ.name + "%" + self.readbackFile + "%" + self.frame_address + "%" + self.frames
        else:
            command = Operation.FRAMES_READ.name + "%" + self.readbackFile + "%" + self.frame_address + "%" + self.frames
        self.clientSend(command)
        self.operation = Operation.FRAMES_READ
        return

    #---------------------------------------------------------------------------------
    # @brief This function reads the status signal of the user logic
    #---------------------------------------------------------------------------------
    def readLogicStatus(self):
        if self.tcpClientSocketStatus != SocketStatus.CONNECTED:
            self.clientStart()
        XtclLog.writeLine(
            "================ Reading active logic status ================ ",
            XtclCommon.blue)
        self.clientSend(Operation.LOGIC_STATUS.name)
        self.operation = Operation.LOGIC_STATUS
        return

    #---------------------------------------------------------------------------------
    # @brief This function reads a configuration register
    # @param register_address: The frame of the register in 5-bit format (i.e. 01010) of in HEX format (0A).
    #---------------------------------------------------------------------------------
    def readRegister(self, register_address):
        if self.tcpClientSocketStatus != SocketStatus.CONNECTED:
            self.clientStart()
        XtclLog.writeLine(
            "================ Reading configuration register ================ ",
            XtclCommon.blue)
        self.register_address = str(register_address)
        command = Operation.REGISTER_READ.name + "%" + self.register_address
        self.clientSend(command)
        self.operation = Operation.REGISTER_READ
        return

    #---------------------------------------------------------------------------------
    # @brief This function writes a configuration register
    # @param register_address: The frame of the register in 5-bit format (i.e. 01010) of in HEX format (0A).
    # @param register_value: The register value to be written in 32-bit HEX format (i.e. A000029B)
    #---------------------------------------------------------------------------------
    def writeRegister(self, register_address, register_value):
        if self.tcpClientSocketStatus != SocketStatus.CONNECTED:
            self.clientStart()
        XtclLog.writeLine(
            "================ Writing configuration register ================ ",
            XtclCommon.blue)
        self.register_address = str(register_address)
        self.register_value = str(register_value)
        command = Operation.REGISTER_WRITE.name + "%" + self.register_address + "%" + self.register_value
        self.clientSend(command)
        self.operation = Operation.REGISTER_WRITE
        return
예제 #30
0
class SerialControl(QObject):
    sig_keyseq_pressed = Signal(str)
    sig_CPU_comout = Signal(int)
    sig_CPU_comin = Signal(str)
    sig_port_change = Signal(str)
    sig_button_pressed = Signal(int)
    sig_terminal_open = Signal(bool)
    sig_firmware_update = Signal(str)

    def __init__(self, cpu, monitor_frame, terminal_frame, usb_frame,
                 statusbar, config, sig_update, config_file_path):
        QObject.__init__(self)

        self.cpu = cpu
        self.monitor = monitor_frame
        self.terminal = terminal_frame
        self.statusbar = statusbar
        self.config = config
        self.ser_port = None
        self.sig_update = sig_update
        self.fd_thread = None
        self.fwth = None
        self.monitor_frame = monitor_frame
        self.usb_frame = usb_frame
        self.config_file_path = config_file_path

        # Connect signal
        self.sig_keyseq_pressed.connect(self.on_key_pressed)
        self.sig_CPU_comout.connect(self.on_comout)
        self.sig_CPU_comin.connect(self.on_comin)
        self.sig_port_change.connect(self.on_port_change)
        self.sig_button_pressed.connect(self.on_button_dispatch)
        self.sig_terminal_open.connect(self.on_terminal_open)
        self.sig_firmware_update.connect(self.on_firmware_update)

        self.monitor_frame.sig_keyseq_pressed = self.sig_keyseq_pressed
        self.monitor_frame.sig_button_pressed = self.sig_button_pressed
        self.cpu.sig_CPU_comout = self.sig_CPU_comout
        self.cpu.sig_CPU_comin = self.sig_CPU_comin
        self.usb_frame.usb_combo.sig_port_change = self.sig_port_change
        self.usb_frame.sig_button_pressed = self.sig_button_pressed
        self.usb_frame.sig_firmware_update = self.sig_firmware_update

        self.terminal.sig_terminal_open = self.sig_terminal_open

        # Disable fw flash button if udr2 binary is nor present
        self.udr2 = f"cli/udr2-{sys.platform}" if sys.platform != "win32" else "cli\\udr2-win32.exe"
        if not os.path.isfile(self.udr2):
            self.usb_frame.firmware_btn.setEnabled(False)
        self.init_serial()

    def init_serial(self, do_refresh=True):
        is_thread = InitSerialThread(self)
        is_thread.update_combo = self.usb_frame.usb_combo.set_ports
        is_thread.do_refresh = do_refresh
        is_thread.start()

    def init_OK(self):
        self.statusbar.sig_temp_message.emit("Serial port Initialized")

    #
    # Signal Handling
    #
    @Slot(str)
    def on_key_pressed(self, key):
        if self.cpu.rx is None and len(key) == 1:
            self.cpu.rx = key
            self.monitor_frame.serial_in.setText(key)

    @Slot(str)
    def on_comin(self, char):
        """Char is handled by CPU, we free the slot"""
        self.monitor_frame.serial_in.setText(" ")

    @Slot(int)
    def on_comout(self, byte):
        """Append the char to the console"""
        self.monitor_frame.append_serial_out(byte)

    @Slot(str)
    def on_port_change(self, port):
        self.config.set("serial", "port", port)
        if self.ser_port:
            self.ser_port.port = port
        with open(self.config_file_path, 'w') as configfile:
            self.config.write(configfile)
        self.init_serial(False)

    @Slot(int)
    def on_button_dispatch(self, btn_nbr):
        if btn_nbr == 0:
            self.to_digirule()
        elif btn_nbr == 1:
            self.from_digirule()
        elif btn_nbr == 2:
            self.init_serial()
        elif btn_nbr == 3:
            self.on_clear_button()

    @Slot(bool)
    def on_terminal_open(self, is_open):
        if is_open:
            # Terminal window is open : create the terminal serial thread
            self.statusbar.sig_temp_message.emit("open terminal thread")
            self.terminal.serth = SerialThread(self)  # Start serial thread
            self.terminal.serth.start()
        else:
            # Terminal window is closed : quit the terminal serial thread
            if self.terminal.serth:
                self.terminal.serth.running = False
                sleep(0.5)
                self.terminal.serth = None

    def on_clear_button(self):
        self.monitor_frame.clear()  # Clear the serial in/out content
        self.cpu.rx = None
        self.cpu.tx = None

    def to_digirule(self):
        if self.ser_port is None:
            self.statusbar.sig_temp_message.emit(
                "Error : No serial port configured")
        else:
            dump = ram2hex(self.cpu.ram)
            self.statusbar.sig_temp_message.emit("Dumpimg memory on port " +
                                                 self.ser_port.port)
            try:
                self.ser_port.open()
            except serial.serialutil.SerialException as ex:
                self.statusbar.sig_temp_message.emit(ex.strerror)
            else:
                for line in dump.splitlines():
                    self.ser_port.write(line.encode("utf-8"))
                    sleep(0.1)
                sleep(2)
                self.ser_port.close()
                self.statusbar.sig_temp_message.emit("Memory sent")

    def from_digirule(self):
        """Launch receive sequence in background"""
        if self.ser_port:
            self.statusbar.sig_temp_message.emit(
                "Waiting to receive Digirule on " + self.ser_port.port)
            self.fd_thread = FromDigiruleThread(self)
            self.fd_thread.start()
        else:
            self.init_serial()

    @Slot(str)
    def on_firmware_update(self, filepath):
        if self.ser_port:
            self.proc = QProcess(self)
            self.proc.readyReadStandardOutput.connect(self.stdoutReady)
            self.proc.readyReadStandardError.connect(self.stderrReady)

            if sys.platform == "win32":
                command = f'{self.udr2} --program {self.ser_port.port} < {filepath}'
                self.usb_frame.out.write(
                    "Firmware update started, please wait ")
                # displays running dots on windows to pretend it is not stalled
                self.bullshitTimer = QTimer()
                self.bullshitTimer.timeout.connect(self.stdoutBullshit)
                self.bullshitTimer.start(1000)
                self.proc.setProcessChannelMode(QProcess.MergedChannels)
                self.proc.start('cmd.exe', ['/c', command])
            else:
                command = f'{self.udr2} --program {self.ser_port.port} < "{filepath}"'
                self.bullshitTimer = None
                self.proc.start('bash', ['-c', command])
            # print(command)

    def stdoutBullshit(self):
        self.usb_frame.out.write(".")

    def stdoutReady(self):
        if self.bullshitTimer:
            self.usb_frame.out.write("\n")
            self.bullshitTimer.stop()

        text = str(self.proc.readAllStandardOutput())
        self.usb_frame.out.write(eval(text).decode('iso8859-1'))

    def stderrReady(self):
        text = str(self.proc.readAllStandardError())
        self.usb_frame.out.write(eval(text).decode('iso8859-1'))
예제 #31
0
class MediaVideo(Media):

    def __init__(self, media, parent_widget):
        super(MediaVideo, self).__init__(media, parent_widget)
        self.widget = QWidget(parent_widget)
        self.process = QProcess(self.widget)
        self.process.setObjectName('%s-process' % self.objectName())
        self.std_out = []
        self.errors = []
        self.stopping = False
        self.mute = False
        self.widget.setGeometry(media['geometry'])
        self.connect(self.process, SIGNAL('error()'), self.process_error)
        self.connect(self.process, SIGNAL('finished()'), self.process_finished)
        self.connect(self.process, SIGNAL('started()'), self.process_started)
        self.set_default_widget_prop()
        self.stop_timer = QTimer(self)
        self.stop_timer.setSingleShot(True)
        self.stop_timer.setInterval(1000)
        self.stop_timer.timeout.connect(self.process_timeout)
        #---- kong ---- for RPi
        self.rect = media['geometry']
        #----

    @Slot()
    def process_timeout(self):
        os.kill(self.process.pid(), signal.SIGTERM)
        self.stopping = False
        if  not self.is_started():
            self.started_signal.emit()
        super(MediaVideo, self).stop()

    @Slot(object)
    def process_error(self, err):
        print('---- process error ----')
        self.errors.append(err)
        self.stop()

    @Slot()
    def process_finished(self):
        self.stop()

    @Slot()
    def process_started(self):
        self.stop_timer.stop()
        if  float(self.duration) > 0:
            self.play_timer.setInterval(int(float(self.duration) * 1000))
            self.play_timer.start()
        self.started_signal.emit()
        pass

    @Slot()
    def play(self):
        self.finished = 0
        self.widget.show()
        self.widget.raise_()
        uri = self.options['uri']
        path = f'content/{uri}'
        #---- kong ---- for RPi
        left, top, right, bottom = self.rect.getCoords()
        rect = f'{left},{top},{right},{bottom}'
        args = [ '--win', rect, '--no-osd', '--layer', self.zindex, path ]
        self.process.start('omxplayer.bin', args)
        self.stop_timer.start()
        #----

    @Slot()
    def stop(self, delete_widget=False):
        #---- kong ---- for RPi
        if  not self.widget:
            return False
        if  self.stopping or self.is_finished():
            return False
        self.stop_timer.start()
        self.stopping = True
        if  self.process.state() == QProcess.ProcessState.Running:
            self.process.write(b'q')
            self.process.waitForFinished()
            self.process.close()
        super(MediaVideo, self).stop(delete_widget)
        self.stopping = False
        self.stop_timer.stop()
        return True
예제 #32
0
class MediaText(Media):

    def __init__(self, media, parent_widget):
        super(MediaText, self).__init__(media, parent_widget)
        self.widget = QWidget(parent_widget)
        self.process = QProcess(self.widget)
        self.process.setObjectName('%s-process' % self.objectName())
        self.std_out = []
        self.errors = []
        self.stopping = False
        self.mute = False
        self.widget.setGeometry(media['geometry'])
        self.connect(self.process, SIGNAL('error()'), self.process_error)
        self.connect(self.process, SIGNAL('finished()'), self.process_finished)
        self.connect(self.process, SIGNAL('started()'), self.process_started)
        self.set_default_widget_prop()
        self.stop_timer = QTimer(self)
        self.stop_timer.setSingleShot(True)
        self.stop_timer.setInterval(1000)
        self.stop_timer.timeout.connect(self.process_timeout)
        self.rect = self.widget.geometry()

    @Slot()
    def process_timeout(self):
        os.kill(self.process.pid(), signal.SIGTERM)
        self.stopping = False
        if  not self.is_started():
            self.started_signal.emit()
        super(MediaText, self).stop()

    @Slot(object)
    def process_error(self, err):
        print('---- process error ----')
        self.errors.append(err)
        self.stop()

    @Slot()
    def process_finished(self):
        self.stop()

    @Slot()
    def process_started(self):
        self.stop_timer.stop()
        if  float(self.duration) > 0:
            self.play_timer.setInterval(int(float(self.duration) * 1000))
            self.play_timer.start()
        self.started_signal.emit()
        pass

    @Slot()
    def play(self):
        self.finished = 0
        self.widget.show()
        self.widget.raise_()
        #---- kong ----
        path = f'file:///home/pi/rdtone/urd/content/{self.layout_id}_{self.region_id}_{self.id}.html'
        
        print(path)
        
        l = str(self.rect.left())
        t = str(self.rect.top())
        w = str(self.rect.width())
        h = str(self.rect.height())
        s = f'--window-size={w},{h}'
        p = f'--window-position={l},{t}'
        args = [
            '--kiosk', s, p, path
            #l, t, w, h, path
        ]
        self.process.start('chromium-browser', args)
        #self.process.start('./xWeb', args)
        self.stop_timer.start()
        #----

    @Slot()
    def stop(self, delete_widget=False):
        #---- kong ----
        if  not self.widget:
            return False
        if  self.stopping or self.is_finished():
            return False
        self.stop_timer.start()
        self.stopping = True
        if  self.process.state() == QProcess.ProcessState.Running:
            #---- kill process ----
            os.system('pkill chromium')
            #os.system('pkill xWeb')
            #----
            self.process.waitForFinished()
            self.process.close()
        super(MediaText, self).stop(delete_widget)
        self.stopping = False
        self.stop_timer.stop()
        return True
예제 #33
0
class App(QApplication):
    def __init__(self, argv):
        super().__init__(argv)

        self._create_tray_icon()
        self._create_ui()
        self._create_interaction_server()

        self._session = None

    def open_preferences(self):
        prefs_dialog = PreferencesDialog()
        prefs_dialog.exec()

    def _mode_changed(self):
        action = self._mode_group.checkedAction()
        if action == self._mode_off:
            self._stop_session()
        elif action == self._mode_enabled:
            self._interaction_server.train = False
            self._start_session()
        elif action == self._mode_training:
            self._interaction_server.train = True
            self._start_session()

    def _start_session(self):
        if self._session is not None:
            return

        self._session = QProcess(self)
        self._session.finished.connect(self._session_ended)
        self._session.readyReadStandardOutput.connect(self._log_append_stdout)
        self._session.readyReadStandardError.connect(self._log_append_stderr)

        settings = QSettings()
        self._session.start(sys.executable, [
            'run_session.py',
            settings.value('CyKitAddress', app.DEFAULT_CYKIT_ADDRESS),
            str(settings.value('CyKitPort', app.DEFAULT_CYKIT_PORT)),
            str(self._interaction_server.port)
        ])

    def _stop_session(self):
        if self._session is not None:
            self._session.close()

    # TODO: Handle non-null exit codes
    def _session_ended(self):
        self._session = None
        self._mode_off.setChecked(True)

    def _log_append_stdout(self):
        process = self.sender()
        self._log_window.moveCursor(QTextCursor.End)
        self._log_window.insertPlainText(
            process.readAllStandardOutput().data().decode('utf-8'))
        self._log_window.moveCursor(QTextCursor.End)

    def _log_append_stderr(self):
        process = self.sender()
        self._log_window.moveCursor(QTextCursor.End)
        self._log_window.insertPlainText(
            process.readAllStandardError().data().decode('utf-8'))
        self._log_window.moveCursor(QTextCursor.End)

    def _select_letter(self, letter):
        self._letter_ui.setText(letter)

    def _create_tray_icon(self):
        menu = QMenu()

        self._mode_group = QActionGroup(menu)
        self._mode_group.triggered.connect(self._mode_changed)

        self._mode_off = QAction("&Off", parent=menu)
        self._mode_off.setCheckable(True)
        self._mode_off.setChecked(True)
        self._mode_group.addAction(self._mode_off)
        menu.addAction(self._mode_off)

        self._mode_enabled = QAction("&Enabled", parent=menu)
        self._mode_enabled.setCheckable(True)
        self._mode_group.addAction(self._mode_enabled)
        menu.addAction(self._mode_enabled)

        self._mode_training = QAction("&Training mode", parent=menu)
        self._mode_training.setCheckable(True)
        self._mode_group.addAction(self._mode_training)
        menu.addAction(self._mode_training)

        menu.addSeparator()
        menu.addAction("&Preferences", self.open_preferences)
        menu.addSeparator()
        menu.addAction("E&xit", self.exit)

        pixmap = QPixmap(32, 32)
        pixmap.fill(Qt.white)
        icon = QIcon(pixmap)

        self._tray_icon = QSystemTrayIcon(parent=self)
        self._tray_icon.setContextMenu(menu)
        self._tray_icon.setIcon(icon)
        self._tray_icon.show()

    def _create_ui(self):
        self._keyboard_ui = KeyboardUI()
        self._keyboard_ui.show()

        # TODO: Get rid of this in favor of os_interaction
        self._letter_ui = QLabel("-")
        self._letter_ui.setWindowTitle("Selected letter")
        self._letter_ui.setStyleSheet('font-size: 72pt')
        self._letter_ui.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self._letter_ui.setGeometry(600, 0, 100, 100)
        self._letter_ui.show()

        # TODO: Replace with more user-friendly log
        self._log_window = QTextBrowser()
        self._log_window.setWindowTitle("Session Log")
        self._log_window.setGeometry(700, 0, 500, 500)
        self._log_window.show()

    def _create_interaction_server(self):
        self._interaction_server = InteractionServer(self)
        self._interaction_server.keyboard_flash_row.connect(
            self._keyboard_ui.flash_row)
        self._interaction_server.keyboard_flash_col.connect(
            self._keyboard_ui.flash_col)
        self._interaction_server.keyboard_highlight_letter.connect(
            self._keyboard_ui.highlight_letter)
        self._interaction_server.keyboard_select_letter.connect(
            self._select_letter)
예제 #34
0
class QProcessExecutionManager(ExecutionManager):
    """Class to manage tool instance execution using a PySide2 QProcess."""

    def __init__(self, logger, program="", args=None, silent=False, semisilent=False):
        """Class constructor.

        Args:
            logger (LoggerInterface): a logger instance
            program (str): Path to program to run in the subprocess (e.g. julia.exe)
            args (list, optional): List of argument for the program (e.g. path to script file)
            silent (bool): Whether or not to emit logger msg signals
            semisilent (bool): If True, show Process Log messages
        """
        super().__init__(logger)
        self._program = program
        self._args = args if args is not None else []
        self._silent = silent  # Do not show Event Log nor Process Log messages
        self._semisilent = semisilent  # Do not show Event Log messages but show Process Log messages
        self.process_failed = False
        self.process_failed_to_start = False
        self.user_stopped = False
        self._process = QProcess(self)
        self.process_output = None  # stdout when running silent
        self.process_error = None  # stderr when running silent
        self._out_chunks = []
        self._err_chunks = []

    def program(self):
        """Program getter method."""
        return self._program

    def args(self):
        """Program argument getter method."""
        return self._args

    # noinspection PyUnresolvedReferences
    def start_execution(self, workdir=None):
        """Starts the execution of a command in a QProcess.

        Args:
            workdir (str, optional): Work directory
        """
        if workdir is not None:
            self._process.setWorkingDirectory(workdir)
        self._process.started.connect(self.process_started)
        self._process.finished.connect(self.on_process_finished)
        if not self._silent and not self._semisilent:  # Loud
            self._process.readyReadStandardOutput.connect(self.on_ready_stdout)
            self._process.readyReadStandardError.connect(self.on_ready_stderr)
            self._process.errorOccurred.connect(self.on_process_error)
            self._process.stateChanged.connect(self.on_state_changed)
        elif self._semisilent:  # semi-silent
            self._process.readyReadStandardOutput.connect(self.on_ready_stdout)
            self._process.readyReadStandardError.connect(self.on_ready_stderr)
        self._process.start(self._program, self._args)
        if self._process is not None and not self._process.waitForStarted(msecs=10000):
            self.process_failed = True
            self.process_failed_to_start = True
            self._process.deleteLater()
            self._process = None
            self.execution_finished.emit(-9998)

    def wait_for_process_finished(self, msecs=30000):
        """Wait for subprocess to finish.

        Args:
            msecs (int): Timeout in milliseconds

        Return:
            True if process finished successfully, False otherwise
        """
        if self._process is None:
            return False
        if self.process_failed or self.process_failed_to_start:
            return False
        if not self._process.waitForFinished(msecs):
            self.process_failed = True
            self._process.close()
            self._process = None
            return False
        return True

    @Slot()
    def process_started(self):
        """Run when subprocess has started."""

    @Slot(int)
    def on_state_changed(self, new_state):
        """Runs when QProcess state changes.

        Args:
            new_state (int): Process state number (``QProcess::ProcessState``)
        """
        if new_state == QProcess.Starting:
            self._logger.msg.emit("\tStarting program <b>{0}</b>".format(self._program))
            arg_str = " ".join(self._args)
            self._logger.msg.emit("\tArguments: <b>{0}</b>".format(arg_str))
        elif new_state == QProcess.Running:
            self._logger.msg_warning.emit("\tExecution in progress...")
        elif new_state == QProcess.NotRunning:
            # logging.debug("Process is not running")
            pass
        else:
            self._logger.msg_error.emit("Process is in an unspecified state")
            logging.error("QProcess unspecified state: %s", new_state)

    @Slot(int)
    def on_process_error(self, process_error):
        """Runs if there is an error in the running QProcess.

        Args:
            process_error (int): Process error number (``QProcess::ProcessError``)
        """
        if process_error == QProcess.FailedToStart:
            self.process_failed = True
            self.process_failed_to_start = True
            self._logger.msg_error.emit("Process failed to start")
        elif process_error == QProcess.Timedout:
            self.process_failed = True
            self._logger.msg_error.emit("Timed out")
        elif process_error == QProcess.Crashed:
            self.process_failed = True
            if not self.user_stopped:
                self._logger.msg_error.emit("Process crashed")
        elif process_error == QProcess.WriteError:
            self._logger.msg_error.emit("Process WriteError")
        elif process_error == QProcess.ReadError:
            self._logger.msg_error.emit("Process ReadError")
        elif process_error == QProcess.UnknownError:
            self._logger.msg_error.emit("Unknown error in process")
        else:
            self._logger.msg_error.emit("Unspecified error in process: {0}".format(process_error))
        self.teardown_process()

    def teardown_process(self):
        """Tears down the QProcess in case a QProcess.ProcessError occurred.
        Emits execution_finished signal."""
        if not self._process:
            pass
        else:
            out = str(self._process.readAllStandardOutput().data(), "utf-8", errors="replace")
            errout = str(self._process.readAllStandardError().data(), "utf-8", errors="replace")
            if out is not None:
                self._logger.msg_proc.emit(out.strip())
            if errout is not None:
                self._logger.msg_proc.emit(errout.strip())
            self._process.deleteLater()
            self._process = None
        self.execution_finished.emit(-9998)

    def stop_execution(self):
        """See base class."""
        self.user_stopped = True
        self.process_failed = True
        if not self._process:
            return
        try:
            self._process.kill()
            if not self._process.waitForFinished(5000):
                self._process.finished.emit(-1, -1)
                self._process.deleteLater()
        except Exception as ex:  # pylint: disable=broad-except
            self._logger.msg_error.emit("[{0}] exception when terminating process".format(ex))
            logging.exception("Exception in closing QProcess: %s", ex)
        finally:
            self._process = None

    @Slot(int, int)
    def on_process_finished(self, exit_code, exit_status):
        """Runs when subprocess has finished.

        Args:
            exit_code (int): Return code from external program (only valid for normal exits)
            exit_status (int): Crash or normal exit (``QProcess::ExitStatus``)
        """
        if not self._process:
            return
        if exit_status == QProcess.CrashExit:
            if not self._silent:
                self._logger.msg_error.emit("\tProcess crashed")
            exit_code = -1
        elif exit_status == QProcess.NormalExit:
            pass
        else:
            if not self._silent:
                self._logger.msg_error.emit("Unknown QProcess exit status [{0}]".format(exit_status))
            exit_code = -1
        if not exit_code == 0:
            self.process_failed = True
        if not self.user_stopped:
            out = str(self._process.readAllStandardOutput().data(), "utf-8", errors="replace")
            errout = str(self._process.readAllStandardError().data(), "utf-8", errors="replace")
            if out is not None:
                if not self._silent:
                    self._logger.msg_proc.emit(out.strip())
                else:
                    self.process_output = out.strip()
                    self.process_error = errout.strip()
        else:
            self._logger.msg.emit("*** Terminating process ***")
        # Delete QProcess
        self._process.deleteLater()
        self._process = None
        self.execution_finished.emit(exit_code)

    @Slot()
    def on_ready_stdout(self):
        """Emit data from stdout."""
        if not self._process:
            return
        self._process.setReadChannel(QProcess.StandardOutput)
        chunk = self._process.readLine().data()
        self._out_chunks.append(chunk)
        if not chunk.endswith(b"\n"):
            return
        line = b"".join(self._out_chunks)
        line = str(line, "unicode_escape", errors="replace").strip()
        self._logger.msg_proc.emit(line)
        self._out_chunks.clear()

    @Slot()
    def on_ready_stderr(self):
        """Emit data from stderr."""
        if not self._process:
            return
        self._process.setReadChannel(QProcess.StandardError)
        chunk = self._process.readLine().data()
        self._err_chunks.append(chunk)
        if not chunk.endswith(b"\n"):
            return
        line = b"".join(self._err_chunks)
        line = str(line, "utf-8", errors="replace").strip()
        self._logger.msg_proc_error.emit(line)
        self._err_chunks.clear()
예제 #35
0
class QSubProcess(QObject):
    """Class to handle starting, running, and finishing PySide2 QProcesses."""

    subprocess_finished_signal = Signal(int, name="subprocess_finished_signal")

    def __init__(self, toolbox, program=None, args=None, silent=False):
        """Class constructor.

        Args:
            toolbox (ToolboxUI): Instance of Main UI class.
            program (str): Path to program to run in the subprocess (e.g. julia.exe)
            args (list): List of argument for the program (e.g. path to script file)
            silent (bool): Whether or not to emit toolbox msg signals
        """
        super().__init__()
        self._toolbox = toolbox
        self._program = program
        self._args = args
        self._silent = silent
        self.process_failed = False
        self.process_failed_to_start = False
        self._user_stopped = False
        self._process = QProcess(self)
        self.output = None

    def program(self):
        """Program getter method."""
        return self._program

    def args(self):
        """Program argument getter method."""
        return self._args

    # noinspection PyUnresolvedReferences
    def start_process(self, workdir=None):
        """Start the execution of a command in a QProcess.

        Args:
            workdir (str): Directory for the script (at least with Julia this is a must)
        """
        if workdir is not None:
            self._process.setWorkingDirectory(workdir)
        self._process.started.connect(self.process_started)
        self._process.finished.connect(self.process_finished)
        if not self._silent:
            self._process.readyReadStandardOutput.connect(self.on_ready_stdout)
            self._process.readyReadStandardError.connect(self.on_ready_stderr)
            self._process.error.connect(
                self.on_process_error)  # errorOccurred available in Qt 5.6
            self._process.stateChanged.connect(self.on_state_changed)
        # self._toolbox.msg.emit("\tStarting program: <b>{0}</b>".format(self._program))
        self._process.start(self._program, self._args)
        if not self._process.waitForStarted(
                msecs=10000
        ):  # This blocks until process starts or timeout happens
            self.process_failed = True
            self.process_failed_to_start = True
            self._process.deleteLater()
            self._process = None
            self.subprocess_finished_signal.emit(-9998)

    def wait_for_finished(self, msecs=30000):
        """Wait for subprocess to finish.

        Return:
            True if process finished successfully, False otherwise
        """
        if not self._process:
            return False
        if self.process_failed or self.process_failed_to_start:
            return False
        if not self._process.waitForFinished(msecs):
            self.process_failed = True
            self._process.close()
            self._process = None
            return False
        return True

    @Slot(name="process_started")
    def process_started(self):
        """Run when subprocess has started."""
        pass
        # self._toolbox.msg.emit("\tSubprocess started...")

    @Slot("QProcess::ProcessState", name="on_state_changed")
    def on_state_changed(self, new_state):
        """Runs when QProcess state changes.

        Args:
            new_state (QProcess::ProcessState): Process state number
        """
        if new_state == QProcess.Starting:
            self._toolbox.msg.emit("\tStarting program <b>{0}</b>".format(
                self._program))
            arg_str = " ".join(self._args)
            self._toolbox.msg.emit("\tArguments: <b>{0}</b>".format(arg_str))
        elif new_state == QProcess.Running:
            self._toolbox.msg_warning.emit(
                "\tExecution is in progress. See Process Log for messages "
                "(stdout&stderr)")
        elif new_state == QProcess.NotRunning:
            # logging.debug("QProcess is not running")
            pass
        else:
            self._toolbox.msg_error.emit("Process is in an unspecified state")
            logging.error("QProcess unspecified state: {0}".format(new_state))

    @Slot("QProcess::ProcessError", name="'on_process_error")
    def on_process_error(self, process_error):
        """Run if there is an error in the running QProcess.

        Args:
            process_error (QProcess::ProcessError): Process error number
        """
        if process_error == QProcess.FailedToStart:
            # self._toolbox.msg_error.emit("Failed to start")
            self.process_failed = True
            self.process_failed_to_start = True
        elif process_error == QProcess.Timedout:
            self.process_failed = True
            self._toolbox.msg_error.emit("Timed out")
        elif process_error == QProcess.Crashed:
            self.process_failed = True
            self._toolbox.msg_error.emit("Process crashed")
        elif process_error == QProcess.WriteError:
            self._toolbox.msg_error.emit("Process WriteError")
        elif process_error == QProcess.ReadError:
            self._toolbox.msg_error.emit("Process ReadError")
        elif process_error == QProcess.UnknownError:
            self._toolbox.msg_error.emit("Unknown error in process")
        else:
            self._toolbox.msg_error.emit(
                "Unspecified error in process: {0}".format(process_error))

    def terminate_process(self):
        """Shutdown simulation in a QProcess."""
        self._toolbox.msg_error.emit("<br/>Terminating process")
        # logging.debug("Terminating QProcess nr.{0}. ProcessState:{1} and ProcessError:{2}"
        #               .format(self._process.processId(), self._process.state(), self._process.error()))
        self._user_stopped = True
        self.process_failed = True
        try:
            self._process.close()
        except Exception as ex:
            self._toolbox.msg_error.emit(
                "[{0}] exception when terminating process".format(ex))
            logging.exception("Exception in closing QProcess: {}".format(ex))

    @Slot(int, name="process_finished")
    def process_finished(self, exit_code):
        """Run when subprocess has finished.

        Args:
            exit_code (int): Return code from external program (only valid for normal exits)
        """
        # logging.debug("Error that occurred last: {0}".format(self._process.error()))
        exit_status = self._process.exitStatus()  # Normal or crash exit
        if exit_status == QProcess.CrashExit:
            if not self._silent:
                self._toolbox.msg_error.emit("\tProcess crashed")
            exit_code = -1
        elif exit_status == QProcess.NormalExit:
            if not self._silent:
                self._toolbox.msg.emit("\tProcess finished")
        else:
            if not self._silent:
                self._toolbox.msg_error.emit(
                    "Unknown QProcess exit status [{0}]".format(exit_status))
            exit_code = -1
        if not exit_code == 0:
            self.process_failed = True
        if not self._user_stopped:
            out = str(self._process.readAllStandardOutput().data(), "utf-8")
            if out is not None:
                if not self._silent:
                    self._toolbox.msg_proc.emit(out.strip())
                else:
                    self.output = out.strip()
        else:
            self._toolbox.msg.emit("*** Terminating process ***")
        # Delete QProcess
        self._process.deleteLater()
        self._process = None
        self.subprocess_finished_signal.emit(exit_code)

    @Slot(name="on_ready_stdout")
    def on_ready_stdout(self):
        """Emit data from stdout."""
        out = str(self._process.readAllStandardOutput().data(), "utf-8")
        self._toolbox.msg_proc.emit(out.strip())

    @Slot(name="on_ready_stderr")
    def on_ready_stderr(self):
        """Emit data from stderr."""
        err = str(self._process.readAllStandardError().data(), "utf-8")
        self._toolbox.msg_proc_error.emit(err.strip())