--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/CondaExecDialog.py Sun Jan 27 19:52:37 2019 +0100 @@ -0,0 +1,226 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to show the output of a conda execution. +""" + +from __future__ import unicode_literals +try: + str = unicode +except NameError: + pass + +import json + +from PyQt5.QtCore import pyqtSlot, QProcess, QTimer +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton + +from E5Gui import E5MessageBox + +from .Ui_CondaExecDialog import Ui_CondaExecDialog + +import Preferences + + +class CondaExecDialog(QDialog, Ui_CondaExecDialog): + """ + Class documentation goes here. + """ + def __init__(self, configuration, venvManager, parent=None): + """ + Constructor + + @param parent reference to the parent widget + @type QWidget + """ + super(CondaExecDialog, self).__init__(parent) + self.setupUi(self) + + self.__venvName = configuration["logicalName"] + self.__venvManager = venvManager + + self.__process = None + self.__condaExe = Preferences.getConda("CondaExecutable") + if not self.__condaExe: + self.__condaExe = "conda" + + @pyqtSlot(QAbstractButton) + def on_buttonBox_clicked(self, button): + """ + Private slot called by a button of the button box clicked. + + @param button button that was clicked + @type QAbstractButton + """ + if button == self.buttonBox.button(QDialogButtonBox.Close): + self.accept() + elif button == self.buttonBox.button(QDialogButtonBox.Cancel): + self.__finish() + + def start(self, arguments): + """ + Public slot to start the conda command. + + @param arguments commandline arguments for conda program + @type list of str + """ + self.errorGroup.hide() + self.progressLabel.hide() + self.progressBar.hide() + + self.contents.clear() + self.errors.clear() + self.progressLabel.clear() + self.progressBar.setValue(0) + + self.__bufferedStdout = None + self.__json = "--json" in arguments + self.__firstProgress = True + + self.__process = QProcess() + self.__process.readyReadStandardOutput.connect(self.__readStdout) + self.__process.readyReadStandardError.connect(self.__readStderr) + self.__process.finished.connect(self.__finish) + + self.__process.start(self.__condaExe, arguments) + procStarted = self.__process.waitForStarted(5000) + if not procStarted: + E5MessageBox.critical( + self, + self.tr("Conda Execution"), + self.tr("""The conda executable could not be started. Is it""" + """ configured correctly?""")) + self.__finish(1, 0) + else: + self.__logOutput(self.tr("Operation started.\n")) + + def __finish(self, exitCode, exitStatus, giveUp=False): + """ + Private slot called when the process finished. + + It is called when the process finished or + the user pressed the button. + + @param exitCode exit code of the process (integer) + @param exitStatus exit status of the process (QProcess.ExitStatus) + @keyparam giveUp flag indicating to not start another attempt (boolean) + """ + if self.__process is not None and \ + self.__process.state() != QProcess.NotRunning: + self.__process.terminate() + QTimer.singleShot(2000, self.__process.kill) + self.__process.waitForFinished(3000) + + self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) + self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) + + self.__logOutput(self.tr("Operation finished.\n")) + if self.__json: + if self.__bufferedStdout: + try: + jsonDict = json.loads(self.__bufferedStdout) + except Exception as error: + self.__logError(str(error)) + return + + if "success" in jsonDict and jsonDict["success"]: + if "prefix" in jsonDict: + prefix = jsonDict["prefix"] + elif "dst_prefix" in jsonDict: + prefix = jsonDict["dst_prefix"] + else: + prefix = "" + self.__venvManager.addVirtualEnv(self.__venvName, + prefix, + isConda=True) + + def __progressLabelString(self, text): + """ + Private method to process a string and format it for the progress + label. + + @param text text to be processed + @type str + @return formatted progress label string + @rtype str + """ + parts = text.split("|") + return self.tr("{0} (Size: {1})".format(parts[0].strip(), + parts[1].strip())) + + def __readStdout(self): + """ + Private slot to handle the readyReadStandardOutput signal. + + It reads the output of the process, formats it and inserts it into + the contents pane. + """ + all_stdout = str(self.__process.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace') + all_stdout = all_stdout.replace("\x00", "") + if self.__json: + for stdout in all_stdout.splitlines(): + try: + jsonDict = json.loads(stdout.replace("\x00", "").strip()) + if "progress" in jsonDict: + self.progressLabel.setText( + self.__progressLabelString(jsonDict["fetch"])) + self.progressBar.setValue( + int(jsonDict["progress"] * 100)) + if self.__firstProgress: + self.progressLabel.show() + self.progressBar.show() + self.__firstProgress = False + else: + if self.__bufferedStdout is None: + self.__bufferedStdout = stdout + else: + self.__bufferedStdout += stdout + except (TypeError, ValueError): + if self.__bufferedStdout is None: + self.__bufferedStdout = stdout + else: + self.__bufferedStdout += stdout + else: + self.__logOutput(all_stdout) + + def __readStderr(self): + """ + Private slot to handle the readyReadStandardError signal. + + It reads the error output of the process and inserts it into the + error pane. + """ + self.__process.setReadChannel(QProcess.StandardError) + + while self.__process.canReadLine(): + stderr = str(self.__process.readLine(), + Preferences.getSystem("IOEncoding"), + 'replace') + self.__logError(stderr) + + def __logOutput(self, stdout): + """ + Private method to log some output. + + @param stdout output string to log + @type str + """ + self.contents.insertPlainText(stdout) + self.contents.ensureCursorVisible() + + def __logError(self, stderr): + """ + Private method to log an error. + + @param stderr error string to log + @type str + """ + self.errorGroup.show() + self.errors.insertPlainText(stderr) + self.errors.ensureCursorVisible()