CondaInterface/CondaExecDialog.py

branch
maintenance
changeset 6826
c6dda2cbe081
parent 6741
33a82a20dd3a
equal deleted inserted replaced
6764:d14ddbfbbd36 6826:c6dda2cbe081
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to show the output of a conda execution.
8 """
9
10 from __future__ import unicode_literals
11 try:
12 str = unicode
13 except NameError:
14 pass
15
16 import json
17
18 from PyQt5.QtCore import pyqtSlot, QProcess, QTimer
19 from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton
20
21 from E5Gui import E5MessageBox
22
23 from .Ui_CondaExecDialog import Ui_CondaExecDialog
24
25 import Preferences
26 import Globals
27
28
29 class CondaExecDialog(QDialog, Ui_CondaExecDialog):
30 """
31 Class implementing a dialog to show the output of a conda execution.
32 """
33 def __init__(self, command, parent=None):
34 """
35 Constructor
36
37 @param command conda command executed
38 @type str
39 @param parent reference to the parent widget
40 @type QWidget
41 """
42 super(CondaExecDialog, self).__init__(parent)
43 self.setupUi(self)
44
45 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
46 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
47
48 self.__condaCommand = command
49
50 self.__process = None
51 self.__condaExe = Preferences.getConda("CondaExecutable")
52 if not self.__condaExe:
53 self.__condaExe = "conda"
54
55 @pyqtSlot(QAbstractButton)
56 def on_buttonBox_clicked(self, button):
57 """
58 Private slot called by a button of the button box clicked.
59
60 @param button button that was clicked
61 @type QAbstractButton
62 """
63 if button == self.buttonBox.button(QDialogButtonBox.Close):
64 self.accept()
65 elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
66 self.__finish(1, 0)
67
68 def start(self, arguments):
69 """
70 Public slot to start the conda command.
71
72 @param arguments commandline arguments for conda program
73 @type list of str
74 """
75 self.errorGroup.hide()
76 self.progressLabel.hide()
77 self.progressBar.hide()
78
79 self.contents.clear()
80 self.errors.clear()
81 self.progressLabel.clear()
82 self.progressBar.setValue(0)
83
84 self.__bufferedStdout = None
85 self.__json = "--json" in arguments
86 self.__firstProgress = True
87 self.__lastFetchFile = ""
88
89 self.__statusOk = False
90 self.__result = None
91
92 self.__logOutput(self.__condaExe + " " + " ".join(arguments) + "\n\n")
93
94 self.__process = QProcess()
95 self.__process.readyReadStandardOutput.connect(self.__readStdout)
96 self.__process.readyReadStandardError.connect(self.__readStderr)
97 self.__process.finished.connect(self.__finish)
98
99 self.__process.start(self.__condaExe, arguments)
100 procStarted = self.__process.waitForStarted(5000)
101 if not procStarted:
102 E5MessageBox.critical(
103 self,
104 self.tr("Conda Execution"),
105 self.tr("""The conda executable could not be started. Is it"""
106 """ configured correctly?"""))
107 self.__finish(1, 0)
108 else:
109 self.__logOutput(self.tr("Operation started.\n"))
110
111 def __finish(self, exitCode, exitStatus, giveUp=False):
112 """
113 Private slot called when the process finished.
114
115 It is called when the process finished or
116 the user pressed the button.
117
118 @param exitCode exit code of the process
119 @type int
120 @param exitStatus exit status of the process
121 @type QProcess.ExitStatus
122 @param giveUp flag indicating to not start another attempt
123 @type bool
124 """
125 if self.__process is not None and \
126 self.__process.state() != QProcess.NotRunning:
127 self.__process.terminate()
128 QTimer.singleShot(2000, self.__process.kill)
129 self.__process.waitForFinished(3000)
130
131 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
132 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
133 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
134
135 self.progressLabel.hide()
136 self.progressBar.hide()
137
138 self.__statusOk = exitCode == 0
139
140 self.__logOutput(self.tr("Operation finished.\n"))
141 if not self.__json and self.__bufferedStdout:
142 self.__logOutput(self.__bufferedStdout)
143
144 if self.__json and self.__bufferedStdout:
145 index = self.__bufferedStdout.find("{")
146 rindex = self.__bufferedStdout.rfind("}")
147 self.__bufferedStdout = self.__bufferedStdout[index:rindex + 1]
148 try:
149 self.__result = json.loads(self.__bufferedStdout)
150 except Exception as error:
151 self.__result = {}
152 self.__logError(str(error))
153 return
154
155 if "error" in self.__result:
156 self.__logError(self.__result["error"])
157 self.__statusOk = False
158 elif "success" in self.__result and \
159 not self.__result["success"]:
160 self.__logError(
161 self.tr("Conda command '{0}' did not return success.")
162 .format(self.__condaCommand))
163 if "message" in self.__result:
164 self.__logError("\n")
165 self.__logError(
166 self.tr("\nConda Message: {0}").format(
167 self.__result["message"]))
168 self.__statusOk = False
169 elif "message" in self.__result:
170 self.__logOutput(
171 self.tr("\nConda Message: {0}").format(
172 self.__result["message"]))
173
174 def getResult(self):
175 """
176 Public method to the result of the command execution.
177
178 @return tuple containing a flag indicating success and the result data.
179 @rtype tuple of (bool, dict)
180 """
181 return self.__statusOk, self.__result
182
183 def __setProgressValues(self, jsonDict, progressType):
184 """
185 Private method to set the value of the progress bar.
186
187 @param jsonDict dictionary containing the progress info
188 @type dict
189 @param progressType action type to check for
190 @type str
191 @return flag indicating success
192 @rtype bool
193 """
194 if progressType in jsonDict and "progress" in jsonDict:
195 if jsonDict["maxval"] == 1:
196 self.progressBar.setMaximum(100)
197 # percent values
198 self.progressBar.setValue(
199 int(jsonDict["progress"] * 100))
200 parts = jsonDict["fetch"].split("|")
201 filename = parts[0].strip()
202 filesize = parts[1].strip()
203 else:
204 self.progressBar.setMaximum(jsonDict["maxval"])
205 self.progressBar.setValue(jsonDict["progress"])
206 filename = jsonDict["fetch"].strip()
207 filesize = Globals.dataString(int(jsonDict["maxval"]))
208
209 self.progressLabel.setText(
210 self.tr("{0} (Size: {1})").format(filename, filesize))
211
212 if progressType == "fetch":
213 if filename != self.__lastFetchFile:
214 self.__logOutput(
215 self.tr("Fetching {0} ...").format(filename))
216 self.__lastFetchFile = filename
217 elif jsonDict["finished"]:
218 self.__logOutput(self.tr(" Done.\n"))
219
220 if self.__firstProgress:
221 self.progressLabel.show()
222 self.progressBar.show()
223 self.__firstProgress = False
224
225 return True
226
227 return False
228
229 def __readStdout(self):
230 """
231 Private slot to handle the readyReadStandardOutput signal.
232
233 It reads the output of the process, formats it and inserts it into
234 the contents pane.
235 """
236 all_stdout = str(self.__process.readAllStandardOutput(),
237 Preferences.getSystem("IOEncoding"),
238 'replace')
239 all_stdout = all_stdout.replace("\x00", "")
240 if self.__json:
241 for stdout in all_stdout.splitlines():
242 try:
243 jsonDict = json.loads(stdout.replace("\x00", "").strip())
244 if self.__setProgressValues(jsonDict, "fetch"):
245 # nothing to do anymore
246 pass
247 elif "progress" not in jsonDict:
248 if self.__bufferedStdout is None:
249 self.__bufferedStdout = stdout
250 else:
251 self.__bufferedStdout += stdout
252 except (TypeError, ValueError):
253 if self.__bufferedStdout is None:
254 self.__bufferedStdout = stdout
255 else:
256 self.__bufferedStdout += stdout
257 else:
258 self.__logOutput(all_stdout)
259
260 def __readStderr(self):
261 """
262 Private slot to handle the readyReadStandardError signal.
263
264 It reads the error output of the process and inserts it into the
265 error pane.
266 """
267 self.__process.setReadChannel(QProcess.StandardError)
268
269 while self.__process.canReadLine():
270 stderr = str(self.__process.readLine(),
271 Preferences.getSystem("IOEncoding"),
272 'replace')
273 self.__logError(stderr)
274
275 def __logOutput(self, stdout):
276 """
277 Private method to log some output.
278
279 @param stdout output string to log
280 @type str
281 """
282 self.contents.insertPlainText(stdout)
283 self.contents.ensureCursorVisible()
284
285 def __logError(self, stderr):
286 """
287 Private method to log an error.
288
289 @param stderr error string to log
290 @type str
291 """
292 self.errorGroup.show()
293 self.errors.insertPlainText(stderr)
294 self.errors.ensureCursorVisible()

eric ide

mercurial