comparison hgrunner.cpp @ 113:5fc7b4fc77a8

* Better error handling/reporting; some futile changes to termios handling; avoid weirdly stretching panned view in panner
author Chris Cannam
date Fri, 26 Nov 2010 21:04:40 +0000
parents 151209bc5bd6
children bb2d2eecdd60
comparison
equal deleted inserted replaced
112:4bd17f36d059 113:5fc7b4fc77a8
32 #include <stdio.h> 32 #include <stdio.h>
33 #include <stdlib.h> 33 #include <stdlib.h>
34 34
35 #ifndef Q_OS_WIN32 35 #ifndef Q_OS_WIN32
36 #include <unistd.h> 36 #include <unistd.h>
37 #include <termios.h>
37 #include <fcntl.h> 38 #include <fcntl.h>
38 #endif 39 #endif
39 40
40 HgRunner::HgRunner(QWidget * parent): QProgressBar(parent) 41 HgRunner::HgRunner(QWidget * parent): QProgressBar(parent)
41 { 42 {
42 m_proc = new QProcess(this); 43 m_proc = 0;
43
44 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
45 env.insert("LANG", "en_US.utf8");
46 env.insert("LC_ALL", "en_US.utf8");
47 env.insert("HGPLAIN", "1");
48 m_proc->setProcessEnvironment(env);
49 44
50 setTextVisible(false); 45 setTextVisible(false);
51 setVisible(false); 46 setVisible(false);
52 m_isRunning = false; 47 m_isRunning = false;
53
54 connect(m_proc, SIGNAL(started()), this, SLOT(started()));
55 connect(m_proc, SIGNAL(finished(int, QProcess::ExitStatus)),
56 this, SLOT(finished(int, QProcess::ExitStatus)));
57 connect(m_proc, SIGNAL(readyReadStandardOutput()),
58 this, SLOT(dataReadyStdout()));
59 connect(m_proc, SIGNAL(readyReadStandardError()),
60 this, SLOT(dataReadyStderr()));
61 } 48 }
62 49
63 HgRunner::~HgRunner() 50 HgRunner::~HgRunner()
64 { 51 {
65 closeTerminal(); 52 closeTerminal();
122 m_realm = realm; 109 m_realm = realm;
123 } 110 }
124 111
125 void HgRunner::getUsername() 112 void HgRunner::getUsername()
126 { 113 {
127 if (m_procInput) { 114 if (m_ptyFile) {
128 bool ok = false; 115 bool ok = false;
129 QString prompt = tr("User name:"); 116 QString prompt = tr("User name:");
130 if (m_realm != "") { 117 if (m_realm != "") {
131 prompt = tr("User name for \"%1\":").arg(m_realm); 118 prompt = tr("User name for \"%1\":").arg(m_realm);
132 } 119 }
133 QString pwd = QInputDialog::getText 120 QString pwd = QInputDialog::getText
134 (qobject_cast<QWidget *>(parent()), 121 (qobject_cast<QWidget *>(parent()),
135 tr("Enter user name"), prompt, 122 tr("Enter user name"), prompt,
136 QLineEdit::Normal, QString(), &ok); 123 QLineEdit::Normal, QString(), &ok);
137 if (ok) { 124 if (ok) {
138 m_procInput->write(QString("%1\n").arg(pwd).toUtf8()); 125 m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8());
139 m_procInput->flush(); 126 m_ptyFile->flush();
140 return; 127 return;
141 } else { 128 } else {
142 DEBUG << "HgRunner::getUsername: user cancelled" << endl; 129 DEBUG << "HgRunner::getUsername: user cancelled" << endl;
143 killCurrentCommand(); 130 killCurrentCommand();
144 return; 131 return;
149 killCurrentCommand(); 136 killCurrentCommand();
150 } 137 }
151 138
152 void HgRunner::getPassword() 139 void HgRunner::getPassword()
153 { 140 {
154 if (m_procInput) { 141 if (m_ptyFile) {
155 bool ok = false; 142 bool ok = false;
156 QString prompt = tr("Password:"); 143 QString prompt = tr("Password:");
157 if (m_userName != "") { 144 if (m_userName != "") {
158 if (m_realm != "") { 145 if (m_realm != "") {
159 prompt = tr("Password for \"%1\" at \"%2\":") 146 prompt = tr("Password for \"%1\" at \"%2\":")
166 QString pwd = QInputDialog::getText 153 QString pwd = QInputDialog::getText
167 (qobject_cast<QWidget *>(parent()), 154 (qobject_cast<QWidget *>(parent()),
168 tr("Enter password"), prompt, 155 tr("Enter password"), prompt,
169 QLineEdit::Password, QString(), &ok); 156 QLineEdit::Password, QString(), &ok);
170 if (ok) { 157 if (ok) {
171 m_procInput->write(QString("%1\n").arg(pwd).toUtf8()); 158 m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8());
172 m_procInput->flush(); 159 m_ptyFile->flush();
173 return; 160 return;
174 } else { 161 } else {
175 DEBUG << "HgRunner::getPassword: user cancelled" << endl; 162 DEBUG << "HgRunner::getPassword: user cancelled" << endl;
176 killCurrentCommand(); 163 killCurrentCommand();
177 return; 164 return;
180 // user cancelled or something went wrong 167 // user cancelled or something went wrong
181 DEBUG << "HgRunner::getPassword: something went wrong" << endl; 168 DEBUG << "HgRunner::getPassword: something went wrong" << endl;
182 killCurrentCommand(); 169 killCurrentCommand();
183 } 170 }
184 171
185 void HgRunner::checkPrompts(QString chunk) 172 bool HgRunner::checkPrompts(QString chunk)
186 { 173 {
187 //DEBUG << "checkPrompts: " << chunk << endl; 174 //DEBUG << "checkPrompts: " << chunk << endl;
188 175
189 QString text = chunk.trimmed(); 176 QString text = chunk.trimmed();
190 QString lower = text.toLower(); 177 QString lower = text.toLower();
191 if (lower.endsWith("password:")) { 178 if (lower.endsWith("password:")) {
192 getPassword(); 179 getPassword();
193 return; 180 return true;
194 } 181 }
195 if (lower.endsWith("user:")) { 182 if (lower.endsWith("user:")) {
196 getUsername(); 183 getUsername();
197 return; 184 return true;
198 } 185 }
199 QRegExp userRe("\\buser:\\s*([^\\s]+)"); 186 QRegExp userRe("\\buser:\\s*([^\\s]+)");
200 if (userRe.indexIn(text) >= 0) { 187 if (userRe.indexIn(text) >= 0) {
201 noteUsername(userRe.cap(1)); 188 noteUsername(userRe.cap(1));
202 } 189 }
203 QRegExp realmRe("\\brealmr:\\s*([^\\s]+)"); 190 QRegExp realmRe("\\brealmr:\\s*([^\\s]+)");
204 if (realmRe.indexIn(text) >= 0) { 191 if (realmRe.indexIn(text) >= 0) {
205 noteRealm(realmRe.cap(1)); 192 noteRealm(realmRe.cap(1));
206 } 193 }
194 return false;
207 } 195 }
208 196
209 void HgRunner::dataReadyStdout() 197 void HgRunner::dataReadyStdout()
210 { 198 {
211 DEBUG << "dataReadyStdout" << endl; 199 DEBUG << "dataReadyStdout" << endl;
212 QString chunk = QString::fromUtf8(m_proc->readAllStandardOutput()); 200 QString chunk = QString::fromUtf8(m_proc->readAllStandardOutput());
213 m_stdout += chunk; 201 if (!checkPrompts(chunk)) {
214 checkPrompts(chunk); 202 m_stdout += chunk;
203 }
215 } 204 }
216 205
217 void HgRunner::dataReadyStderr() 206 void HgRunner::dataReadyStderr()
218 { 207 {
219 DEBUG << "dataReadyStderr" << endl; 208 DEBUG << "dataReadyStderr" << endl;
220 QString chunk = QString::fromUtf8(m_proc->readAllStandardError()); 209 QString chunk = QString::fromUtf8(m_proc->readAllStandardError());
221 m_stderr += chunk; 210 DEBUG << chunk;
222 checkPrompts(chunk); 211 if (!checkPrompts(chunk)) {
212 m_stderr += chunk;
213 }
214 }
215
216 void HgRunner::dataReadyPty()
217 {
218 DEBUG << "dataReadyPty" << endl;
219 QString chunk = QString::fromUtf8(m_ptyFile->readAll());
220 DEBUG << "chunk of " << chunk.length() << " chars" << endl;
221 if (!checkPrompts(chunk)) {
222 m_stdout += chunk;
223 }
223 } 224 }
224 225
225 void HgRunner::finished(int procExitCode, QProcess::ExitStatus procExitStatus) 226 void HgRunner::finished(int procExitCode, QProcess::ExitStatus procExitStatus)
226 { 227 {
227 // Save the current action and reset m_currentAction before we 228 // Save the current action and reset m_currentAction before we
233 234
234 m_isRunning = false; 235 m_isRunning = false;
235 m_currentAction = HgAction(); 236 m_currentAction = HgAction();
236 237
237 closeProcInput(); 238 closeProcInput();
239 delete m_proc;
240 m_proc = 0;
238 241
239 if (completedAction.action == ACT_NONE) { 242 if (completedAction.action == ACT_NONE) {
240 DEBUG << "HgRunner::finished: WARNING: completed action is ACT_NONE" << endl; 243 DEBUG << "HgRunner::finished: WARNING: completed action is ACT_NONE" << endl;
241 }
242
243 if (procExitCode == 0 && procExitStatus == QProcess::NormalExit) {
244 DEBUG << "HgRunner::finished: Command completed successfully" << endl;
245 emit commandCompleted(completedAction, m_stdout);
246 } else { 244 } else {
247 DEBUG << "HgRunner::finished: Command failed, stderr follows" << endl; 245 if (procExitCode == 0 && procExitStatus == QProcess::NormalExit) {
248 DEBUG << m_stderr << endl; 246 DEBUG << "HgRunner::finished: Command completed successfully"
249 emit commandFailed(completedAction, m_stderr); 247 << endl;
248 emit commandCompleted(completedAction, m_stdout);
249 } else {
250 DEBUG << "HgRunner::finished: Command failed, exit code "
251 << procExitCode << ", exit status " << procExitStatus
252 << ", stderr follows" << endl;
253 DEBUG << m_stderr << endl;
254 emit commandFailed(completedAction, m_stderr);
255 }
250 } 256 }
251 257
252 checkQueue(); 258 checkQueue();
253 } 259 }
254 260
255 void HgRunner::killCurrentCommand() 261 void HgRunner::killCurrentCommand()
256 { 262 {
257 if (m_isRunning) { 263 if (m_isRunning) {
258 m_proc -> kill(); 264 m_currentAction.action = ACT_NONE; // so that we don't bother to notify
265 m_proc->kill();
259 } 266 }
260 } 267 }
261 268
262 void HgRunner::checkQueue() 269 void HgRunner::checkQueue()
263 { 270 {
304 m_stdout.clear(); 311 m_stdout.clear();
305 m_stderr.clear(); 312 m_stderr.clear();
306 m_realm = ""; 313 m_realm = "";
307 m_userName = ""; 314 m_userName = "";
308 315
316 m_proc = new QProcess;
317
318 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
319 env.insert("LANG", "en_US.utf8");
320 env.insert("LC_ALL", "en_US.utf8");
321 env.insert("HGPLAIN", "1");
322 m_proc->setProcessEnvironment(env);
323
324 connect(m_proc, SIGNAL(started()), this, SLOT(started()));
325 connect(m_proc, SIGNAL(finished(int, QProcess::ExitStatus)),
326 this, SLOT(finished(int, QProcess::ExitStatus)));
327 connect(m_proc, SIGNAL(readyReadStandardOutput()),
328 this, SLOT(dataReadyStdout()));
329 connect(m_proc, SIGNAL(readyReadStandardError()),
330 this, SLOT(dataReadyStderr()));
331
309 if (!action.workingDir.isEmpty()) { 332 if (!action.workingDir.isEmpty()) {
310 m_proc->setWorkingDirectory(action.workingDir); 333 m_proc->setWorkingDirectory(action.workingDir);
311 } 334 }
312 335
313 if (interactive) { 336 if (interactive) {
314 openTerminal(); 337 openTerminal();
315 if (m_ptySlaveFilename != "") { 338 if (m_ptySlaveFilename != "") {
339 DEBUG << "HgRunner: connecting to pseudoterminal" << endl;
316 m_proc->setStandardInputFile(m_ptySlaveFilename); 340 m_proc->setStandardInputFile(m_ptySlaveFilename);
341 m_proc->setStandardOutputFile(m_ptySlaveFilename);
342 // m_proc->setStandardErrorFile(m_ptySlaveFilename);
317 } 343 }
318 } 344 }
319 345
320 QString cmdline = executable; 346 QString cmdline = executable;
321 foreach (QString param, params) cmdline += " " + param; 347 foreach (QString param, params) cmdline += " " + param;
322 DEBUG << "HgRunner: starting: " << cmdline << " with cwd " 348 DEBUG << "HgRunner: starting: " << cmdline << " with cwd "
323 << action.workingDir << endl; 349 << action.workingDir << endl;
324 350
325 m_currentAction = action; 351 m_currentAction = action;
352
353 // fill these out with what we actually ran
354 m_currentAction.executable = executable;
355 m_currentAction.params = params;
326 356
327 DEBUG << "set current action to " << m_currentAction.action << endl; 357 DEBUG << "set current action to " << m_currentAction.action << endl;
328 358
329 m_proc->start(executable, params); 359 m_proc->start(executable, params);
330 } 360 }
345 if (master < 0) { 375 if (master < 0) {
346 DEBUG << "openpt failed" << endl; 376 DEBUG << "openpt failed" << endl;
347 perror("openpt failed"); 377 perror("openpt failed");
348 return; 378 return;
349 } 379 }
380 struct termios t;
381 if (tcgetattr(master, &t)) {
382 DEBUG << "tcgetattr failed" << endl;
383 perror("tcgetattr failed");
384 }
385 cfmakeraw(&t);
386 if (tcsetattr(master, TCSANOW, &t)) {
387 DEBUG << "tcsetattr failed" << endl;
388 perror("tcsetattr failed");
389 }
350 if (grantpt(master)) { 390 if (grantpt(master)) {
351 perror("grantpt failed"); 391 perror("grantpt failed");
352 } 392 }
353 if (unlockpt(master)) { 393 if (unlockpt(master)) {
354 perror("unlockpt failed"); 394 perror("unlockpt failed");
358 perror("ptsname failed"); 398 perror("ptsname failed");
359 ::close(master); 399 ::close(master);
360 return; 400 return;
361 } 401 }
362 m_ptyMasterFd = master; 402 m_ptyMasterFd = master;
363 m_procInput = new QFile(); 403 m_ptyFile = new QFile();
364 m_procInput->open(m_ptyMasterFd, QFile::WriteOnly); 404 connect(m_ptyFile, SIGNAL(readyRead()), this, SLOT(dataReadyPty()));
405 if (!m_ptyFile->open(m_ptyMasterFd, QFile::ReadWrite)) {
406 DEBUG << "HgRunner::openTerminal: Failed to open QFile on master fd" << endl;
407 }
365 m_ptySlaveFilename = slave; 408 m_ptySlaveFilename = slave;
366 DEBUG << "HgRunner::openTerminal: succeeded, slave is " 409 DEBUG << "HgRunner::openTerminal: succeeded, slave is "
367 << m_ptySlaveFilename << endl; 410 << m_ptySlaveFilename << endl;
368 #endif 411 #endif
369 } 412 }
370 413
371 void HgRunner::closeTerminal() 414 void HgRunner::closeTerminal()
372 { 415 {
373 #ifndef Q_OS_WIN32 416 #ifndef Q_OS_WIN32
374 if (m_ptySlaveFilename != "") { 417 if (m_ptySlaveFilename != "") {
375 delete m_procInput; 418 delete m_ptyFile;
376 m_procInput = 0; 419 m_ptyFile = 0;
377 ::close(m_ptyMasterFd); 420 ::close(m_ptyMasterFd);
378 m_ptySlaveFilename = ""; 421 m_ptySlaveFilename = "";
379 } 422 }
380 #endif 423 #endif
381 } 424 }