|
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 |
|
27 |
|
28 class CondaExecDialog(QDialog, Ui_CondaExecDialog): |
|
29 """ |
|
30 Class documentation goes here. |
|
31 """ |
|
32 def __init__(self, configuration, venvManager, parent=None): |
|
33 """ |
|
34 Constructor |
|
35 |
|
36 @param parent reference to the parent widget |
|
37 @type QWidget |
|
38 """ |
|
39 super(CondaExecDialog, self).__init__(parent) |
|
40 self.setupUi(self) |
|
41 |
|
42 self.__venvName = configuration["logicalName"] |
|
43 self.__venvManager = venvManager |
|
44 |
|
45 self.__process = None |
|
46 self.__condaExe = Preferences.getConda("CondaExecutable") |
|
47 if not self.__condaExe: |
|
48 self.__condaExe = "conda" |
|
49 |
|
50 @pyqtSlot(QAbstractButton) |
|
51 def on_buttonBox_clicked(self, button): |
|
52 """ |
|
53 Private slot called by a button of the button box clicked. |
|
54 |
|
55 @param button button that was clicked |
|
56 @type QAbstractButton |
|
57 """ |
|
58 if button == self.buttonBox.button(QDialogButtonBox.Close): |
|
59 self.accept() |
|
60 elif button == self.buttonBox.button(QDialogButtonBox.Cancel): |
|
61 self.__finish() |
|
62 |
|
63 def start(self, arguments): |
|
64 """ |
|
65 Public slot to start the conda command. |
|
66 |
|
67 @param arguments commandline arguments for conda program |
|
68 @type list of str |
|
69 """ |
|
70 self.errorGroup.hide() |
|
71 self.progressLabel.hide() |
|
72 self.progressBar.hide() |
|
73 |
|
74 self.contents.clear() |
|
75 self.errors.clear() |
|
76 self.progressLabel.clear() |
|
77 self.progressBar.setValue(0) |
|
78 |
|
79 self.__bufferedStdout = None |
|
80 self.__json = "--json" in arguments |
|
81 self.__firstProgress = True |
|
82 |
|
83 self.__process = QProcess() |
|
84 self.__process.readyReadStandardOutput.connect(self.__readStdout) |
|
85 self.__process.readyReadStandardError.connect(self.__readStderr) |
|
86 self.__process.finished.connect(self.__finish) |
|
87 |
|
88 self.__process.start(self.__condaExe, arguments) |
|
89 procStarted = self.__process.waitForStarted(5000) |
|
90 if not procStarted: |
|
91 E5MessageBox.critical( |
|
92 self, |
|
93 self.tr("Conda Execution"), |
|
94 self.tr("""The conda executable could not be started. Is it""" |
|
95 """ configured correctly?""")) |
|
96 self.__finish(1, 0) |
|
97 else: |
|
98 self.__logOutput(self.tr("Operation started.\n")) |
|
99 |
|
100 def __finish(self, exitCode, exitStatus, giveUp=False): |
|
101 """ |
|
102 Private slot called when the process finished. |
|
103 |
|
104 It is called when the process finished or |
|
105 the user pressed the button. |
|
106 |
|
107 @param exitCode exit code of the process (integer) |
|
108 @param exitStatus exit status of the process (QProcess.ExitStatus) |
|
109 @keyparam giveUp flag indicating to not start another attempt (boolean) |
|
110 """ |
|
111 if self.__process is not None and \ |
|
112 self.__process.state() != QProcess.NotRunning: |
|
113 self.__process.terminate() |
|
114 QTimer.singleShot(2000, self.__process.kill) |
|
115 self.__process.waitForFinished(3000) |
|
116 |
|
117 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) |
|
118 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) |
|
119 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) |
|
120 |
|
121 self.__logOutput(self.tr("Operation finished.\n")) |
|
122 if self.__json: |
|
123 if self.__bufferedStdout: |
|
124 try: |
|
125 jsonDict = json.loads(self.__bufferedStdout) |
|
126 except Exception as error: |
|
127 self.__logError(str(error)) |
|
128 return |
|
129 |
|
130 if "success" in jsonDict and jsonDict["success"]: |
|
131 if "prefix" in jsonDict: |
|
132 prefix = jsonDict["prefix"] |
|
133 elif "dst_prefix" in jsonDict: |
|
134 prefix = jsonDict["dst_prefix"] |
|
135 else: |
|
136 prefix = "" |
|
137 self.__venvManager.addVirtualEnv(self.__venvName, |
|
138 prefix, |
|
139 isConda=True) |
|
140 |
|
141 def __progressLabelString(self, text): |
|
142 """ |
|
143 Private method to process a string and format it for the progress |
|
144 label. |
|
145 |
|
146 @param text text to be processed |
|
147 @type str |
|
148 @return formatted progress label string |
|
149 @rtype str |
|
150 """ |
|
151 parts = text.split("|") |
|
152 return self.tr("{0} (Size: {1})".format(parts[0].strip(), |
|
153 parts[1].strip())) |
|
154 |
|
155 def __readStdout(self): |
|
156 """ |
|
157 Private slot to handle the readyReadStandardOutput signal. |
|
158 |
|
159 It reads the output of the process, formats it and inserts it into |
|
160 the contents pane. |
|
161 """ |
|
162 all_stdout = str(self.__process.readAllStandardOutput(), |
|
163 Preferences.getSystem("IOEncoding"), |
|
164 'replace') |
|
165 all_stdout = all_stdout.replace("\x00", "") |
|
166 if self.__json: |
|
167 for stdout in all_stdout.splitlines(): |
|
168 try: |
|
169 jsonDict = json.loads(stdout.replace("\x00", "").strip()) |
|
170 if "progress" in jsonDict: |
|
171 self.progressLabel.setText( |
|
172 self.__progressLabelString(jsonDict["fetch"])) |
|
173 self.progressBar.setValue( |
|
174 int(jsonDict["progress"] * 100)) |
|
175 if self.__firstProgress: |
|
176 self.progressLabel.show() |
|
177 self.progressBar.show() |
|
178 self.__firstProgress = False |
|
179 else: |
|
180 if self.__bufferedStdout is None: |
|
181 self.__bufferedStdout = stdout |
|
182 else: |
|
183 self.__bufferedStdout += stdout |
|
184 except (TypeError, ValueError): |
|
185 if self.__bufferedStdout is None: |
|
186 self.__bufferedStdout = stdout |
|
187 else: |
|
188 self.__bufferedStdout += stdout |
|
189 else: |
|
190 self.__logOutput(all_stdout) |
|
191 |
|
192 def __readStderr(self): |
|
193 """ |
|
194 Private slot to handle the readyReadStandardError signal. |
|
195 |
|
196 It reads the error output of the process and inserts it into the |
|
197 error pane. |
|
198 """ |
|
199 self.__process.setReadChannel(QProcess.StandardError) |
|
200 |
|
201 while self.__process.canReadLine(): |
|
202 stderr = str(self.__process.readLine(), |
|
203 Preferences.getSystem("IOEncoding"), |
|
204 'replace') |
|
205 self.__logError(stderr) |
|
206 |
|
207 def __logOutput(self, stdout): |
|
208 """ |
|
209 Private method to log some output. |
|
210 |
|
211 @param stdout output string to log |
|
212 @type str |
|
213 """ |
|
214 self.contents.insertPlainText(stdout) |
|
215 self.contents.ensureCursorVisible() |
|
216 |
|
217 def __logError(self, stderr): |
|
218 """ |
|
219 Private method to log an error. |
|
220 |
|
221 @param stderr error string to log |
|
222 @type str |
|
223 """ |
|
224 self.errorGroup.show() |
|
225 self.errors.insertPlainText(stderr) |
|
226 self.errors.ensureCursorVisible() |