Ejemplo n.º 1
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
Ejemplo n.º 2
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())
Ejemplo n.º 3
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)
Ejemplo n.º 4
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())
Ejemplo n.º 5
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)
Ejemplo n.º 6
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
Ejemplo n.º 7
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)
Ejemplo n.º 8
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)
Ejemplo n.º 9
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)
Ejemplo n.º 10
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()
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())
Ejemplo n.º 12
0
class Standard(object):
    """Action
    download ---|--> install --|-->  ==  run
                v              v     \\
              cancel        cancel   uninstall
    """
    # common
    name = ""  # 应用名称
    desc = ""  # 应用描述
    icon = ""  # 应用图标
    app_url = ""  # 应用地址
    versions = {}  # 应用版本及下载地址

    # installed
    install_version = ""  # 选择安装的版本
    app_folder = ""  # 应用安装路径

    py_ = ""  # 解释器 (G 中实时获取)
    entry_ = ""  # 启动文件 (build.json实时获取)
    requirement_ = ""  # 缺失依赖

    def __init__(self, parent: QWidget, **kwargs):
        self.cls_name = self.__class__.__name__
        self.parent = parent
        self.action = kwargs.get("action", Actions.DOWNLOAD)
        self.thread_pool = QThreadPool()
        self.check(**kwargs)
        self.div: AppDiv
        self.div_init()
        self.count = 0
        self.start_time = 0
        self.cancel = False
        self.process = QProcess(self.parent)
        self.process.readyReadStandardOutput.connect(self.on_readoutput)
        self.process.readyReadStandardError.connect(self.on_readerror)

    def check(self, **kwargs):
        """检查已下载应用参数"""

        self.app_folder = kwargs.get("app_folder")  # 应用安装路径
        self.install_version = kwargs.get('install_version')  # 应用安装路径
        self.app_info(**kwargs)

    def app_info(self, **kwargs):
        raise NotImplementedError

    @property
    def pack_name(self):
        """安装后id"""
        return self.cls_name + "_" + self.install_version.replace('.', '_')

    @property
    def ui_name(self):
        """ui中id"""
        return self.cls_name + "_app"

    def _transfer(self, widget, value=None):
        if widget == 'bar':
            self.div.job.progressbar_signal.emit(value)
        elif widget == 'msg':
            self.div.job.msg_signal.emit(value)
        elif widget == 'switch':
            self.div.job.switch_signal.emit()
        elif widget == 'action':
            self.div.job.action_signal.emit(value)

    def before_handle(self):
        self.action = Actions.CANCEL
        self.cancel = False
        self.div.action.setText(Actions.to_zn(self.action))
        self._transfer('switch')

    def _tip(self, msg):
        self.parent.mainwindow.job.msg_box_signal.emit({"msg": str(msg)})

    def div_init(self):
        self.div = AppDiv(self.parent)
        self.div.icon.setStyleSheet(f"image: url({self.icon});")
        self.div.name.setText(self.name)
        self.div.action.setText(Actions.to_zn(self.action))
        self.div.action.clicked.connect(self.action_handler)
        if self.action == Actions.DOWNLOAD:
            for i in self.versions.keys():
                act = QAction(i, self.parent)
                setattr(self.div,
                        f"act_{'_'.join([j for j in i if i.isalnum()])}", act)
                self.div.menu.addAction(act)
            self.div.menu.triggered[QAction].connect(
                self.version_action_triggered)
            self.div.desc.setText(self.desc)
            self.div.desc.url = self.app_url  # 可点击
            setattr(self.parent, self.ui_name, self)
            self.parent.apps_layout.addLayout(self.div.layout)
        elif self.action == Actions.RUN:
            act_uninstall = QAction(Actions.to_zn(Actions.UNINSTALL),
                                    self.parent)
            act_setting = QAction("解释器", self.parent)
            act_upgrade = QAction(Actions.to_zn(Actions.UPGRADE), self.parent)
            setattr(self.div, f"act_uninstall", act_uninstall)
            setattr(self.div, f"act_setting", act_setting)
            setattr(self.div, f"act_upgrade", act_upgrade)
            self.div.menu.addAction(act_setting)
            self.div.menu.addAction(act_upgrade)
            self.div.menu.addAction(act_uninstall)
            self.div.menu.triggered[QAction].connect(
                self.menu_action_triggered)
            self.div.desc.setText(self.install_version)
            setattr(self.parent, self.pack_name, self)
            self.parent.installed_layout.addLayout(self.div.layout)

    def version_action_triggered(self, q):
        """点击版本号直接下载"""
        if self.action == Actions.CANCEL:
            return
        self.install_version = q.text()
        self.action_handler()

    def menu_action_triggered(self, q):
        """卸载/更新处理"""
        if self.action == Actions.CANCEL:
            return
        act = q.text()
        if act == "解释器":
            self.act_setting_slot()
        elif Actions.to_en(act) == Actions.UNINSTALL:
            self.uninstall_handler()
        elif Actions.to_en(act) == Actions.UPGRADE:
            self.upgrade_handler()

    def act_setting_slot(self):
        self.py_manage = PyManageWidget(self.parent, self.pack_name)
        self.py_manage.show()

    @before_download
    def download_handler(self):
        """
        版本号
        下载目录
        """
        url = self.versions[self.install_version]
        postfix = os.path.splitext(url)[-1]  # .zip
        self.app_folder = os.path.join(G.config.install_path, self.pack_name)
        file_temp = self.app_folder + postfix  # 压缩文件路径
        ## 文件续传
        if os.path.exists(file_temp):
            local_file = os.path.getsize(file_temp)
            headers = {'Range': 'bytes=%d-' % local_file}
            mode = 'ab'
        else:
            local_file = 0
            headers = {}
            mode = 'wb'
        # download
        self._transfer("msg", "获取中...")
        try:
            response = requests.get(url, stream=True, headers=headers)
            response.raise_for_status()
        except Exception as e:
            return False
        content_size = float(response.headers.get('Content-Length', 0))
        self._transfer("bar", dict(range=[0, content_size]))
        # save
        with open(file_temp, mode) as file:
            chunk_size = 1024
            for data in response.iter_content(chunk_size=chunk_size):
                if self.cancel or G.pool_done:
                    return False
                file.write(data)  ##
                self.count += 1
                ##show
                current = chunk_size * self.count + local_file
                if content_size:
                    self._transfer("bar", dict(value=current))
                speed = format_size(current / (time.time() - self.start_time))
                self._transfer(
                    "msg",
                    f"{round(current / 1024, 2)}KB/{round(content_size / 1024, 2) or '-'}KB | {speed}/s"
                )
        extract(file_temp)  # 解压
        return True

    def on_download_callback(self, res):
        self._transfer('switch')
        if res is True:
            data = {
                "cls_name": self.cls_name,
                "install_version": self.install_version,
                "action": Actions.RUN,
                "app_folder": self.app_folder,
                "py_": ""
            }
            G.config.installed_apps.update({self.pack_name: data})
            G.config.to_file()
            self.div.add_installed_layout(data)
        elif res is False:
            pass
        self.action = Actions.DOWNLOAD
        self._transfer("action", Actions.to_zn(self.action))

    def get_build(self):
        """
        :return:  path 路径
        """
        path = find_file(self.app_folder, 'build.json')
        if path:
            try:
                with open(path[0], 'r') as f:
                    build = json.load(f)
                entry = find_file(self.app_folder, build['entry'])[0]
                requirement = find_file(self.app_folder,
                                        build['requirement'])[0]
            except KeyError:
                raise Exception('请确保build.json中含有entry和requirement')
            except IndexError:
                raise Exception("未找到entry文件或requirement文件")
            except json.JSONDecodeError:
                raise Exception("build.json 有错误")
            return entry, requirement
        else:
            raise Exception("未找到文件build.json")

    @before_install
    def install_handler(self):
        """解析 build.json"""
        for line in self.requirement_:
            line = line.strip().replace('==', '>=')
            cmd_ = [self.py_, "-m", "pip", "install", line
                    ] + G.config.get_pypi_source()
            if self.cancel or G.pool_done:
                return False
            self.process.start(" ".join(cmd_))
            self.process.waitForFinished()
        return True

    def on_readoutput(self):
        output = self.process.readAllStandardOutput().data().decode()
        self._transfer("msg", output.replace('\n', ''))

    def on_readerror(self):
        error = self.process.readAllStandardError().data().decode()
        self._tip(error)

    def on_install_callback(self, res):
        self._transfer('switch')
        self.action = Actions.RUN
        self._transfer("action", Actions.to_zn(self.action))

    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)

    def upgrade_handler(self):
        pass

    @before_uninstall
    def uninstall_handler(self):
        try:
            if os.path.exists(self.app_folder) and os.path.isdir(
                    self.app_folder):
                shutil.rmtree(self.app_folder)
        except Exception as e:
            self._tip({"msg": str(e)})
        finally:
            for name, attr in self.div.__dict__.items():
                if name not in self.div.not_widget:
                    attr.deleteLater()
            G.config.installed_apps.pop(self.pack_name, None)
            G.config.to_file()

    def cancel_handler(self):
        self.cancel = True
        self._transfer("msg", "Releasing...")

    def action_handler(self):
        if self.action == Actions.DOWNLOAD:
            self.download_handler()
            # self.git_download_handler()
        elif self.action == Actions.CANCEL:
            self.cancel_handler()
        elif self.action == Actions.RUN:
            self.run_handler()
Ejemplo n.º 13
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')
Ejemplo n.º 14
0
class NewEnvWidget(QDialog, Ui_NewEnv):
    """新建虚拟环境  或  导入外部环境"""

    def __init__(self, interpreter):
        super(self.__class__, self).__init__()
        self.setupUi(self)
        self.setStyleSheet(qss)
        self.interpreter = interpreter
        self.job = NewEnvObject()
        self.job.sig.connect(self.tip_)
        self.thread_pool = QThreadPool()
        # rex
        # btn
        self.name.textChanged.connect(self.name_change_slot)
        self.exis_path.textChanged.connect(self.exis_path_change_slot)
        self.env_radio.clicked.connect(self.env_radio_slot)
        self.exit_radio.clicked.connect(self.exit_radio_slot)
        self.path_btn.clicked.connect(self.path_btn_slot)
        self.exit_path_btn.clicked.connect(self.exit_path_btn_slot)
        self.ok_btn.clicked.connect(self.ok_btn_slot)
        self.cancel_btn.clicked.connect(self.close)
        self.ready_action()

    def ready_action(self):
        self.env_radio_slot()
        self.name_change_slot()
        self.load_py()
        self.path.setText(venv_path)

    def load_py(self):
        for k, v in G.config.python_path.items():
            self.base_py_list.addItem(k)

    def name_change_slot(self):
        name = self.name.text()
        if name.strip() and name not in G.config.python_path:
            self.name.setStyleSheet("color:green")
            self.ok_btn.setEnabled(True)
        else:
            self.name.setStyleSheet("color:red")
            self.ok_btn.setDisabled(True)

    def env_radio_slot(self):
        self.exis_path.setDisabled(True)
        self.exit_path_btn.setDisabled(True)
        #
        self.path.setEnabled(True)
        self.path_btn.setEnabled(True)
        self.base_py_list.setEnabled(True)

    def exit_radio_slot(self):
        self.path.setDisabled(True)
        self.path_btn.setDisabled(True)
        self.base_py_list.setDisabled(True)
        #
        self.exis_path.setEnabled(True)
        self.exit_path_btn.setEnabled(True)

    def path_btn_slot(self):
        path = QFileDialog.getExistingDirectory(self, "新建虚拟环境至", '/')
        self.path.setText(path)

    def exit_path_btn_slot(self):
        path, _ = QFileDialog.getOpenFileName(self, "选择Python解释器", '/', 'Python Interpreter (*.exe)')
        if py_path_check(path):
            self.exis_path.setText(path)

    def exis_path_change_slot(self):
        path = self.exis_path.text()
        if py_path_check(path):
            self.exis_path.setText(path)
        else:
            self.exis_path.setText("")

    def ok_btn_slot(self):
        name = self.name.text()
        if self.env_radio.isChecked():
            path = self.path.text()
            py_ = G.config.python_path[self.base_py_list.currentText()]
            vir_path = os.path.join(path, name)
            self.create_env(py_, vir_path, name)
        if self.exit_radio.isChecked():
            path = self.exis_path.text()
            G.config.python_path.update({name: path})
            G.config.to_file()
            self.close()

    @Slot(str)
    def tip_(self, msg):
        QMessageBox.information(self, "提示", msg)
        self.close()

    def readout_slot(self):
        output = self.p.readAllStandardOutput().data().decode()
        self.infobox.sig.msg.emit(output)

    def finished_slot(self):
        if self.infobox_flag:
            self.infobox.close()
            G.config.to_file()
            self.job.sig.emit("创建完成")

    def create_env(self, py_, vir_path, name):
        self.infobox = ProgressMsgDialog(msg="准备中...")
        self.infobox.show()
        self.infobox.raise_()
        self.infobox_flag = False
        self.p = QProcess()
        self.p.readyReadStandardOutput.connect(self.readout_slot)
        self.p.finished.connect(self.finished_slot)
        virtualenv_ = "virtualenv"
        img_ = G.config.get_pypi_source()
        cmd = " ".join([py_, "-m", 'pip', 'install', virtualenv_] + img_)
        self.p.start(cmd)
        self.p.waitForFinished()
        # 开始新建venv
        self.infobox.sig.msg.emit('新建虚拟环境中...')
        cmd = " ".join([py_, "-m", virtualenv_, "--no-site-packages", vir_path])
        self.p.start(cmd)
        self.infobox_flag = True
        # record
        py_path = join_path(vir_path, 'Scripts', 'python.exe')
        G.config.python_path.update({name: py_path})

    def closeEvent(self, event):
        self.interpreter.load_py()
        event.accept()

    def window_cleanup(self):
        self.close()
Ejemplo n.º 15
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')
Ejemplo n.º 16
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
Ejemplo n.º 17
0
class WebMediaView(MediaView):
    def __init__(self, media, parent):
        super(WebMediaView, self).__init__(media, parent)
        self.widget = QWidget(parent)
        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(WebMediaView, 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 ----
        url = self.options['uri']
        args = [
            str(self.rect.left()),
            str(self.rect.top()),
            str(self.rect.width()),
            str(self.rect.height()),
            QUrl.fromPercentEncoding(QByteArray(url.encode('utf-8')))
        ]
        #self.process.start('dist/web.exe', args) # for windows
        #self.process.start('./dist/web', args) # for RPi
        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 ----
            self.process.terminate()  # for windows
            self.process.kill()  # for linux
            #os.system('pkill web') # for RPi
            #----
            self.process.waitForFinished()
            self.process.close()
        super(WebMediaView, self).stop(delete_widget)
        self.stopping = False
        self.stop_timer.stop()
        return True
Ejemplo n.º 18
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())
Ejemplo n.º 19
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