Chris@57: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@57: Chris@57: /* Chris@57: EasyMercurial Chris@57: Chris@57: Based on HgExplorer by Jari Korhonen Chris@57: Copyright (c) 2010 Jari Korhonen Chris@57: Copyright (c) 2010 Chris Cannam Chris@57: Copyright (c) 2010 Queen Mary, University of London Chris@57: Chris@57: This program is free software; you can redistribute it and/or Chris@57: modify it under the terms of the GNU General Public License as Chris@57: published by the Free Software Foundation; either version 2 of the Chris@57: License, or (at your option) any later version. See the file Chris@57: COPYING included with this distribution for more information. Chris@57: */ jtkorhonen@0: jtkorhonen@0: #include "hgrunner.h" Chris@62: #include "common.h" Chris@57: #include "debug.h" Chris@50: Chris@50: #include Chris@50: #include Chris@50: #include Chris@50: #include Chris@50: #include Chris@62: #include Chris@75: #include jtkorhonen@0: Chris@43: #include jtkorhonen@0: #include Chris@75: #include Chris@75: #include jtkorhonen@0: Chris@76: #ifndef Q_OS_WIN32 Chris@80: #ifdef Q_OS_MAC Chris@80: #include Chris@80: #else Chris@76: #include Chris@76: #endif Chris@80: #endif Chris@76: jtkorhonen@0: HgRunner::HgRunner(QWidget * parent): QProgressBar(parent) jtkorhonen@0: { Chris@84: m_proc = new QProcess(this); jtkorhonen@0: cannam@45: QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); cannam@45: env.insert("LANG", "en_US.utf8"); cannam@45: env.insert("LC_ALL", "en_US.utf8"); Chris@104: env.insert("HGPLAIN", "1"); Chris@84: m_proc->setProcessEnvironment(env); Chris@84: jtkorhonen@0: setTextVisible(false); jtkorhonen@0: setVisible(false); Chris@84: m_isRunning = false; jtkorhonen@0: Chris@84: connect(m_proc, SIGNAL(started()), this, SLOT(started())); Chris@84: connect(m_proc, SIGNAL(finished(int, QProcess::ExitStatus)), Chris@62: this, SLOT(finished(int, QProcess::ExitStatus))); Chris@110: connect(m_proc, SIGNAL(readyReadStandardOutput()), Chris@110: this, SLOT(dataReadyStdout())); Chris@110: connect(m_proc, SIGNAL(readyReadStandardError()), Chris@110: this, SLOT(dataReadyStderr())); jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: HgRunner::~HgRunner() jtkorhonen@0: { Chris@84: if (m_ptySlaveFilename != "") { Chris@84: ::close(m_ptyMasterFd); Chris@75: } Chris@84: delete m_proc; jtkorhonen@0: } jtkorhonen@0: Chris@109: void HgRunner::requestAction(HgAction action) Chris@109: { Chris@109: DEBUG << "requestAction " << action.action << endl; Chris@109: bool pushIt = true; Chris@109: if (m_queue.empty()) { Chris@109: if (action == m_currentAction) { Chris@109: // this request is identical to the thing we're executing Chris@109: DEBUG << "requestAction: we're already handling this one, ignoring identical request" << endl; Chris@109: pushIt = false; Chris@109: } Chris@109: } else { Chris@109: HgAction last = m_queue.back(); Chris@109: if (action == last) { Chris@109: // this request is identical to the previous thing we Chris@109: // queued which we haven't executed yet Chris@109: DEBUG << "requestAction: we're already queueing this one, ignoring identical request" << endl; Chris@109: pushIt = false; Chris@109: } Chris@109: } Chris@109: if (pushIt) m_queue.push_back(action); Chris@109: checkQueue(); Chris@109: } Chris@109: Chris@62: QString HgRunner::getHgBinaryName() Chris@62: { Chris@62: QSettings settings; Chris@77: QString hg = settings.value("hgbinary", "").toString(); Chris@77: if (hg == "") { Chris@77: hg = findExecutable("hg"); Chris@77: } Chris@77: if (hg != "hg") { Chris@77: settings.setValue("hgbinary", hg); Chris@77: } Chris@62: return hg; Chris@62: } Chris@62: jtkorhonen@0: void HgRunner::started() jtkorhonen@0: { Chris@104: DEBUG << "started" << endl; Chris@75: /* Chris@104: m_proc->write("blah\n"); Chris@104: m_proc->write("blah\n"); Chris@104: m_proc -> closeWriteChannel(); Chris@75: */ jtkorhonen@0: } jtkorhonen@0: Chris@84: void HgRunner::noteUsername(QString name) Chris@75: { Chris@84: m_userName = name; Chris@75: } Chris@75: Chris@84: void HgRunner::noteRealm(QString realm) Chris@75: { Chris@84: m_realm = realm; Chris@84: } Chris@84: Chris@84: void HgRunner::getUsername() Chris@84: { Chris@84: if (m_procInput) { Chris@84: bool ok = false; Chris@84: QString prompt = tr("User name:"); Chris@84: if (m_realm != "") { Chris@84: prompt = tr("User name for \"%1\":").arg(m_realm); Chris@84: } Chris@84: QString pwd = QInputDialog::getText Chris@84: (qobject_cast(parent()), Chris@84: tr("Enter user name"), prompt, Chris@84: QLineEdit::Normal, QString(), &ok); Chris@84: if (ok) { Chris@84: m_procInput->write(QString("%1\n").arg(pwd).toUtf8()); Chris@84: m_procInput->flush(); Chris@84: return; Chris@84: } Chris@84: } Chris@84: // user cancelled or something went wrong Chris@84: killCurrentCommand(); Chris@84: } Chris@84: Chris@84: void HgRunner::getPassword() Chris@84: { Chris@84: if (m_procInput) { Chris@84: bool ok = false; Chris@84: QString prompt = tr("Password:"); Chris@84: if (m_userName != "") { Chris@84: if (m_realm != "") { Chris@84: prompt = tr("Password for \"%1\" at \"%2\":") Chris@84: .arg(m_userName).arg(m_realm); Chris@75: } else { Chris@84: prompt = tr("Password for user \"%1\":") Chris@84: .arg(m_userName); Chris@75: } Chris@75: } Chris@84: QString pwd = QInputDialog::getText Chris@84: (qobject_cast(parent()), Chris@84: tr("Enter password"), prompt, Chris@84: QLineEdit::Password, QString(), &ok); Chris@84: if (ok) { Chris@84: m_procInput->write(QString("%1\n").arg(pwd).toUtf8()); Chris@84: m_procInput->flush(); Chris@84: return; Chris@84: } Chris@75: } Chris@84: // user cancelled or something went wrong Chris@84: killCurrentCommand(); Chris@84: } Chris@84: Chris@84: void HgRunner::checkPrompts(QString chunk) Chris@84: { Chris@93: //DEBUG << "checkPrompts: " << chunk << endl; Chris@84: Chris@84: QString text = chunk.trimmed(); Chris@84: QString lower = text.toLower(); Chris@84: if (lower.endsWith("password:")) { Chris@84: getPassword(); Chris@84: return; Chris@84: } Chris@84: if (lower.endsWith("user:")) { Chris@84: getUsername(); Chris@84: return; Chris@84: } Chris@84: QRegExp userRe("\\buser:\\s*([^\\s]+)"); Chris@84: if (userRe.indexIn(text) >= 0) { Chris@84: noteUsername(userRe.cap(1)); Chris@84: } Chris@84: QRegExp realmRe("\\brealmr:\\s*([^\\s]+)"); Chris@84: if (realmRe.indexIn(text) >= 0) { Chris@84: noteRealm(realmRe.cap(1)); Chris@84: } Chris@84: } Chris@84: Chris@110: void HgRunner::dataReadyStdout() Chris@84: { Chris@110: DEBUG << "dataReadyStdout" << endl; Chris@110: QString chunk = QString::fromUtf8(m_proc->readAllStandardOutput()); Chris@110: m_stdout += chunk; Chris@110: checkPrompts(chunk); Chris@110: } Chris@110: Chris@110: void HgRunner::dataReadyStderr() Chris@110: { Chris@110: DEBUG << "dataReadyStderr" << endl; Chris@110: QString chunk = QString::fromUtf8(m_proc->readAllStandardError()); Chris@110: m_stderr += chunk; Chris@84: checkPrompts(chunk); Chris@75: } Chris@75: jtkorhonen@0: void HgRunner::finished(int procExitCode, QProcess::ExitStatus procExitStatus) jtkorhonen@0: { Chris@109: // Save the current action and reset m_currentAction before we Chris@109: // emit a signal to mark the completion; otherwise we may be Chris@109: // resetting the action after a slot has already tried to set it Chris@109: // to something else to start a new action Chris@109: Chris@109: HgAction completedAction = m_currentAction; Chris@109: Chris@84: m_isRunning = false; Chris@109: m_currentAction = HgAction(); Chris@84: Chris@84: closeProcInput(); jtkorhonen@0: Chris@109: if (completedAction.action == ACT_NONE) { Chris@109: DEBUG << "HgRunner::finished: WARNING: completed action is ACT_NONE" << endl; Chris@109: } Chris@109: Chris@74: if (procExitCode == 0 && procExitStatus == QProcess::NormalExit) { Chris@84: DEBUG << "HgRunner::finished: Command completed successfully" << endl; Chris@110: emit commandCompleted(completedAction, m_stdout); Chris@62: } else { Chris@84: DEBUG << "HgRunner::finished: Command failed" << endl; Chris@110: emit commandFailed(completedAction, m_stderr); jtkorhonen@0: } Chris@109: Chris@109: checkQueue(); jtkorhonen@0: } Chris@109: /* Chris@62: bool HgRunner::isCommandRunning() jtkorhonen@0: { Chris@84: return m_isRunning; jtkorhonen@0: } Chris@109: */ jtkorhonen@0: Chris@62: void HgRunner::killCurrentCommand() jtkorhonen@0: { Chris@109: if (m_isRunning) { Chris@84: m_proc -> kill(); jtkorhonen@0: } jtkorhonen@0: } jtkorhonen@0: Chris@109: void HgRunner::checkQueue() Chris@62: { Chris@109: if (m_isRunning) { Chris@109: return; Chris@109: } Chris@109: if (m_queue.empty()) { Chris@109: hide(); Chris@109: return; Chris@109: } Chris@109: HgAction toRun = m_queue.front(); Chris@109: m_queue.pop_front(); Chris@109: DEBUG << "checkQueue: have action: running " << toRun.action << endl; Chris@109: startCommand(toRun); Chris@109: } Chris@109: Chris@109: void HgRunner::startCommand(HgAction action) Chris@109: { Chris@109: QString executable = action.executable; Chris@109: bool interactive = false; Chris@109: QStringList params = action.params; Chris@109: Chris@109: if (executable == "") { Chris@109: // This is a Hg command Chris@109: executable = getHgBinaryName(); Chris@104: #ifdef Q_OS_WIN32 Chris@109: // This at least means we won't block on the non-working password prompt Chris@109: params.push_front("--noninteractive"); Chris@104: #else Chris@109: // password prompt should work here Chris@109: if (action.mayBeInteractive()) { Chris@109: params.push_front("ui.interactive=true"); Chris@109: params.push_front("--config"); Chris@109: interactive = true; Chris@109: } else { Chris@109: params.push_front("--noninteractive"); Chris@109: } Chris@107: } Chris@104: #endif jtkorhonen@0: Chris@84: m_isRunning = true; jtkorhonen@0: setRange(0, 0); Chris@109: show(); Chris@110: m_stdout.clear(); Chris@110: m_stderr.clear(); Chris@84: m_realm = ""; Chris@84: m_userName = ""; jtkorhonen@0: Chris@109: if (!action.workingDir.isEmpty()) { Chris@109: m_proc->setWorkingDirectory(action.workingDir); jtkorhonen@0: } jtkorhonen@0: Chris@84: m_procInput = 0; Chris@107: m_ptySlaveFilename = ""; Chris@107: Chris@84: #ifndef Q_OS_WIN32 Chris@107: if (interactive) { Chris@107: char name[1024]; Chris@107: if (openpty(&m_ptyMasterFd, &m_ptySlaveFd, name, NULL, NULL)) { Chris@107: perror("openpty failed"); Chris@107: } else { Chris@107: DEBUG << "openpty succeeded: master " << m_ptyMasterFd Chris@107: << " slave " << m_ptySlaveFd << " filename " << name << endl; Chris@107: m_procInput = new QFile; Chris@107: m_procInput->open(m_ptyMasterFd, QFile::WriteOnly); Chris@107: m_ptySlaveFilename = name; Chris@107: m_proc->setStandardInputFile(m_ptySlaveFilename); Chris@107: ::close(m_ptySlaveFd); Chris@107: } Chris@84: } Chris@84: #endif Chris@84: Chris@109: QString cmdline = executable; Chris@57: foreach (QString param, params) cmdline += " " + param; Chris@64: DEBUG << "HgRunner: starting: " << cmdline << " with cwd " Chris@109: << action.workingDir << endl; Chris@43: Chris@109: m_currentAction = action; Chris@109: Chris@109: DEBUG << "set current action to " << m_currentAction.action << endl; Chris@109: Chris@109: m_proc->start(executable, params); Chris@84: } Chris@84: Chris@84: void HgRunner::closeProcInput() Chris@84: { Chris@84: DEBUG << "closeProcInput" << endl; Chris@84: Chris@84: m_proc->closeWriteChannel(); Chris@84: #ifndef Q_OS_WIN32 Chris@84: if (m_ptySlaveFilename != "") { Chris@84: ::close(m_ptyMasterFd); Chris@84: m_ptySlaveFilename = ""; Chris@84: } Chris@84: #endif jtkorhonen@0: } jtkorhonen@0: