def run(self, argv, error_message, in_build_dir=False, timeout=30000): """ Execute a command and capture the output. """ if in_build_dir: project = self._project saved_cwd = os.getcwd() build_dir = project.path_from_user(project.build_dir) build_dir = QDir.toNativeSeparators(build_dir) os.chdir(build_dir) self._message_handler.verbose_message( "{0} is now the current directory".format(build_dir)) else: saved_cwd = None self._message_handler.verbose_message("Running '{0}'".format( ' '.join(argv))) QCoreApplication.processEvents() process = QProcess() process.readyReadStandardOutput.connect( lambda: self._message_handler.progress_message( QTextCodec.codecForLocale().toUnicode( process.readAllStandardOutput()).strip())) stderr_output = QByteArray() process.readyReadStandardError.connect( lambda: stderr_output.append(process.readAllStandardError())) process.start(argv[0], argv[1:]) finished = process.waitForFinished(timeout) if saved_cwd is not None: os.chdir(saved_cwd) self._message_handler.verbose_message( "{0} is now the current directory".format(saved_cwd)) if not finished: raise UserException(error_message, process.errorString()) if process.exitStatus() != QProcess.NormalExit or process.exitCode( ) != 0: raise UserException( error_message, QTextCodec.codecForLocale().toUnicode(stderr_output).strip())
def run(self, argv, error_message, in_build_dir=False): """ Execute a command and capture the output. """ if in_build_dir: project = self._project saved_cwd = os.getcwd() build_dir = project.path_from_user(project.build_dir) build_dir = QDir.toNativeSeparators(build_dir) os.chdir(build_dir) self._message_handler.verbose_message( "{0} is now the current directory".format(build_dir)) else: saved_cwd = None self._message_handler.verbose_message( "Running '{0}'".format(' '.join(argv))) QCoreApplication.processEvents() process = QProcess() process.readyReadStandardOutput.connect( lambda: self._message_handler.progress_message( QTextCodec.codecForLocale().toUnicode( process.readAllStandardOutput()).strip())) stderr_output = QByteArray() process.readyReadStandardError.connect( lambda: stderr_output.append(process.readAllStandardError())) process.start(argv[0], argv[1:]) finished = process.waitForFinished() if saved_cwd is not None: os.chdir(saved_cwd) self._message_handler.verbose_message( "{0} is now the current directory".format(saved_cwd)) if not finished: raise UserException(error_message, process.errorString()) if process.exitStatus() != QProcess.NormalExit or process.exitCode() != 0: raise UserException(error_message, QTextCodec.codecForLocale().toUnicode(stderr_output).strip())
class GUIProcess(QObject): """An external process which shows notifications in the GUI. Args: cmd: The command which was started. args: A list of arguments which gets passed. verbose: Whether to show more messages. _output_messages: Show output as messages. _started: Whether the underlying process is started. _proc: The underlying QProcess. _what: What kind of thing is spawned (process/editor/userscript/...). Used in messages. Signals: error/finished/started signals proxied from QProcess. """ error = pyqtSignal(QProcess.ProcessError) finished = pyqtSignal(int, QProcess.ExitStatus) started = pyqtSignal() def __init__(self, what, *, verbose=False, additional_env=None, output_messages=False, parent=None): super().__init__(parent) self._what = what self.verbose = verbose self._output_messages = output_messages self._started = False self.cmd = None self.args = None self.final_stdout: str = "" self.final_stderr: str = "" self._proc = QProcess(self) self._proc.errorOccurred.connect(self._on_error) self._proc.errorOccurred.connect(self.error) self._proc.finished.connect(self._on_finished) self._proc.finished.connect(self.finished) self._proc.started.connect(self._on_started) self._proc.started.connect(self.started) if additional_env is not None: procenv = QProcessEnvironment.systemEnvironment() for k, v in additional_env.items(): procenv.insert(k, v) self._proc.setProcessEnvironment(procenv) @pyqtSlot(QProcess.ProcessError) def _on_error(self, error): """Show a message if there was an error while spawning.""" if error == QProcess.Crashed and not utils.is_windows: # Already handled via ExitStatus in _on_finished return what = f"{self._what} {self.cmd!r}" error_descriptions = { QProcess.FailedToStart: f"{what.capitalize()} failed to start", QProcess.Crashed: f"{what.capitalize()} crashed", QProcess.Timedout: f"{what.capitalize()} timed out", QProcess.WriteError: f"Write error for {what}", QProcess.WriteError: f"Read error for {what}", } error_string = self._proc.errorString() msg = ': '.join([error_descriptions[error], error_string]) # We can't get some kind of error code from Qt... # https://bugreports.qt.io/browse/QTBUG-44769 # However, it looks like those strings aren't actually translated? known_errors = ['No such file or directory', 'Permission denied'] if (': ' in error_string and # pragma: no branch error_string.split(': ', maxsplit=1)[1] in known_errors): msg += f'\n(Hint: Make sure {self.cmd!r} exists and is executable)' message.error(msg) @pyqtSlot(int, QProcess.ExitStatus) def _on_finished(self, code, status): """Show a message when the process finished.""" self._started = False log.procs.debug("Process finished with code {}, status {}.".format( code, status)) encoding = locale.getpreferredencoding(do_setlocale=False) stderr = self._proc.readAllStandardError().data().decode( encoding, 'replace') stdout = self._proc.readAllStandardOutput().data().decode( encoding, 'replace') if self._output_messages: if stdout: message.info(stdout.strip()) if stderr: message.error(stderr.strip()) if status == QProcess.CrashExit: exitinfo = "{} crashed.".format(self._what.capitalize()) message.error(exitinfo) elif status == QProcess.NormalExit and code == 0: exitinfo = "{} exited successfully.".format( self._what.capitalize()) if self.verbose: message.info(exitinfo) else: assert status == QProcess.NormalExit # We call this 'status' here as it makes more sense to the user - # it's actually 'code'. exitinfo = ("{} exited with status {}, see :messages for " "details.").format(self._what.capitalize(), code) message.error(exitinfo) if stdout: log.procs.error("Process stdout:\n" + stdout.strip()) if stderr: log.procs.error("Process stderr:\n" + stderr.strip()) qutescheme.spawn_output = self._spawn_format(exitinfo, stdout, stderr) self.final_stdout = stdout self.final_stderr = stderr def _spawn_format(self, exitinfo, stdout, stderr): """Produce a formatted string for spawn output.""" stdout = (stdout or "(No output)").strip() stderr = (stderr or "(No output)").strip() spawn_string = ("{}\n" "\nProcess stdout:\n {}" "\nProcess stderr:\n {}").format( exitinfo, stdout, stderr) return spawn_string @pyqtSlot() def _on_started(self): """Called when the process started successfully.""" log.procs.debug("Process started.") assert not self._started self._started = True def _pre_start(self, cmd, args): """Prepare starting of a QProcess.""" if self._started: raise ValueError("Trying to start a running QProcess!") self.cmd = cmd self.args = args fake_cmdline = ' '.join(shlex.quote(e) for e in [cmd] + list(args)) log.procs.debug("Executing: {}".format(fake_cmdline)) if self.verbose: message.info('Executing: ' + fake_cmdline) def start(self, cmd, args): """Convenience wrapper around QProcess::start.""" log.procs.debug("Starting process.") self._pre_start(cmd, args) self._proc.start(cmd, args) self._proc.closeWriteChannel() def start_detached(self, cmd, args): """Convenience wrapper around QProcess::startDetached.""" log.procs.debug("Starting detached.") self._pre_start(cmd, args) ok, _pid = self._proc.startDetached(cmd, args, None) # type: ignore[call-arg] if not ok: message.error("Error while spawning {}".format(self._what)) return False log.procs.debug("Process started.") self._started = True return True def exit_status(self): return self._proc.exitStatus()
class VideoService(QObject): def __init__(self, parent): super(VideoService, self).__init__(parent) self.parent = parent self.consoleOutput = '' if sys.platform == 'win32': self.backend = os.path.join(self.getAppPath(), 'bin', 'ffmpeg.exe') if not os.path.exists(self.backend): self.backend = find_executable('ffmpeg.exe') elif sys.platform == 'darwin': self.backend = os.path.join(self.getAppPath(), 'bin', 'ffmpeg') else: for exe in ('ffmpeg', 'avconv'): exe_path = find_executable(exe) if exe_path is not None: self.backend = exe_path break self.initProc() def initProc(self) -> None: self.proc = QProcess(self.parent) self.proc.setProcessChannelMode(QProcess.MergedChannels) env = QProcessEnvironment.systemEnvironment() self.proc.setProcessEnvironment(env) self.proc.setWorkingDirectory(self.getAppPath()) if hasattr(self.proc, 'errorOccurred'): self.proc.errorOccurred.connect(self.cmdError) def capture(self, source: str, frametime: str) -> QPixmap: img, capres = None, QPixmap() try: img = QTemporaryFile(os.path.join(QDir.tempPath(), 'XXXXXX.jpg')) if img.open(): imagecap = img.fileName() args = '-ss %s -i "%s" -vframes 1 -s 100x70 -y %s' % ( frametime, source, imagecap) if self.cmdExec(self.backend, args): capres = QPixmap(imagecap, 'JPG') finally: del img return capres def cut(self, source: str, output: str, frametime: str, duration: str) -> bool: args = '-i "%s" -ss %s -t %s -vcodec copy -acodec copy -y "%s"' \ % (source, frametime, duration, QDir.fromNativeSeparators(output)) return self.cmdExec(self.backend, args) def join(self, filelist: list, output: str) -> bool: args = '-f concat -safe 0 -i "%s" -c copy -y "%s"' % ( filelist, QDir.fromNativeSeparators(output)) return self.cmdExec(self.backend, args) def cmdExec(self, cmd: str, args: str = None) -> bool: if os.getenv('DEBUG', False): print('VideoService CMD: %s %s' % (cmd, args)) if self.proc.state() == QProcess.NotRunning: self.proc.start(cmd, shlex.split(args)) self.proc.waitForFinished(-1) if self.proc.exitStatus( ) == QProcess.NormalExit and self.proc.exitCode() == 0: return True return False @pyqtSlot(QProcess.ProcessError) def cmdError(self, error: QProcess.ProcessError) -> None: if error != QProcess.Crashed: QMessageBox.critical(self.parent.parent, '', '<h4>%s Error:</h4>' % self.backend + '<p>%s</p>' % self.proc.errorString(), buttons=QMessageBox.Close) qApp.quit() def getAppPath(self) -> str: if getattr(sys, 'frozen', False): return sys._MEIPASS return QFileInfo(__file__).absolutePath()
class GUIProcess(QObject): """An external process which shows notifications in the GUI. Args: cmd: The command which was started. args: A list of arguments which gets passed. verbose: Whether to show more messages. _started: Whether the underlying process is started. _proc: The underlying QProcess. _what: What kind of thing is spawned (process/editor/userscript/...). Used in messages. Signals: error/finished/started signals proxied from QProcess. """ error = pyqtSignal(QProcess.ProcessError) finished = pyqtSignal(int, QProcess.ExitStatus) started = pyqtSignal() def __init__(self, what, *, verbose=False, additional_env=None, parent=None): super().__init__(parent) self._what = what self.verbose = verbose self._started = False self.cmd = None self.args = None self._proc = QProcess(self) self._proc.errorOccurred.connect(self._on_error) self._proc.errorOccurred.connect(self.error) self._proc.finished.connect(self._on_finished) self._proc.finished.connect(self.finished) self._proc.started.connect(self._on_started) self._proc.started.connect(self.started) if additional_env is not None: procenv = QProcessEnvironment.systemEnvironment() for k, v in additional_env.items(): procenv.insert(k, v) self._proc.setProcessEnvironment(procenv) @pyqtSlot() def _on_error(self): """Show a message if there was an error while spawning.""" msg = self._proc.errorString() message.error("Error while spawning {}: {}".format(self._what, msg)) @pyqtSlot(int, QProcess.ExitStatus) def _on_finished(self, code, status): """Show a message when the process finished.""" self._started = False log.procs.debug("Process finished with code {}, status {}.".format( code, status)) encoding = locale.getpreferredencoding(do_setlocale=False) stderr = bytes(self._proc.readAllStandardError()).decode( encoding, 'replace') stdout = bytes(self._proc.readAllStandardOutput()).decode( encoding, 'replace') if status == QProcess.CrashExit: exitinfo = "{} crashed!".format(self._what.capitalize()) message.error(exitinfo) elif status == QProcess.NormalExit and code == 0: exitinfo = "{} exited successfully.".format( self._what.capitalize()) if self.verbose: message.info(exitinfo) else: assert status == QProcess.NormalExit # We call this 'status' here as it makes more sense to the user - # it's actually 'code'. exitinfo = ("{} exited with status {}, see :messages for " "details.").format(self._what.capitalize(), code) message.error(exitinfo) if stdout: log.procs.error("Process stdout:\n" + stdout.strip()) if stderr: log.procs.error("Process stderr:\n" + stderr.strip()) glimpsescheme.spawn_output = self._spawn_format(exitinfo, stdout, stderr) def _spawn_format(self, exitinfo, stdout, stderr): """Produce a formatted string for spawn output.""" stdout = (stdout or "(No output)").strip() stderr = (stderr or "(No output)").strip() spawn_string = ("{}\n" "\nProcess stdout:\n {}" "\nProcess stderr:\n {}").format(exitinfo, stdout, stderr) return spawn_string @pyqtSlot() def _on_started(self): """Called when the process started successfully.""" log.procs.debug("Process started.") assert not self._started self._started = True def _pre_start(self, cmd, args): """Prepare starting of a QProcess.""" if self._started: raise ValueError("Trying to start a running QProcess!") self.cmd = cmd self.args = args fake_cmdline = ' '.join(shlex.quote(e) for e in [cmd] + list(args)) log.procs.debug("Executing: {}".format(fake_cmdline)) if self.verbose: message.info('Executing: ' + fake_cmdline) def start(self, cmd, args): """Convenience wrapper around QProcess::start.""" log.procs.debug("Starting process.") self._pre_start(cmd, args) self._proc.start(cmd, args) self._proc.closeWriteChannel() def start_detached(self, cmd, args): """Convenience wrapper around QProcess::startDetached.""" log.procs.debug("Starting detached.") self._pre_start(cmd, args) ok, _pid = self._proc.startDetached(cmd, args, None) if not ok: message.error("Error while spawning {}".format(self._what)) return False log.procs.debug("Process started.") self._started = True return True def exit_status(self): return self._proc.exitStatus()
class GUIProcess(QObject): """An external process which shows notifications in the GUI. Args: cmd: The command which was started. args: A list of arguments which gets passed. verbose: Whether to show more messages. running: Whether the underlying process is started. what: What kind of thing is spawned (process/editor/userscript/...). Used in messages. _output_messages: Show output as messages. _proc: The underlying QProcess. Signals: error/finished/started signals proxied from QProcess. """ error = pyqtSignal(QProcess.ProcessError) finished = pyqtSignal(int, QProcess.ExitStatus) started = pyqtSignal() def __init__( self, what: str, *, verbose: bool = False, additional_env: Mapping[str, str] = None, output_messages: bool = False, parent: QObject = None, ): super().__init__(parent) self.what = what self.verbose = verbose self._output_messages = output_messages self.outcome = ProcessOutcome(what=what) self.cmd: Optional[str] = None self.args: Optional[Sequence[str]] = None self.pid: Optional[int] = None self.stdout: str = "" self.stderr: str = "" self._cleanup_timer = usertypes.Timer(self, 'process-cleanup') self._cleanup_timer.setTimerType(Qt.VeryCoarseTimer) self._cleanup_timer.setInterval(3600 * 1000) # 1h self._cleanup_timer.timeout.connect(self._on_cleanup_timer) self._cleanup_timer.setSingleShot(True) self._proc = QProcess(self) self._proc.errorOccurred.connect(self._on_error) self._proc.errorOccurred.connect(self.error) self._proc.finished.connect(self._on_finished) self._proc.finished.connect(self.finished) self._proc.started.connect(self._on_started) self._proc.started.connect(self.started) self._proc.readyReadStandardOutput.connect(self._on_ready_read_stdout) self._proc.readyReadStandardError.connect(self._on_ready_read_stderr) if additional_env is not None: procenv = QProcessEnvironment.systemEnvironment() for k, v in additional_env.items(): procenv.insert(k, v) self._proc.setProcessEnvironment(procenv) def __str__(self) -> str: if self.cmd is None or self.args is None: return f'<unknown {self.what} command>' return ' '.join(shlex.quote(e) for e in [self.cmd] + list(self.args)) def _decode_data(self, qba: QByteArray) -> str: """Decode data coming from a process.""" encoding = locale.getpreferredencoding(do_setlocale=False) return qba.data().decode(encoding, 'replace') def _process_text(self, data: QByteArray, attr: str) -> None: """Process new stdout/stderr text. Arguments: data: The new process data. attr: Either 'stdout' or 'stderr'. """ text = self._decode_data(data) if '\r' in text and not utils.is_windows: # Crude handling of CR for e.g. progress output. # Discard everything before the last \r in the new input, then discard # everything after the last \n in self.stdout/self.stderr. text = text.rsplit('\r', maxsplit=1)[-1] existing = getattr(self, attr) if '\n' in existing: new = existing.rsplit('\n', maxsplit=1)[0] + '\n' else: new = '' setattr(self, attr, new) if attr == 'stdout': self.stdout += text elif attr == 'stderr': self.stderr += text else: raise utils.Unreachable(attr) @pyqtSlot() def _on_ready_read_stdout(self) -> None: if not self._output_messages: return self._process_text(self._proc.readAllStandardOutput(), 'stdout') message.info(self._elide_output(self.stdout), replace=f"stdout-{self.pid}") @pyqtSlot() def _on_ready_read_stderr(self) -> None: if not self._output_messages: return self._process_text(self._proc.readAllStandardError(), 'stderr') message.error(self._elide_output(self.stderr), replace=f"stderr-{self.pid}") @pyqtSlot(QProcess.ProcessError) def _on_error(self, error: QProcess.ProcessError) -> None: """Show a message if there was an error while spawning.""" if error == QProcess.Crashed and not utils.is_windows: # Already handled via ExitStatus in _on_finished return what = f"{self.what} {self.cmd!r}" error_descriptions = { QProcess.FailedToStart: f"{what.capitalize()} failed to start", QProcess.Crashed: f"{what.capitalize()} crashed", QProcess.Timedout: f"{what.capitalize()} timed out", QProcess.WriteError: f"Write error for {what}", QProcess.ReadError: f"Read error for {what}", } error_string = self._proc.errorString() msg = ': '.join([error_descriptions[error], error_string]) # We can't get some kind of error code from Qt... # https://bugreports.qt.io/browse/QTBUG-44769 # However, it looks like those strings aren't actually translated? known_errors = ['No such file or directory', 'Permission denied'] if (': ' in error_string and # pragma: no branch error_string.split(': ', maxsplit=1)[1] in known_errors): msg += f'\nHint: Make sure {self.cmd!r} exists and is executable' if version.is_flatpak(): msg += ' inside the Flatpak container' message.error(msg) def _elide_output(self, output: str) -> str: """Shorten long output before showing it.""" output = output.strip() lines = output.splitlines() count = len(lines) threshold = 20 if count > threshold: lines = [ f'[{count - threshold} lines hidden, see :process for the full output]' ] + lines[-threshold:] output = '\n'.join(lines) return output @pyqtSlot(int, QProcess.ExitStatus) def _on_finished(self, code: int, status: QProcess.ExitStatus) -> None: """Show a message when the process finished.""" log.procs.debug("Process finished with code {}, status {}.".format( code, status)) self.outcome.running = False self.outcome.code = code self.outcome.status = status self.stderr += self._decode_data(self._proc.readAllStandardError()) self.stdout += self._decode_data(self._proc.readAllStandardOutput()) if self._output_messages: if self.stdout: message.info( self._elide_output(self.stdout), replace=f"stdout-{self.pid}") if self.stderr: message.error( self._elide_output(self.stderr), replace=f"stderr-{self.pid}") if self.outcome.was_successful(): if self.verbose: message.info(str(self.outcome)) self._cleanup_timer.start() else: if self.stdout: log.procs.error("Process stdout:\n" + self.stdout.strip()) if self.stderr: log.procs.error("Process stderr:\n" + self.stderr.strip()) message.error(str(self.outcome) + " See :process for details.") @pyqtSlot() def _on_started(self) -> None: """Called when the process started successfully.""" log.procs.debug("Process started.") assert not self.outcome.running self.outcome.running = True def _pre_start(self, cmd: str, args: Sequence[str]) -> None: """Prepare starting of a QProcess.""" if self.outcome.running: raise ValueError("Trying to start a running QProcess!") self.cmd = cmd self.args = args log.procs.debug(f"Executing: {self}") if self.verbose: message.info(f'Executing: {self}') def start(self, cmd: str, args: Sequence[str]) -> None: """Convenience wrapper around QProcess::start.""" log.procs.debug("Starting process.") self._pre_start(cmd, args) self._proc.start(cmd, args) self._post_start() self._proc.closeWriteChannel() def start_detached(self, cmd: str, args: Sequence[str]) -> bool: """Convenience wrapper around QProcess::startDetached.""" log.procs.debug("Starting detached.") self._pre_start(cmd, args) ok, self.pid = self._proc.startDetached( cmd, args, None) # type: ignore[call-arg] if not ok: message.error("Error while spawning {}".format(self.what)) return False log.procs.debug("Process started.") self.outcome.running = True self._post_start() return True def _post_start(self) -> None: """Register this process and remember the process ID after starting.""" self.pid = self._proc.processId() all_processes[self.pid] = self global last_pid last_pid = self.pid @pyqtSlot() def _on_cleanup_timer(self) -> None: """Remove the process from all registered processes.""" log.procs.debug(f"Cleaning up data for {self.pid}") assert self.pid in all_processes all_processes[self.pid] = None self.deleteLater() def terminate(self, kill: bool = False) -> None: """Terminate or kill the process.""" if kill: self._proc.kill() else: self._proc.terminate()
class GUIProcess(QObject): """An external process which shows notifications in the GUI. Args: cmd: The command which was started. args: A list of arguments which gets passed. verbose: Whether to show more messages. _started: Whether the underlying process is started. _proc: The underlying QProcess. _what: What kind of thing is spawned (process/editor/userscript/...). Used in messages. Signals: error/finished/started signals proxied from QProcess. """ error = pyqtSignal(QProcess.ProcessError) finished = pyqtSignal(int, QProcess.ExitStatus) started = pyqtSignal() def __init__(self, what, *, verbose=False, additional_env=None, parent=None): super().__init__(parent) self._what = what self.verbose = verbose self._started = False self.cmd = None self.args = None self._proc = QProcess(self) self._proc.errorOccurred.connect(self._on_error) self._proc.errorOccurred.connect(self.error) self._proc.finished.connect(self._on_finished) self._proc.finished.connect(self.finished) self._proc.started.connect(self._on_started) self._proc.started.connect(self.started) if additional_env is not None: procenv = QProcessEnvironment.systemEnvironment() for k, v in additional_env.items(): procenv.insert(k, v) self._proc.setProcessEnvironment(procenv) @pyqtSlot() def _on_error(self): """Show a message if there was an error while spawning.""" msg = self._proc.errorString() message.error("Error while spawning {}: {}".format(self._what, msg)) @pyqtSlot(int, QProcess.ExitStatus) def _on_finished(self, code, status): """Show a message when the process finished.""" self._started = False log.procs.debug("Process finished with code {}, status {}.".format( code, status)) encoding = locale.getpreferredencoding(do_setlocale=False) stderr = bytes(self._proc.readAllStandardError()).decode( encoding, 'replace') stdout = bytes(self._proc.readAllStandardOutput()).decode( encoding, 'replace') if status == QProcess.CrashExit: exitinfo = "{} crashed!".format(self._what.capitalize()) message.error(exitinfo) elif status == QProcess.NormalExit and code == 0: exitinfo = "{} exited successfully.".format( self._what.capitalize()) if self.verbose: message.info(exitinfo) else: assert status == QProcess.NormalExit # We call this 'status' here as it makes more sense to the user - # it's actually 'code'. exitinfo = ("{} exited with status {}, see :messages for " "details.").format(self._what.capitalize(), code) message.error(exitinfo) if stdout: log.procs.error("Process stdout:\n" + stdout.strip()) if stderr: log.procs.error("Process stderr:\n" + stderr.strip()) qutescheme.spawn_output = self._spawn_format(exitinfo, stdout, stderr) def _spawn_format(self, exitinfo, stdout, stderr): """Produce a formatted string for spawn output.""" stdout = (stdout or "(No output)").strip() stderr = (stderr or "(No output)").strip() spawn_string = ("{}\n" "\nProcess stdout:\n {}" "\nProcess stderr:\n {}").format(exitinfo, stdout, stderr) return spawn_string @pyqtSlot() def _on_started(self): """Called when the process started successfully.""" log.procs.debug("Process started.") assert not self._started self._started = True def _pre_start(self, cmd, args): """Prepare starting of a QProcess.""" if self._started: raise ValueError("Trying to start a running QProcess!") self.cmd = cmd self.args = args fake_cmdline = ' '.join(shlex.quote(e) for e in [cmd] + list(args)) log.procs.debug("Executing: {}".format(fake_cmdline)) if self.verbose: message.info('Executing: ' + fake_cmdline) def start(self, cmd, args): """Convenience wrapper around QProcess::start.""" log.procs.debug("Starting process.") self._pre_start(cmd, args) self._proc.start(cmd, args) self._proc.closeWriteChannel() def start_detached(self, cmd, args): """Convenience wrapper around QProcess::startDetached.""" log.procs.debug("Starting detached.") self._pre_start(cmd, args) ok, _pid = self._proc.startDetached(cmd, args, None) if not ok: message.error("Error while spawning {}".format(self._what)) return False log.procs.debug("Process started.") self._started = True return True def exit_status(self): return self._proc.exitStatus()
class Downloader(QDialog): dltool_cmd = 'aria2c' dltool_args = '-x 12 -d {dl_path} {dl_link}' def __init__(self, link_url: str, dl_path: str, parent=None): super(Downloader, self).__init__(parent) self.parent = parent self.dltool_cmd = find_executable(self.download_cmd) self.download_link = link_url self.download_path = dl_path if self.dltool_cmd.strip(): self.dltool_args = self.dltool_args.format( dl_path=self.download_path, dl_link=self.download_link) self.console = QTextEdit(self.parent) self.console.setWindowTitle('%s Downloader' % qApp.applicationName()) self.proc = QProcess(self.parent) layout = QVBoxLayout() layout.addWidget(self.console) self.setLayout(layout) self.setFixedSize(QSize(400, 300)) else: QMessageBox.critical( self.parent, 'DOWNLOADER ERROR', '<p>The <b>aria2c</b> executable binary could not ' + 'be found in your installation folders. The binary comes packaged with this ' + 'application so it is likely that it was accidentally deleted via human ' + 'intervntion or incorrect file permissions are preventing access to it.</p>' + '<p>You may either download and install <b>aria2</b> manually yourself, ensuring ' + 'its installation location is globally accessible via PATH environmnt variables or ' + 'simply reinstall this application again. If the issue is not resolved then try ' + 'to download the application again incase the orignal you installed already was ' + 'corrupted/broken.', buttons=QMessageBox.Close) def __del__(self) -> None: self.proc.terminate() if not self.proc.waitForFinished(10000): self.proc.kill() @staticmethod def get_machine_code() -> str: mcode = '' if sys.platform == 'darwin': mcode = 'macOS' elif sys.platform == 'win32' and platform.machine().endswith('86'): mcode = 'win32' elif sys.platform == 'win32' and platform.machine().endswith('64'): mcode = 'win64' elif sys.platform.startswith('linux') and platform.machine().endswith( '86'): mcode = 'linux32' elif sys.platform.startswith('linux') and platform.machine().endswith( '64'): mcode = 'linux64' return mcode @staticmethod def setup_aria() -> bool: aria_zip = Downloader.aria_clients()[ Downloader.get_machine_code()]['bin_archive'] aria_install = Downloader.aria_clients()[ Downloader.get_machine_code()]['target_path'] if os.path.exists(aria_zip): with ZipFile(aria_zip) as archive: target_path, target_file = os.path.split(aria_install) extracted_path = archive.extract(target_file, path=target_path) if extracted_path == aria_install and os.path.exists( extracted_path): if sys.platform is not 'win32': os.chmod(extracted_path, 0o755) return True return False def init_proc(self) -> None: self.proc.setProcessChannelMode(QProcess.MergedChannels) self.proc.readyRead.connect(self.console_output) self.proc.setProgram(self.aria2_cmd) self.proc.setArguments(shlex.split(self.aria2_args)) def start(self) -> None: self.init_proc() self.show() self.proc.start() @pyqtSlot() def console_output(self) -> None: self.console.append(str(self.proc.readAllStandardOutput())) @pyqtSlot(QProcess.ProcessError) def cmd_error(self, error: QProcess.ProcessError) -> None: if error != QProcess.Crashed: QMessageBox.critical(self.parent, 'Error calling an external process', self.proc.errorString(), buttons=QMessageBox.Close) @staticmethod def get_path(path: str) -> str: prefix = sys._MEIPASS if getattr( sys, 'frozen', False) else QFileInfo(__file__).absolutePath() return os.path.join(prefix, path)
class Updater(QThread): updateAvailable = pyqtSignal(bool, str) pypi_api_endpoint = 'https://pypi.python.org/pypi/vidcutter/json' github_api_endpoint = 'https://api.github.com/repos/ozmartian/vidcutter/releases/latest' latest_release_webpage = 'https://github.com/ozmartian/vidcutter/releases/latest' def __init__(self): QThread.__init__(self) def __del__(self) -> None: self.wait() @staticmethod def restart_app(): os.execl(sys.executable, sys.executable, *sys.argv) def cmd_exec(self, cmd: str, args: str = None) -> bool: self.proc = QProcess(self) self.proc.setProcessChannelMode(QProcess.MergedChannels) self.proc.setWorkingDirectory(QFileInfo(__file__).absolutePath()) if hasattr(self.proc, 'errorOccurred'): self.proc.errorOccurred.connect(self.cmdError) if self.proc.state() == QProcess.NotRunning: self.proc.start(cmd, shlex.split(args)) self.proc.waitForFinished(-1) if self.proc.exitStatus( ) == QProcess.NormalExit and self.proc.exitCode() == 0: return True return False @pyqtSlot(QProcess.ProcessError) def cmdError(self, error: QProcess.ProcessError) -> None: if error != QProcess.Crashed: QMessageBox.critical(None, "Error calling an external process", self.proc.errorString(), buttons=QMessageBox.Close) def check_latest_github(self) -> None: try: res = json.loads( urlopen(self.github_api_endpoint).read().decode('utf-8')) if 'tag_name' in res.keys(): latest_release = str(res['tag_name']) if LooseVersion(latest_release) > LooseVersion( qApp.applicationVersion()): # self.notify_update(version=latest_release, installer=self.install_update) self.updateAvailable.emit(True, latest_release) return self.updateAvailable.emit(False, None) except HTTPError: self.updateAvailable.emit(False, None) def check_latest_pypi(self) -> None: try: res = json.loads( urlopen(self.pypi_api_endpoint).read().decode('utf-8')) if 'info' in res.keys(): latest_release = str(res['info']['version']) if LooseVersion(latest_release) > LooseVersion( qApp.applicationVersion()): # self.notify_update(version=latest_release, installer=self.install_update) self.updateAvailable.emit(True, latest_release) return self.updateAvailable.emit(False, None) except HTTPError: self.updateAvailable.emit(False, None) def install_update(self, parent: QWidget) -> None: returncode = self.cmd_exec( 'x-terminal-emulator', '-title "VidCutter Updater" -e "sudo pip3 install ' + '--upgrade vidcutter"') self.confirm_update(parent, returncode) def run(self) -> None: if getattr(sys, 'frozen', False): self.check_latest_github() else: self.check_latest_pypi() @staticmethod def notify_update(parent: QWidget, version: str) -> QMessageBox.ButtonRole: mbox = QMessageBox(parent) mbox.setIconPixmap(qApp.windowIcon().pixmap(64, 64)) mbox.setWindowTitle('%s UPDATER' % qApp.applicationName()) mbox.setText( '<table border="0" width="350"><tr><td><h4 align="center">Your Version: %s <br/> Available Version: %s' % (qApp.applicationVersion(), version) + '</h4></td></tr></table><br/>') mbox.setInformativeText( 'A new version of %s has been detected. Would you like to update now?' % qApp.applicationName()) install_btn = mbox.addButton('Install Update', QMessageBox.AcceptRole) reject_btn = mbox.addButton('Not Now', QMessageBox.RejectRole) mbox.setDefaultButton(install_btn) return mbox.exec_() @staticmethod def notify_no_update(parent: QWidget) -> None: mbox = QMessageBox(parent) mbox.setIconPixmap(QIcon(':/images/thumbsup.png').pixmap(64, 64)) mbox.setWindowTitle('%s UPDATER' % qApp.applicationName()) mbox.setText('<h3 style="color:#6A4572;">%s %s</h3>' % (qApp.applicationName(), qApp.applicationVersion())) mbox.setInformativeText( 'You are already running the latest version.' + '<table width="350"><tr><td></td></tr></table>') mbox.setStandardButtons(QMessageBox.Close) mbox.setDefaultButton(QMessageBox.Close) return mbox.exec_() @staticmethod def notify_restart(parent: QWidget) -> bool: mbox = QMessageBox(parent) mbox.setIconPixmap(qApp.windowIcon().pixmap(64, 64)) mbox.setWindowTitle('%s UPDATER' % qApp.applicationName()) mbox.setText( '<h3 style="color:#6A4572;">INSTALLATION COMPLETE</h3>' + '<table border="0" width="350"><tr><td><p>The application needs to be restarted in order to use ' + 'the newly installed version.</p><p>Would you like to restart now?</td></tr></table><br/>' ) restart_btn = mbox.addButton('Yes', QMessageBox.AcceptRole) restart_btn.clicked.connect(Updater.restart_app) reject_btn = mbox.addButton('No', QMessageBox.RejectRole) mbox.setDefaultButton(restart_btn) return mbox.exec_() @staticmethod def confirm_update(parent: QWidget, update_success: bool) -> None: if update_success and QMessageBox.question( parent, '%s UPDATER' % qApp.applicationName(), '<h3>UPDATE COMPLETE</h3><p>To begin using the newly installed ' + 'version the application needs to be restarted.</p>' + '<p>Would you like to restart now?</p><br/>', buttons=(QMessageBox.Yes | QMessageBox.No)): Updater.restart_app()
class VideoService(QObject): def __init__(self, parent): super(VideoService, self).__init__(parent) self.parent = parent self.consoleOutput = '' self.backend = 'ffmpeg' self.arch = 'x86' if platform.architecture()[0] == '32bit' else 'x64' if sys.platform == 'win32': self.backend = os.path.join(self.getAppPath(), 'bin', self.arch, 'ffmpeg.exe') self.initProc() def initProc(self) -> None: self.proc = QProcess(self.parent) self.proc.setProcessChannelMode(QProcess.MergedChannels) self.proc.setWorkingDirectory(self.getAppPath()) if hasattr(self.proc, 'errorOccurred'): self.proc.errorOccurred.connect(self.cmdError) def capture(self, source: str, frametime: str) -> QPixmap: img, capres = None, QPixmap() try: img = QTemporaryFile(os.path.join(QDir.tempPath(), 'XXXXXX.jpg')) if img.open(): imagecap = img.fileName() args = '-ss %s -i "%s" -vframes 1 -s 100x70 -y %s' % ( frametime, source, imagecap) if self.cmdExec(self.backend, args): capres = QPixmap(imagecap, 'JPG') finally: del img return capres def cut(self, source: str, output: str, frametime: str, duration: str) -> bool: args = '-i "%s" -ss %s -t %s -vcodec copy -acodec copy -y "%s"'\ % (source, frametime, duration, QDir.fromNativeSeparators(output)) return self.cmdExec(self.backend, args) def join(self, filelist: list, output: str) -> bool: args = '-f concat -safe 0 -i "%s" -c copy -y "%s"' % ( filelist, QDir.fromNativeSeparators(output)) return self.cmdExec(self.backend, args) def cmdExec(self, cmd: str, args: str = None) -> bool: if self.proc.state() == QProcess.NotRunning: self.proc.start(cmd, shlex.split(args)) self.proc.waitForFinished(-1) if self.proc.exitStatus( ) == QProcess.NormalExit and self.proc.exitCode() == 0: return True return False @pyqtSlot(QProcess.ProcessError) def cmdError(self, error: QProcess.ProcessError) -> None: if error != QProcess.Crashed: QMessageBox.critical(self.parent.parent, "Error calling an external process", self.proc.errorString(), buttons=QMessageBox.Cancel) def getAppPath(self) -> str: if getattr(sys, 'frozen', False): return sys._MEIPASS return QFileInfo(__file__).absolutePath()
class LoaderLauncher: BIN_PREFIX = '/bin/' EXE_NAME = 'EDIICore' @staticmethod def check_edii_path(path): return os.path.isfile(LoaderLauncher.make_paths(path)[0]) @staticmethod def default_path(): return os.getcwd() + '/EDII' @staticmethod def exe_suffix(): ostype = platform.system() if ostype == 'Windows': return '.exe' else: return '' @staticmethod def _path_tail(): return LoaderLauncher.BIN_PREFIX + LoaderLauncher.EXE_NAME + LoaderLauncher.exe_suffix( ) @staticmethod def make_paths(path): if platform.system() == 'Windows': path = path.lower() tail = LoaderLauncher._path_tail() if platform.system() == 'Windows': tail = tail.lower() if path.endswith(tail): return (path, path[:-len(tail)] + LoaderLauncher.BIN_PREFIX) return (path + tail, path + LoaderLauncher.BIN_PREFIX) def __init__(self): self._ldrProcess = QProcess() def launchIfNeeded(self, loaderpath): self._setup_path(loaderpath) try: if signaltraceloaderfactory.serviceAvailable(): return except signaltraceloader.SignalTraceLoaderError as ex: raise EDIIConnectionError(str(ex)) self._ldrProcess.start() if not self._ldrProcess.waitForStarted(2000): raise EDIIStartError(self._ldrProcess.errorString()) time.sleep(0.25) try: if not signaltraceloaderfactory.serviceAvailable(): raise EDIIConnectionError('EDII service is not available') except signaltraceloader.SignalTraceLoaderError as ex: raise EDIIConnectionError(str(ex)) def _setup_path(self, loaderpath): if isinstance(loaderpath, str) is False: raise EDIIStartError('Invalid path to EDII service') path = '' if not os.path.isabs(loaderpath): path = os.path.abspath(loaderpath) else: path = loaderpath exe_path, wd_path = LoaderLauncher.make_paths(path) self._ldrProcess.setProgram(exe_path) self._ldrProcess.setWorkingDirectory(wd_path) def terminate(self): self._ldrProcess.terminate() self._ldrProcess.waitForFinished(3000)
def start(self, projectDir): """ Public method to populate the status list. @param projectDir name of the project directory @type str """ # find the root of the repo self.__repodir = projectDir while not os.path.isdir(os.path.join(self.__repodir, self.__vcs.adminDir)): self.__repodir = os.path.dirname(self.__repodir) if os.path.splitdrive(self.__repodir)[1] == os.sep: return self.errorGroup.hide() self.errors.clear() self.statusList.clear() self.buttonBox.setEnabled(False) args = self.__vcs.initCommand("submodule") args.append("status") if self.recursiveCheckBox.isChecked(): args.append("--recursive") if self.indexCheckBox.isChecked(): args.append("--cached") process = QProcess() process.setWorkingDirectory(self.__repodir) process.start('git', args) procStarted = process.waitForStarted(5000) if procStarted: finished = process.waitForFinished(30000) if finished and process.exitCode() == 0: ioEncoding = Preferences.getSystem("IOEncoding") output = str(process.readAllStandardOutput(), ioEncoding, 'replace') error = str(process.readAllStandardError(), ioEncoding, 'replace') if error: self.errors.setText(error) self.errorGroup.show() self.__processOutput(output) else: if not finished: self.errors.setText(self.tr( "The process {0} did not finish within 30 seconds.") .format("git")) else: self.errors.setText(self.tr( "The process {0} finished with an error.\n" "Error: {1}") .format("git", process.errorString())) self.errorGroup.show() else: self.errors.setText(self.tr( "The process {0} could not be started. " "Ensure, that it is in the search path.").format("git")) self.errorGroup.show() self.buttonBox.setEnabled(True) self.buttonBox.setFocus()
class runV2raycore(QObject): """ you should emit a signal to start or stop a program. """ start = pyqtSignal() stop = pyqtSignal() def __init__(self, outputTextEdit, v2rayPath="v2ray", v2rayOption="", bridgetreasureChest=False): super().__init__() self.outputTextEdit = outputTextEdit self.v2rayPath = v2rayPath self.v2rayOption = v2rayOption self.bridgetreasureChest = bridgetreasureChest if not self.bridgetreasureChest: from bridgehouse.extension import bridgetreasureChest self.bridgetreasureChest = bridgetreasureChest.bridgetreasureChest( ) self.v2rayProcess = QProcess() self.v2rayProcess.setProcessChannelMode(QProcess.MergedChannels) self.v2rayProcess.setProcessEnvironment( QProcessEnvironment.systemEnvironment()) self.v2rayProcess.readyRead.connect(self.setoutputTextEdit) self.v2rayProcess.started.connect(self.oncreatePIDFile) self.start.connect(self.onstart) self.stop.connect(self.onstop) self.translate = QCoreApplication.translate self.pidFile = ".v2rayPID" def onstart(self): if (self.v2rayProcess.state() == QProcess.NotRunning): self.outputTextEdit.clear() command = self.translate("runV2raycore", "v2ray file path had no seted.") if (self.v2rayPath): checkSpaces = re.search(" ", self.v2rayPath) if checkSpaces: # in fact, you can just keep this line. # do not need check spaces command = '"' + self.v2rayPath + '" ' + self.v2rayOption else: command = "{} {}".format(self.v2rayPath, self.v2rayOption) self.killOrphanProcess() self.v2rayProcess.start(command, QIODevice.ReadWrite) self.outputTextEdit.insertPlainText("{}\n\n".format(command)) if (self.v2rayProcess.state() == QProcess.NotRunning): self.outputTextEdit.moveCursor(QTextCursor.End) self.outputTextEdit.append("\n") self.outputTextEdit.insertPlainText(str( "{}\n".format(command))) self.outputTextEdit.insertPlainText( str( self.translate("runV2raycore", "{} Error Code:{}").format( self.v2rayProcess.errorString(), self.v2rayProcess.error()))) self.outputTextEdit.moveCursor(QTextCursor.End) self.outputTextEdit.textChanged.connect(self.getV2raycoreVersion) def killOrphanProcess(self): openFile = QFileInfo(self.pidFile) fileName = openFile.fileName() if QFile.exists(fileName): openFile = QFile(fileName) else: return v2rayPID = None try: openFile.open(QIODevice.ReadOnly | QIODevice.Text) v2rayPID = str(openFile.readAll(), "utf-8") except Exception: pass try: os.kill(int(v2rayPID), signal.SIGTERM) except Exception: pass def oncreatePIDFile(self): if self.v2rayProcess.state() == QProcess.NotRunning: return outFile = QFileInfo(self.pidFile) fileName = outFile.fileName() if QFile.exists(fileName): QFile.remove(fileName) outFile = QFile(fileName) v2rayPID = str(self.v2rayProcess.processId()) qDebug("process ID is: {}".format(v2rayPID)) try: outFile.open(QIODevice.WriteOnly | QIODevice.Text) outFile.write(codecs.encode(v2rayPID, "utf-8")) except Exception: pass outFile.close() def getV2raycoreVersion(self): text = self.outputTextEdit.toPlainText() version = re.findall("V2Ray v\d\.\d{1,2}", text) failtostart = re.findall("Failed to start App", text) if (version): version = version[0].split(" ")[1] self.bridgetreasureChest.setV2raycoreVersion(version) if (failtostart): self.outputTextEdit.textChanged.disconnect( self.getV2raycoreVersion) self.onstop() def onstop(self): if (self.v2rayProcess.state() == QProcess.Running): self.v2rayProcess.close() self.v2rayProcess.kill() self.outputTextEdit.moveCursor(QTextCursor.End) self.outputTextEdit.append("\n\n") self.outputTextEdit.insertPlainText( str( self.translate("runV2raycore", "{} is stop now...").format( self.v2rayPath))) self.outputTextEdit.insertPlainText( str( self.translate("runV2raycore", "\n{} is ready to run...").format( self.v2rayPath))) self.outputTextEdit.moveCursor(QTextCursor.End) def setoutputTextEdit(self): self.outputTextEdit.moveCursor(QTextCursor.End) self.outputTextEdit.insertPlainText( str(self.v2rayProcess.readAllStandardOutput(), "utf-8")) self.outputTextEdit.insertPlainText( str(self.v2rayProcess.readAllStandardError(), "utf-8")) self.outputTextEdit.moveCursor(QTextCursor.End)