CondaInterface/CondaExecDialog.py

branch
maintenance
changeset 6826
c6dda2cbe081
parent 6741
33a82a20dd3a
diff -r d14ddbfbbd36 -r c6dda2cbe081 CondaInterface/CondaExecDialog.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CondaInterface/CondaExecDialog.py	Sat Mar 02 11:15:24 2019 +0100
@@ -0,0 +1,294 @@
+# -*- 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
+import Globals
+
+
+class CondaExecDialog(QDialog, Ui_CondaExecDialog):
+    """
+    Class implementing a dialog to show the output of a conda execution.
+    """
+    def __init__(self, command, parent=None):
+        """
+        Constructor
+        
+        @param command conda command executed
+        @type str
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(CondaExecDialog, self).__init__(parent)
+        self.setupUi(self)
+        
+        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
+        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
+        
+        self.__condaCommand = command
+        
+        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(1, 0)
+    
+    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.__lastFetchFile = ""
+        
+        self.__statusOk = False
+        self.__result = None
+        
+        self.__logOutput(self.__condaExe + " " + " ".join(arguments) + "\n\n")
+        
+        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
+        @type int
+        @param exitStatus exit status of the process
+        @type QProcess.ExitStatus
+        @param giveUp flag indicating to not start another attempt
+        @type bool
+        """
+        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.progressLabel.hide()
+        self.progressBar.hide()
+        
+        self.__statusOk = exitCode == 0
+        
+        self.__logOutput(self.tr("Operation finished.\n"))
+        if not self.__json and self.__bufferedStdout:
+            self.__logOutput(self.__bufferedStdout)
+        
+        if self.__json and self.__bufferedStdout:
+            index = self.__bufferedStdout.find("{")
+            rindex = self.__bufferedStdout.rfind("}")
+            self.__bufferedStdout = self.__bufferedStdout[index:rindex + 1]
+            try:
+                self.__result = json.loads(self.__bufferedStdout)
+            except Exception as error:
+                self.__result = {}
+                self.__logError(str(error))
+                return
+            
+            if "error" in self.__result:
+                self.__logError(self.__result["error"])
+                self.__statusOk = False
+            elif "success" in self.__result and \
+                    not self.__result["success"]:
+                self.__logError(
+                    self.tr("Conda command '{0}' did not return success.")
+                    .format(self.__condaCommand))
+                if "message" in self.__result:
+                    self.__logError("\n")
+                    self.__logError(
+                        self.tr("\nConda Message: {0}").format(
+                            self.__result["message"]))
+                self.__statusOk = False
+            elif "message" in self.__result:
+                self.__logOutput(
+                    self.tr("\nConda Message: {0}").format(
+                        self.__result["message"]))
+    
+    def getResult(self):
+        """
+        Public method to the result of the command execution.
+        
+        @return tuple containing a flag indicating success and the result data.
+        @rtype tuple of (bool, dict)
+        """
+        return self.__statusOk, self.__result
+    
+    def __setProgressValues(self, jsonDict, progressType):
+        """
+        Private method to set the value of the progress bar.
+        
+        @param jsonDict dictionary containing the progress info
+        @type dict
+        @param progressType action type to check for
+        @type str
+        @return flag indicating success
+        @rtype bool
+        """
+        if progressType in jsonDict and "progress" in jsonDict:
+            if jsonDict["maxval"] == 1:
+                self.progressBar.setMaximum(100)
+                # percent values
+                self.progressBar.setValue(
+                    int(jsonDict["progress"] * 100))
+                parts = jsonDict["fetch"].split("|")
+                filename = parts[0].strip()
+                filesize = parts[1].strip()
+            else:
+                self.progressBar.setMaximum(jsonDict["maxval"])
+                self.progressBar.setValue(jsonDict["progress"])
+                filename = jsonDict["fetch"].strip()
+                filesize = Globals.dataString(int(jsonDict["maxval"]))
+            
+            self.progressLabel.setText(
+                self.tr("{0} (Size: {1})").format(filename, filesize))
+            
+            if progressType == "fetch":
+                if filename != self.__lastFetchFile:
+                    self.__logOutput(
+                        self.tr("Fetching {0} ...").format(filename))
+                    self.__lastFetchFile = filename
+                elif jsonDict["finished"]:
+                    self.__logOutput(self.tr(" Done.\n"))
+            
+            if self.__firstProgress:
+                self.progressLabel.show()
+                self.progressBar.show()
+                self.__firstProgress = False
+            
+            return True
+        
+        return False
+    
+    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 self.__setProgressValues(jsonDict, "fetch"):
+                        # nothing to do anymore
+                        pass
+                    elif "progress" not in jsonDict:
+                        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()

eric ide

mercurial