src/eric7/CondaInterface/CondaExecDialog.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2019 - 2022 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 import json
11
12 from PyQt6.QtCore import pyqtSlot, QProcess, QTimer
13 from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton
14
15 from EricWidgets import EricMessageBox
16
17 from .Ui_CondaExecDialog import Ui_CondaExecDialog
18
19 import Preferences
20 import Globals
21
22
23 class CondaExecDialog(QDialog, Ui_CondaExecDialog):
24 """
25 Class implementing a dialog to show the output of a conda execution.
26 """
27 def __init__(self, command, parent=None):
28 """
29 Constructor
30
31 @param command conda command executed
32 @type str
33 @param parent reference to the parent widget
34 @type QWidget
35 """
36 super().__init__(parent)
37 self.setupUi(self)
38
39 self.buttonBox.button(
40 QDialogButtonBox.StandardButton.Close).setEnabled(False)
41 self.buttonBox.button(
42 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
43
44 self.__condaCommand = command
45
46 self.__process = None
47 self.__condaExe = Preferences.getConda("CondaExecutable")
48 if not self.__condaExe:
49 self.__condaExe = "conda"
50
51 @pyqtSlot(QAbstractButton)
52 def on_buttonBox_clicked(self, button):
53 """
54 Private slot called by a button of the button box clicked.
55
56 @param button button that was clicked
57 @type QAbstractButton
58 """
59 if button == self.buttonBox.button(
60 QDialogButtonBox.StandardButton.Close
61 ):
62 self.accept()
63 elif button == self.buttonBox.button(
64 QDialogButtonBox.StandardButton.Cancel
65 ):
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 EricMessageBox.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.ProcessState.NotRunning):
127 self.__process.terminate()
128 QTimer.singleShot(2000, self.__process.kill)
129 self.__process.waitForFinished(3000)
130
131 self.buttonBox.button(
132 QDialogButtonBox.StandardButton.Close).setEnabled(True)
133 self.buttonBox.button(
134 QDialogButtonBox.StandardButton.Cancel).setEnabled(False)
135 self.buttonBox.button(
136 QDialogButtonBox.StandardButton.Close).setDefault(True)
137
138 self.progressLabel.hide()
139 self.progressBar.hide()
140
141 self.__statusOk = exitCode == 0
142
143 self.__logOutput(self.tr("Operation finished.\n"))
144 if not self.__json and self.__bufferedStdout:
145 self.__logOutput(self.__bufferedStdout)
146
147 if self.__json and self.__bufferedStdout:
148 index = self.__bufferedStdout.find("{")
149 rindex = self.__bufferedStdout.rfind("}")
150 self.__bufferedStdout = self.__bufferedStdout[index:rindex + 1]
151 try:
152 self.__result = json.loads(self.__bufferedStdout)
153 except Exception as error:
154 self.__result = {}
155 self.__logError(str(error))
156 return
157
158 if "error" in self.__result:
159 self.__logError(self.__result["error"])
160 self.__statusOk = False
161 elif ("success" in self.__result and
162 not self.__result["success"]):
163 self.__logError(
164 self.tr("Conda command '{0}' did not return success.")
165 .format(self.__condaCommand))
166 if "message" in self.__result:
167 self.__logError("\n")
168 self.__logError(
169 self.tr("\nConda Message: {0}").format(
170 self.__result["message"]))
171 self.__statusOk = False
172 elif "message" in self.__result:
173 self.__logOutput(
174 self.tr("\nConda Message: {0}").format(
175 self.__result["message"]))
176
177 def getResult(self):
178 """
179 Public method to the result of the command execution.
180
181 @return tuple containing a flag indicating success and the result data.
182 @rtype tuple of (bool, dict)
183 """
184 return self.__statusOk, self.__result
185
186 def __setProgressValues(self, jsonDict, progressType):
187 """
188 Private method to set the value of the progress bar.
189
190 @param jsonDict dictionary containing the progress info
191 @type dict
192 @param progressType action type to check for
193 @type str
194 @return flag indicating success
195 @rtype bool
196 """
197 if progressType in jsonDict and "progress" in jsonDict:
198 if jsonDict["maxval"] == 1:
199 self.progressBar.setMaximum(100)
200 # percent values
201 self.progressBar.setValue(
202 int(jsonDict["progress"] * 100))
203 parts = jsonDict["fetch"].split("|")
204 filename = parts[0].strip()
205 filesize = parts[1].strip()
206 else:
207 self.progressBar.setMaximum(jsonDict["maxval"])
208 self.progressBar.setValue(jsonDict["progress"])
209 filename = jsonDict["fetch"].strip()
210 filesize = Globals.dataString(int(jsonDict["maxval"]))
211
212 self.progressLabel.setText(
213 self.tr("{0} (Size: {1})").format(filename, filesize))
214
215 if progressType == "fetch":
216 if filename != self.__lastFetchFile:
217 self.__logOutput(
218 self.tr("Fetching {0} ...").format(filename))
219 self.__lastFetchFile = filename
220 elif jsonDict["finished"]:
221 self.__logOutput(self.tr(" Done.\n"))
222
223 if self.__firstProgress:
224 self.progressLabel.show()
225 self.progressBar.show()
226 self.__firstProgress = False
227
228 return True
229
230 return False
231
232 def __readStdout(self):
233 """
234 Private slot to handle the readyReadStandardOutput signal.
235
236 It reads the output of the process, formats it and inserts it into
237 the contents pane.
238 """
239 all_stdout = str(self.__process.readAllStandardOutput(),
240 Preferences.getSystem("IOEncoding"),
241 'replace')
242 all_stdout = all_stdout.replace("\x00", "")
243 if self.__json:
244 for stdout in all_stdout.splitlines():
245 try:
246 jsonDict = json.loads(stdout.replace("\x00", "").strip())
247 if self.__setProgressValues(jsonDict, "fetch"):
248 # nothing to do anymore
249 pass
250 elif "progress" not in jsonDict:
251 if self.__bufferedStdout is None:
252 self.__bufferedStdout = stdout
253 else:
254 self.__bufferedStdout += stdout
255 except (TypeError, ValueError):
256 if self.__bufferedStdout is None:
257 self.__bufferedStdout = stdout
258 else:
259 self.__bufferedStdout += stdout
260 else:
261 self.__logOutput(all_stdout)
262
263 def __readStderr(self):
264 """
265 Private slot to handle the readyReadStandardError signal.
266
267 It reads the error output of the process and inserts it into the
268 error pane.
269 """
270 self.__process.setReadChannel(QProcess.ProcessChannel.StandardError)
271
272 while self.__process.canReadLine():
273 stderr = str(self.__process.readLine(),
274 Preferences.getSystem("IOEncoding"),
275 'replace')
276 self.__logError(stderr)
277
278 def __logOutput(self, stdout):
279 """
280 Private method to log some output.
281
282 @param stdout output string to log
283 @type str
284 """
285 self.contents.insertPlainText(stdout)
286 self.contents.ensureCursorVisible()
287
288 def __logError(self, stderr):
289 """
290 Private method to log an error.
291
292 @param stderr error string to log
293 @type str
294 """
295 self.errorGroup.show()
296 self.errors.insertPlainText(stderr)
297 self.errors.ensureCursorVisible()

eric ide

mercurial