Mercurial > hg > easyhg
diff src/hgrunner.cpp @ 370:b9c153e00e84
Move source files to src/
author | Chris Cannam |
---|---|
date | Thu, 24 Mar 2011 10:27:51 +0000 |
parents | hgrunner.cpp@bb189827f6d1 |
children | 5cc0d897eb26 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hgrunner.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,544 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "hgrunner.h" +#include "common.h" +#include "debug.h" +#include "settingsdialog.h" + +#include <QPushButton> +#include <QListWidget> +#include <QDialog> +#include <QLabel> +#include <QVBoxLayout> +#include <QSettings> +#include <QInputDialog> +#include <QTemporaryFile> +#include <QDir> + +#include <iostream> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#ifndef Q_OS_WIN32 +#include <unistd.h> +#include <termios.h> +#include <fcntl.h> +#endif + +HgRunner::HgRunner(QString myDirPath, QWidget * parent) : + QProgressBar(parent), + m_myDirPath(myDirPath) +{ + m_proc = 0; + + // Always unbundle the extension: even if it already exists (in + // case we're upgrading) and even if we're not going to use it (so + // that it's available in case someone wants to use it later, + // e.g. to fix a malfunctioning setup). But the path we actually + // prefer is the one in the settings first, if it exists; then the + // unbundled one; then anything in the path if for some reason + // unbundling failed + unbundleExtension(); + + setTextVisible(false); + setVisible(false); + m_isRunning = false; +} + +HgRunner::~HgRunner() +{ + closeTerminal(); + if (m_proc) { + m_proc->kill(); + m_proc->deleteLater(); + } +} + +QString HgRunner::getUnbundledFileName() +{ + return SettingsDialog::getUnbundledExtensionFileName(); +} + +QString HgRunner::unbundleExtension() +{ + // Pull out the bundled Python file into a temporary file, and + // copy it to our known extension location, replacing the magic + // text NO_EASYHG_IMPORT_PATH with our installation location + + QString bundled = ":easyhg.py"; + QString unbundled = getUnbundledFileName(); + + QString target = QFileInfo(unbundled).path(); + if (!QDir().mkpath(target)) { + DEBUG << "Failed to make unbundle path " << target << endl; + std::cerr << "Failed to make unbundle path " << target << std::endl; + return ""; + } + + QFile bf(bundled); + DEBUG << "unbundle: bundled file will be " << bundled << endl; + if (!bf.exists() || !bf.open(QIODevice::ReadOnly)) { + DEBUG << "Bundled extension is missing!" << endl; + return ""; + } + + QTemporaryFile tmpfile(QString("%1/easyhg.py.XXXXXX").arg(target)); + tmpfile.setAutoRemove(false); + DEBUG << "unbundle: temp file will be " << tmpfile.fileName() << endl; + if (!tmpfile.open()) { + DEBUG << "Failed to open temporary file " << tmpfile.fileName() << endl; + std::cerr << "Failed to open temporary file " << tmpfile.fileName() << std::endl; + return ""; + } + + QString all = QString::fromUtf8(bf.readAll()); + all.replace("NO_EASYHG_IMPORT_PATH", m_myDirPath); + tmpfile.write(all.toUtf8()); + DEBUG << "unbundle: wrote " << all.length() << " characters" << endl; + + tmpfile.close(); + + QFile ef(unbundled); + if (ef.exists()) { + DEBUG << "unbundle: removing old file " << unbundled << endl; + ef.remove(); + } + DEBUG << "unbundle: renaming " << tmpfile.fileName() << " to " << unbundled << endl; + if (!tmpfile.rename(unbundled)) { + DEBUG << "Failed to move temporary file to target file " << unbundled << endl; + std::cerr << "Failed to move temporary file to target file " << unbundled << std::endl; + return ""; + } + + DEBUG << "Unbundled extension to " << unbundled << endl; + return unbundled; +} + +void HgRunner::requestAction(HgAction action) +{ + DEBUG << "requestAction " << action.action << endl; + bool pushIt = true; + if (m_queue.empty()) { + if (action == m_currentAction) { + // this request is identical to the thing we're executing + DEBUG << "requestAction: we're already handling this one, ignoring identical request" << endl; + pushIt = false; + } + } else { + HgAction last = m_queue.back(); + if (action == last) { + // this request is identical to the previous thing we + // queued which we haven't executed yet + DEBUG << "requestAction: we're already queueing this one, ignoring identical request" << endl; + pushIt = false; + } + } + if (pushIt) m_queue.push_back(action); + checkQueue(); +} + +QString HgRunner::getHgBinaryName() +{ + QSettings settings; + settings.beginGroup("Locations"); + return settings.value("hgbinary", "").toString(); +} + +QString HgRunner::getExtensionLocation() +{ + QSettings settings; + settings.beginGroup("Locations"); + QString extpath = settings.value("extensionpath", "").toString(); + if (extpath != "" && QFile(extpath).exists()) return extpath; + return ""; +} + +void HgRunner::started() +{ + DEBUG << "started" << endl; + /* + m_proc->write("blah\n"); + m_proc->write("blah\n"); + m_proc -> closeWriteChannel(); + */ +} + +void HgRunner::noteUsername(QString name) +{ + m_userName = name; +} + +void HgRunner::noteRealm(QString realm) +{ + m_realm = realm; +} + +void HgRunner::getUsername() +{ + if (m_ptyFile) { + bool ok = false; + QString prompt = tr("User name:"); + if (m_realm != "") { + prompt = tr("User name for \"%1\":").arg(m_realm); + } + QString pwd = QInputDialog::getText + (qobject_cast<QWidget *>(parent()), + tr("Enter user name"), prompt, + QLineEdit::Normal, QString(), &ok); + if (ok) { + m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8()); + m_ptyFile->flush(); + return; + } else { + DEBUG << "HgRunner::getUsername: user cancelled" << endl; + killCurrentCommand(); + return; + } + } + // user cancelled or something went wrong + DEBUG << "HgRunner::getUsername: something went wrong" << endl; + killCurrentCommand(); +} + +void HgRunner::getPassword() +{ + if (m_ptyFile) { + bool ok = false; + QString prompt = tr("Password:"); + if (m_userName != "") { + if (m_realm != "") { + prompt = tr("Password for \"%1\" at \"%2\":") + .arg(m_userName).arg(m_realm); + } else { + prompt = tr("Password for user \"%1\":") + .arg(m_userName); + } + } + QString pwd = QInputDialog::getText + (qobject_cast<QWidget *>(parent()), + tr("Enter password"), prompt, + QLineEdit::Password, QString(), &ok); + if (ok) { + m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8()); + m_ptyFile->flush(); + return; + } else { + DEBUG << "HgRunner::getPassword: user cancelled" << endl; + killCurrentCommand(); + return; + } + } + // user cancelled or something went wrong + DEBUG << "HgRunner::getPassword: something went wrong" << endl; + killCurrentCommand(); +} + +bool HgRunner::checkPrompts(QString chunk) +{ + //DEBUG << "checkPrompts: " << chunk << endl; + + if (!m_currentAction.mayBeInteractive()) return false; + + QString text = chunk.trimmed(); + QString lower = text.toLower(); + if (lower.endsWith("password:")) { + getPassword(); + return true; + } + if (lower.endsWith("user:") || lower.endsWith("username:")) { + getUsername(); + return true; + } + QRegExp userRe("\\buser(name)?:\\s*([^\\s]+)"); + if (userRe.indexIn(text) >= 0) { + noteUsername(userRe.cap(2)); + } + QRegExp realmRe("\\brealmr:\\s*([^\\s]+)"); + if (realmRe.indexIn(text) >= 0) { + noteRealm(realmRe.cap(1)); + } + return false; +} + +void HgRunner::dataReadyStdout() +{ + DEBUG << "dataReadyStdout" << endl; + QString chunk = QString::fromUtf8(m_proc->readAllStandardOutput()); + if (!checkPrompts(chunk)) { + m_stdout += chunk; + } +} + +void HgRunner::dataReadyStderr() +{ + DEBUG << "dataReadyStderr" << endl; + QString chunk = QString::fromUtf8(m_proc->readAllStandardError()); + DEBUG << chunk; + if (!checkPrompts(chunk)) { + m_stderr += chunk; + } +} + +void HgRunner::dataReadyPty() +{ + DEBUG << "dataReadyPty" << endl; + QString chunk = QString::fromUtf8(m_ptyFile->readAll()); + DEBUG << "chunk of " << chunk.length() << " chars" << endl; + if (!checkPrompts(chunk)) { + m_stdout += chunk; + } +} + +void HgRunner::error(QProcess::ProcessError) +{ + finished(-1, QProcess::CrashExit); +} + +void HgRunner::finished(int procExitCode, QProcess::ExitStatus procExitStatus) +{ + // Save the current action and reset m_currentAction before we + // emit a signal to mark the completion; otherwise we may be + // resetting the action after a slot has already tried to set it + // to something else to start a new action + + HgAction completedAction = m_currentAction; + + m_isRunning = false; + m_currentAction = HgAction(); + + //closeProcInput(); + m_proc->deleteLater(); + m_proc = 0; + + if (completedAction.action == ACT_NONE) { + DEBUG << "HgRunner::finished: WARNING: completed action is ACT_NONE" << endl; + } else { + if (procExitCode == 0 && procExitStatus == QProcess::NormalExit) { + DEBUG << "HgRunner::finished: Command completed successfully" + << endl; +// DEBUG << "stdout is " << m_stdout << endl; + emit commandCompleted(completedAction, m_stdout); + } else { + DEBUG << "HgRunner::finished: Command failed, exit code " + << procExitCode << ", exit status " << procExitStatus + << ", stderr follows" << endl; + DEBUG << m_stderr << endl; + emit commandFailed(completedAction, m_stderr); + } + } + + checkQueue(); +} + +void HgRunner::killCurrentActions() +{ + m_queue.clear(); + killCurrentCommand(); +} + +void HgRunner::killCurrentCommand() +{ + if (m_isRunning) { + m_currentAction.action = ACT_NONE; // so that we don't bother to notify + m_proc->kill(); + } +} + +void HgRunner::checkQueue() +{ + if (m_isRunning) { + return; + } + if (m_queue.empty()) { + hide(); + return; + } + HgAction toRun = m_queue.front(); + m_queue.pop_front(); + DEBUG << "checkQueue: have action: running " << toRun.action << endl; + startCommand(toRun); +} + +void HgRunner::startCommand(HgAction action) +{ + QString executable = action.executable; + bool interactive = false; + QStringList params = action.params; + + if (action.workingDir.isEmpty()) { + // We require a working directory, never just operate in pwd + emit commandFailed(action, "EasyMercurial: No working directory supplied, will not run Mercurial command without one"); + return; + } + + QSettings settings; + settings.beginGroup("General"); + + if (executable == "") { + // This is a Hg command + executable = getHgBinaryName(); + + if (action.mayBeInteractive()) { + params.push_front("ui.interactive=true"); + params.push_front("--config"); + + if (settings.value("useextension", true).toBool()) { + QString extpath = getExtensionLocation(); + params.push_front(QString("extensions.easyhg=%1").arg(extpath)); + params.push_front("--config"); + } + interactive = true; + } + + //!!! want an option to use the mercurial_keyring extension as well + } + + m_isRunning = true; + setRange(0, 0); + if (!action.shouldBeFast()) show(); + m_stdout.clear(); + m_stderr.clear(); + m_realm = ""; + m_userName = ""; + + m_proc = new QProcess; + + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + +#ifdef Q_OS_WIN32 + // On Win32 we like to bundle Hg and other executables with EasyHg + if (m_myDirPath != "") { + env.insert("PATH", m_myDirPath + ";" + env.value("PATH")); + } +#endif + +#ifdef Q_OS_MAC + if (settings.value("python32", false).toBool()) { + env.insert("VERSIONER_PYTHON_PREFER_32_BIT", "1"); + } +#endif + + env.insert("LANG", "en_US.utf8"); + env.insert("LC_ALL", "en_US.utf8"); + env.insert("HGPLAIN", "1"); + m_proc->setProcessEnvironment(env); + + connect(m_proc, SIGNAL(started()), this, SLOT(started())); + connect(m_proc, SIGNAL(error(QProcess::ProcessError)), + this, SLOT(error(QProcess::ProcessError))); + connect(m_proc, SIGNAL(finished(int, QProcess::ExitStatus)), + this, SLOT(finished(int, QProcess::ExitStatus))); + connect(m_proc, SIGNAL(readyReadStandardOutput()), + this, SLOT(dataReadyStdout())); + connect(m_proc, SIGNAL(readyReadStandardError()), + this, SLOT(dataReadyStderr())); + + m_proc->setWorkingDirectory(action.workingDir); + + if (interactive) { + openTerminal(); + if (m_ptySlaveFilename != "") { + DEBUG << "HgRunner: connecting to pseudoterminal" << endl; + m_proc->setStandardInputFile(m_ptySlaveFilename); +// m_proc->setStandardOutputFile(m_ptySlaveFilename); +// m_proc->setStandardErrorFile(m_ptySlaveFilename); + } + } + + QString cmdline = executable; + foreach (QString param, params) cmdline += " " + param; + DEBUG << "HgRunner: starting: " << cmdline << " with cwd " + << action.workingDir << endl; + + m_currentAction = action; + + // fill these out with what we actually ran + m_currentAction.executable = executable; + m_currentAction.params = params; + + DEBUG << "set current action to " << m_currentAction.action << endl; + + emit commandStarting(action); + + m_proc->start(executable, params); +} + +void HgRunner::closeProcInput() +{ + DEBUG << "closeProcInput" << endl; + + m_proc->closeWriteChannel(); +} + +void HgRunner::openTerminal() +{ +#ifndef Q_OS_WIN32 + if (m_ptySlaveFilename != "") return; // already open + DEBUG << "HgRunner::openTerminal: trying to open new pty" << endl; + int master = posix_openpt(O_RDWR | O_NOCTTY); + if (master < 0) { + DEBUG << "openpt failed" << endl; + perror("openpt failed"); + return; + } + struct termios t; + if (tcgetattr(master, &t)) { + DEBUG << "tcgetattr failed" << endl; + perror("tcgetattr failed"); + } + cfmakeraw(&t); + if (tcsetattr(master, TCSANOW, &t)) { + DEBUG << "tcsetattr failed" << endl; + perror("tcsetattr failed"); + } + if (grantpt(master)) { + perror("grantpt failed"); + } + if (unlockpt(master)) { + perror("unlockpt failed"); + } + char *slave = ptsname(master); + if (!slave) { + perror("ptsname failed"); + ::close(master); + return; + } + m_ptyMasterFd = master; + m_ptyFile = new QFile(); + connect(m_ptyFile, SIGNAL(readyRead()), this, SLOT(dataReadyPty())); + if (!m_ptyFile->open(m_ptyMasterFd, QFile::ReadWrite)) { + DEBUG << "HgRunner::openTerminal: Failed to open QFile on master fd" << endl; + } + m_ptySlaveFilename = slave; + DEBUG << "HgRunner::openTerminal: succeeded, slave is " + << m_ptySlaveFilename << endl; +#endif +} + +void HgRunner::closeTerminal() +{ +#ifndef Q_OS_WIN32 + if (m_ptySlaveFilename != "") { + delete m_ptyFile; + m_ptyFile = 0; + ::close(m_ptyMasterFd); + m_ptySlaveFilename = ""; + } +#endif +}