annotate hgrunner.cpp @ 110:0f039d3cc38e

* Separate out the hgrunner output into stdout and stderr again (merging them was a failed experiment)
author Chris Cannam
date Fri, 26 Nov 2010 14:50:10 +0000
parents 1721c580c10e
children 151209bc5bd6
rev   line source
Chris@57 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@57 2
Chris@57 3 /*
Chris@57 4 EasyMercurial
Chris@57 5
Chris@57 6 Based on HgExplorer by Jari Korhonen
Chris@57 7 Copyright (c) 2010 Jari Korhonen
Chris@57 8 Copyright (c) 2010 Chris Cannam
Chris@57 9 Copyright (c) 2010 Queen Mary, University of London
Chris@57 10
Chris@57 11 This program is free software; you can redistribute it and/or
Chris@57 12 modify it under the terms of the GNU General Public License as
Chris@57 13 published by the Free Software Foundation; either version 2 of the
Chris@57 14 License, or (at your option) any later version. See the file
Chris@57 15 COPYING included with this distribution for more information.
Chris@57 16 */
jtkorhonen@0 17
jtkorhonen@0 18 #include "hgrunner.h"
Chris@62 19 #include "common.h"
Chris@57 20 #include "debug.h"
Chris@50 21
Chris@50 22 #include <QPushButton>
Chris@50 23 #include <QListWidget>
Chris@50 24 #include <QDialog>
Chris@50 25 #include <QLabel>
Chris@50 26 #include <QVBoxLayout>
Chris@62 27 #include <QSettings>
Chris@75 28 #include <QInputDialog>
jtkorhonen@0 29
Chris@43 30 #include <iostream>
jtkorhonen@0 31 #include <unistd.h>
Chris@75 32 #include <errno.h>
Chris@75 33 #include <stdio.h>
jtkorhonen@0 34
Chris@76 35 #ifndef Q_OS_WIN32
Chris@80 36 #ifdef Q_OS_MAC
Chris@80 37 #include <util.h>
Chris@80 38 #else
Chris@76 39 #include <pty.h>
Chris@76 40 #endif
Chris@80 41 #endif
Chris@76 42
jtkorhonen@0 43 HgRunner::HgRunner(QWidget * parent): QProgressBar(parent)
jtkorhonen@0 44 {
Chris@84 45 m_proc = new QProcess(this);
jtkorhonen@0 46
cannam@45 47 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
cannam@45 48 env.insert("LANG", "en_US.utf8");
cannam@45 49 env.insert("LC_ALL", "en_US.utf8");
Chris@104 50 env.insert("HGPLAIN", "1");
Chris@84 51 m_proc->setProcessEnvironment(env);
Chris@84 52
jtkorhonen@0 53 setTextVisible(false);
jtkorhonen@0 54 setVisible(false);
Chris@84 55 m_isRunning = false;
jtkorhonen@0 56
Chris@84 57 connect(m_proc, SIGNAL(started()), this, SLOT(started()));
Chris@84 58 connect(m_proc, SIGNAL(finished(int, QProcess::ExitStatus)),
Chris@62 59 this, SLOT(finished(int, QProcess::ExitStatus)));
Chris@110 60 connect(m_proc, SIGNAL(readyReadStandardOutput()),
Chris@110 61 this, SLOT(dataReadyStdout()));
Chris@110 62 connect(m_proc, SIGNAL(readyReadStandardError()),
Chris@110 63 this, SLOT(dataReadyStderr()));
jtkorhonen@0 64 }
jtkorhonen@0 65
jtkorhonen@0 66 HgRunner::~HgRunner()
jtkorhonen@0 67 {
Chris@84 68 if (m_ptySlaveFilename != "") {
Chris@84 69 ::close(m_ptyMasterFd);
Chris@75 70 }
Chris@84 71 delete m_proc;
jtkorhonen@0 72 }
jtkorhonen@0 73
Chris@109 74 void HgRunner::requestAction(HgAction action)
Chris@109 75 {
Chris@109 76 DEBUG << "requestAction " << action.action << endl;
Chris@109 77 bool pushIt = true;
Chris@109 78 if (m_queue.empty()) {
Chris@109 79 if (action == m_currentAction) {
Chris@109 80 // this request is identical to the thing we're executing
Chris@109 81 DEBUG << "requestAction: we're already handling this one, ignoring identical request" << endl;
Chris@109 82 pushIt = false;
Chris@109 83 }
Chris@109 84 } else {
Chris@109 85 HgAction last = m_queue.back();
Chris@109 86 if (action == last) {
Chris@109 87 // this request is identical to the previous thing we
Chris@109 88 // queued which we haven't executed yet
Chris@109 89 DEBUG << "requestAction: we're already queueing this one, ignoring identical request" << endl;
Chris@109 90 pushIt = false;
Chris@109 91 }
Chris@109 92 }
Chris@109 93 if (pushIt) m_queue.push_back(action);
Chris@109 94 checkQueue();
Chris@109 95 }
Chris@109 96
Chris@62 97 QString HgRunner::getHgBinaryName()
Chris@62 98 {
Chris@62 99 QSettings settings;
Chris@77 100 QString hg = settings.value("hgbinary", "").toString();
Chris@77 101 if (hg == "") {
Chris@77 102 hg = findExecutable("hg");
Chris@77 103 }
Chris@77 104 if (hg != "hg") {
Chris@77 105 settings.setValue("hgbinary", hg);
Chris@77 106 }
Chris@62 107 return hg;
Chris@62 108 }
Chris@62 109
jtkorhonen@0 110 void HgRunner::started()
jtkorhonen@0 111 {
Chris@104 112 DEBUG << "started" << endl;
Chris@75 113 /*
Chris@104 114 m_proc->write("blah\n");
Chris@104 115 m_proc->write("blah\n");
Chris@104 116 m_proc -> closeWriteChannel();
Chris@75 117 */
jtkorhonen@0 118 }
jtkorhonen@0 119
Chris@84 120 void HgRunner::noteUsername(QString name)
Chris@75 121 {
Chris@84 122 m_userName = name;
Chris@75 123 }
Chris@75 124
Chris@84 125 void HgRunner::noteRealm(QString realm)
Chris@75 126 {
Chris@84 127 m_realm = realm;
Chris@84 128 }
Chris@84 129
Chris@84 130 void HgRunner::getUsername()
Chris@84 131 {
Chris@84 132 if (m_procInput) {
Chris@84 133 bool ok = false;
Chris@84 134 QString prompt = tr("User name:");
Chris@84 135 if (m_realm != "") {
Chris@84 136 prompt = tr("User name for \"%1\":").arg(m_realm);
Chris@84 137 }
Chris@84 138 QString pwd = QInputDialog::getText
Chris@84 139 (qobject_cast<QWidget *>(parent()),
Chris@84 140 tr("Enter user name"), prompt,
Chris@84 141 QLineEdit::Normal, QString(), &ok);
Chris@84 142 if (ok) {
Chris@84 143 m_procInput->write(QString("%1\n").arg(pwd).toUtf8());
Chris@84 144 m_procInput->flush();
Chris@84 145 return;
Chris@84 146 }
Chris@84 147 }
Chris@84 148 // user cancelled or something went wrong
Chris@84 149 killCurrentCommand();
Chris@84 150 }
Chris@84 151
Chris@84 152 void HgRunner::getPassword()
Chris@84 153 {
Chris@84 154 if (m_procInput) {
Chris@84 155 bool ok = false;
Chris@84 156 QString prompt = tr("Password:");
Chris@84 157 if (m_userName != "") {
Chris@84 158 if (m_realm != "") {
Chris@84 159 prompt = tr("Password for \"%1\" at \"%2\":")
Chris@84 160 .arg(m_userName).arg(m_realm);
Chris@75 161 } else {
Chris@84 162 prompt = tr("Password for user \"%1\":")
Chris@84 163 .arg(m_userName);
Chris@75 164 }
Chris@75 165 }
Chris@84 166 QString pwd = QInputDialog::getText
Chris@84 167 (qobject_cast<QWidget *>(parent()),
Chris@84 168 tr("Enter password"), prompt,
Chris@84 169 QLineEdit::Password, QString(), &ok);
Chris@84 170 if (ok) {
Chris@84 171 m_procInput->write(QString("%1\n").arg(pwd).toUtf8());
Chris@84 172 m_procInput->flush();
Chris@84 173 return;
Chris@84 174 }
Chris@75 175 }
Chris@84 176 // user cancelled or something went wrong
Chris@84 177 killCurrentCommand();
Chris@84 178 }
Chris@84 179
Chris@84 180 void HgRunner::checkPrompts(QString chunk)
Chris@84 181 {
Chris@93 182 //DEBUG << "checkPrompts: " << chunk << endl;
Chris@84 183
Chris@84 184 QString text = chunk.trimmed();
Chris@84 185 QString lower = text.toLower();
Chris@84 186 if (lower.endsWith("password:")) {
Chris@84 187 getPassword();
Chris@84 188 return;
Chris@84 189 }
Chris@84 190 if (lower.endsWith("user:")) {
Chris@84 191 getUsername();
Chris@84 192 return;
Chris@84 193 }
Chris@84 194 QRegExp userRe("\\buser:\\s*([^\\s]+)");
Chris@84 195 if (userRe.indexIn(text) >= 0) {
Chris@84 196 noteUsername(userRe.cap(1));
Chris@84 197 }
Chris@84 198 QRegExp realmRe("\\brealmr:\\s*([^\\s]+)");
Chris@84 199 if (realmRe.indexIn(text) >= 0) {
Chris@84 200 noteRealm(realmRe.cap(1));
Chris@84 201 }
Chris@84 202 }
Chris@84 203
Chris@110 204 void HgRunner::dataReadyStdout()
Chris@84 205 {
Chris@110 206 DEBUG << "dataReadyStdout" << endl;
Chris@110 207 QString chunk = QString::fromUtf8(m_proc->readAllStandardOutput());
Chris@110 208 m_stdout += chunk;
Chris@110 209 checkPrompts(chunk);
Chris@110 210 }
Chris@110 211
Chris@110 212 void HgRunner::dataReadyStderr()
Chris@110 213 {
Chris@110 214 DEBUG << "dataReadyStderr" << endl;
Chris@110 215 QString chunk = QString::fromUtf8(m_proc->readAllStandardError());
Chris@110 216 m_stderr += chunk;
Chris@84 217 checkPrompts(chunk);
Chris@75 218 }
Chris@75 219
jtkorhonen@0 220 void HgRunner::finished(int procExitCode, QProcess::ExitStatus procExitStatus)
jtkorhonen@0 221 {
Chris@109 222 // Save the current action and reset m_currentAction before we
Chris@109 223 // emit a signal to mark the completion; otherwise we may be
Chris@109 224 // resetting the action after a slot has already tried to set it
Chris@109 225 // to something else to start a new action
Chris@109 226
Chris@109 227 HgAction completedAction = m_currentAction;
Chris@109 228
Chris@84 229 m_isRunning = false;
Chris@109 230 m_currentAction = HgAction();
Chris@84 231
Chris@84 232 closeProcInput();
jtkorhonen@0 233
Chris@109 234 if (completedAction.action == ACT_NONE) {
Chris@109 235 DEBUG << "HgRunner::finished: WARNING: completed action is ACT_NONE" << endl;
Chris@109 236 }
Chris@109 237
Chris@74 238 if (procExitCode == 0 && procExitStatus == QProcess::NormalExit) {
Chris@84 239 DEBUG << "HgRunner::finished: Command completed successfully" << endl;
Chris@110 240 emit commandCompleted(completedAction, m_stdout);
Chris@62 241 } else {
Chris@84 242 DEBUG << "HgRunner::finished: Command failed" << endl;
Chris@110 243 emit commandFailed(completedAction, m_stderr);
jtkorhonen@0 244 }
Chris@109 245
Chris@109 246 checkQueue();
jtkorhonen@0 247 }
Chris@109 248 /*
Chris@62 249 bool HgRunner::isCommandRunning()
jtkorhonen@0 250 {
Chris@84 251 return m_isRunning;
jtkorhonen@0 252 }
Chris@109 253 */
jtkorhonen@0 254
Chris@62 255 void HgRunner::killCurrentCommand()
jtkorhonen@0 256 {
Chris@109 257 if (m_isRunning) {
Chris@84 258 m_proc -> kill();
jtkorhonen@0 259 }
jtkorhonen@0 260 }
jtkorhonen@0 261
Chris@109 262 void HgRunner::checkQueue()
Chris@62 263 {
Chris@109 264 if (m_isRunning) {
Chris@109 265 return;
Chris@109 266 }
Chris@109 267 if (m_queue.empty()) {
Chris@109 268 hide();
Chris@109 269 return;
Chris@109 270 }
Chris@109 271 HgAction toRun = m_queue.front();
Chris@109 272 m_queue.pop_front();
Chris@109 273 DEBUG << "checkQueue: have action: running " << toRun.action << endl;
Chris@109 274 startCommand(toRun);
Chris@109 275 }
Chris@109 276
Chris@109 277 void HgRunner::startCommand(HgAction action)
Chris@109 278 {
Chris@109 279 QString executable = action.executable;
Chris@109 280 bool interactive = false;
Chris@109 281 QStringList params = action.params;
Chris@109 282
Chris@109 283 if (executable == "") {
Chris@109 284 // This is a Hg command
Chris@109 285 executable = getHgBinaryName();
Chris@104 286 #ifdef Q_OS_WIN32
Chris@109 287 // This at least means we won't block on the non-working password prompt
Chris@109 288 params.push_front("--noninteractive");
Chris@104 289 #else
Chris@109 290 // password prompt should work here
Chris@109 291 if (action.mayBeInteractive()) {
Chris@109 292 params.push_front("ui.interactive=true");
Chris@109 293 params.push_front("--config");
Chris@109 294 interactive = true;
Chris@109 295 } else {
Chris@109 296 params.push_front("--noninteractive");
Chris@109 297 }
Chris@107 298 }
Chris@104 299 #endif
jtkorhonen@0 300
Chris@84 301 m_isRunning = true;
jtkorhonen@0 302 setRange(0, 0);
Chris@109 303 show();
Chris@110 304 m_stdout.clear();
Chris@110 305 m_stderr.clear();
Chris@84 306 m_realm = "";
Chris@84 307 m_userName = "";
jtkorhonen@0 308
Chris@109 309 if (!action.workingDir.isEmpty()) {
Chris@109 310 m_proc->setWorkingDirectory(action.workingDir);
jtkorhonen@0 311 }
jtkorhonen@0 312
Chris@84 313 m_procInput = 0;
Chris@107 314 m_ptySlaveFilename = "";
Chris@107 315
Chris@84 316 #ifndef Q_OS_WIN32
Chris@107 317 if (interactive) {
Chris@107 318 char name[1024];
Chris@107 319 if (openpty(&m_ptyMasterFd, &m_ptySlaveFd, name, NULL, NULL)) {
Chris@107 320 perror("openpty failed");
Chris@107 321 } else {
Chris@107 322 DEBUG << "openpty succeeded: master " << m_ptyMasterFd
Chris@107 323 << " slave " << m_ptySlaveFd << " filename " << name << endl;
Chris@107 324 m_procInput = new QFile;
Chris@107 325 m_procInput->open(m_ptyMasterFd, QFile::WriteOnly);
Chris@107 326 m_ptySlaveFilename = name;
Chris@107 327 m_proc->setStandardInputFile(m_ptySlaveFilename);
Chris@107 328 ::close(m_ptySlaveFd);
Chris@107 329 }
Chris@84 330 }
Chris@84 331 #endif
Chris@84 332
Chris@109 333 QString cmdline = executable;
Chris@57 334 foreach (QString param, params) cmdline += " " + param;
Chris@64 335 DEBUG << "HgRunner: starting: " << cmdline << " with cwd "
Chris@109 336 << action.workingDir << endl;
Chris@43 337
Chris@109 338 m_currentAction = action;
Chris@109 339
Chris@109 340 DEBUG << "set current action to " << m_currentAction.action << endl;
Chris@109 341
Chris@109 342 m_proc->start(executable, params);
Chris@84 343 }
Chris@84 344
Chris@84 345 void HgRunner::closeProcInput()
Chris@84 346 {
Chris@84 347 DEBUG << "closeProcInput" << endl;
Chris@84 348
Chris@84 349 m_proc->closeWriteChannel();
Chris@84 350 #ifndef Q_OS_WIN32
Chris@84 351 if (m_ptySlaveFilename != "") {
Chris@84 352 ::close(m_ptyMasterFd);
Chris@84 353 m_ptySlaveFilename = "";
Chris@84 354 }
Chris@84 355 #endif
jtkorhonen@0 356 }
jtkorhonen@0 357