# # # patch "guitone/res/i18n/guitone_de.ts" # from [6a9a72e618efd019a7f5ffbe58ca4e669c5c6ec4] # to [c5de6f12a546b2e620ad7a17b662d42cffc19c4a] # # patch "guitone/src/model/ContentDiff.cpp" # from [3966a4c4626f4e44043f1cefd986b3b8f03bd498] # to [bb1566ca40ccc7944e965896aed30bb46b2d34f9] # # patch "guitone/src/model/GetFile.cpp" # from [847161c06efe93bed700432f94b5e358b8d62ebf] # to [cca2f1587219c6967e807541bf215cef57968b98] # # patch "guitone/src/model/MonotoneDelegate.cpp" # from [4ebb6a0e87914d4dbe4b3e68be5b206661c08e49] # to [890aa34c28c32e9e5c4fb608fd24078992b54493] # # patch "guitone/src/model/MonotoneDelegate.h" # from [63b9b9a1f636c8e6cded8e14250d90cb8c307628] # to [808f597b7debdf4b6a9e9484337fcb39a92ffc87] # # patch "guitone/src/monotone/Monotone.cpp" # from [31361a604f3edb7120e0d751c0fd015f66e9e460] # to [2b27c8a0d3a2b569cb7f5924f94d8a4a9a3a6312] # # patch "guitone/src/monotone/Monotone.h" # from [acbb4071e26433ba787703de88d9a8b67425eaa5] # to [44a8a36ea6ac22617789013e6fc7a71b9ed4db07] # # patch "guitone/src/view/dialogs/GenerateKeypair.cpp" # from [c28127273be56000aa5799437874a633c408b2a0] # to [b01d54d34c43b82259dd4015fa7896cb57cbbb7c] # # patch "guitone/src/view/dialogs/RevisionManifest.cpp" # from [87c413184dee30b528d784b2f67d448d45f10ff7] # to [8d8d61614f41b1de70cb302354e0153170668cd8] # ============================================================ --- guitone/res/i18n/guitone_de.ts 6a9a72e618efd019a7f5ffbe58ca4e669c5c6ec4 +++ guitone/res/i18n/guitone_de.ts c5de6f12a546b2e620ad7a17b662d42cffc19c4a @@ -136,22 +136,22 @@ ChangesetModel - + Revision ID Revisions-ID - + Date Datum - + Author Autor - + Changelog ============================================================ --- guitone/src/model/ContentDiff.cpp 3966a4c4626f4e44043f1cefd986b3b8f03bd498 +++ guitone/src/model/ContentDiff.cpp bb1566ca40ccc7944e965896aed30bb46b2d34f9 @@ -51,7 +51,7 @@ bool ContentDiff::readDiff( QString fileName, QString leftRevision, QString rightRevision ) { - // reset the view + // reset the view reset(); Monotone *mtn = Monotone::singleton(); @@ -63,18 +63,16 @@ bool ContentDiff::readDiff( if (leftRevision.length() == 0) { - int commandNumber; - QStringList cmd; - cmd << "get_base_revision_id"; - - if (!mtn->executeCommand(cmd, commandNumber) || - mtn->getReturnCode(commandNumber) > 0) + int retCode; + + if (!mtn->executeCommand(QStringList() << "get_base_revision_id", retCode) + || retCode != 0) { qWarning("ContentDiff::readDiff: could not execute get_base_revision_id"); return false; } - leftRevision = mtn->getDecodedData(commandNumber); + leftRevision = mtn->getDecodedDataAndReset(); leftRevision.chop(1); } ============================================================ --- guitone/src/model/GetFile.cpp 847161c06efe93bed700432f94b5e358b8d62ebf +++ guitone/src/model/GetFile.cpp cca2f1587219c6967e807541bf215cef57968b98 @@ -37,18 +37,17 @@ bool GetFile::readFileByName(QString fil { Monotone * mtn = Monotone::singleton(); - int commandNumber; QStringList cmd; cmd << "get_base_revision_id"; + int retCode; - if (!mtn->executeCommand(cmd, commandNumber) || - mtn->getReturnCode(commandNumber) > 0) + if (!mtn->executeCommand(cmd, retCode) || retCode != 0) { qWarning("GetFile::readFileByName: could not execute get_base_revision_id"); return false; } - QString baseRevision = mtn->getDecodedData(commandNumber); + QString baseRevision = mtn->getDecodedDataAndReset(); baseRevision.chop(1); return readFileByName(fileName, baseRevision); ============================================================ --- guitone/src/model/MonotoneDelegate.cpp 4ebb6a0e87914d4dbe4b3e68be5b206661c08e49 +++ guitone/src/model/MonotoneDelegate.cpp 890aa34c28c32e9e5c4fb608fd24078992b54493 @@ -24,12 +24,14 @@ MonotoneDelegate::MonotoneDelegate(Autom #include MonotoneDelegate::MonotoneDelegate(AutomateCommand * cmd) - : cmdModel(cmd), commandNumber(-1) + : cmdModel(cmd) { } MonotoneDelegate::~MonotoneDelegate() {} +CommandQueue MonotoneDelegate::commandQueue; + bool MonotoneDelegate::triggerCommand(const QStringList & cmd) { return triggerCommand(cmd, QStringList()); @@ -37,46 +39,69 @@ bool MonotoneDelegate::triggerCommand(co bool MonotoneDelegate::triggerCommand(const QStringList & cmd, const QStringList & opts) { + if(mtnFree) + { + qApp->setOverrideCursor(Qt::WaitCursor); + mtnFree = false; + return doTriggerCommand(cmd, opts); + } + else + { + CommandQueueEntry entry; + entry.cmd = cmd; + entry.del = this; + entry.opts = opts; + commandQueue.enqueue(entry); + return true; + } +} +bool MonotoneDelegate::mtnFree = true; + +bool MonotoneDelegate::doTriggerCommand(const QStringList & cmd, const QStringList & opts) +{ Monotone * mtn = Monotone::singleton(); connect( - mtn, SIGNAL(commandFinished()), - this, SLOT(commandFinished()) + mtn, SIGNAL(commandFinished(int)), + this, SLOT(commandFinished(int)) ); - qApp->setOverrideCursor(Qt::WaitCursor); - - return mtn->triggerCommand(cmd, opts, commandNumber); + return mtn->triggerCommand(cmd, opts); } -void MonotoneDelegate::commandFinished() + +void MonotoneDelegate::commandFinished(int retCode) { Monotone * mtn = Monotone::singleton(); - - // check if this is the command which is interesting for us - if (!mtn->isCommandFinished(commandNumber)) return; - + disconnect( - mtn, SIGNAL(commandFinished()), - this, SLOT(commandFinished()) + mtn, SIGNAL(commandFinished(int)), + this, SLOT(commandFinished(int)) ); // FIXME: does any of our models ever need the raw data? - cmdModel->setAutomateData(mtn->getDecodedData(commandNumber)); - int returnCode = mtn->getReturnCode(commandNumber); + cmdModel->setAutomateData(mtn->getDecodedDataAndReset()); - if (returnCode == 0) + if (retCode == 0) { cmdModel->parseOutput(); } else { - if (!cmdModel->handleError(returnCode)) + if (!cmdModel->handleError(retCode)) { - qCritical("MonotoneDelegate::commandFinished: couldn't handle error %d", returnCode); + qCritical("MonotoneDelegate::commandFinished: couldn't handle error %d", retCode); } } - - qApp->restoreOverrideCursor(); + if(commandQueue.isEmpty()) + { + mtnFree = true; + qApp->restoreOverrideCursor(); + } + else + { + CommandQueueEntry entry = commandQueue.dequeue(); + entry.del->doTriggerCommand(entry.cmd, entry.opts); + } } ============================================================ --- guitone/src/model/MonotoneDelegate.h 63b9b9a1f636c8e6cded8e14250d90cb8c307628 +++ guitone/src/model/MonotoneDelegate.h 808f597b7debdf4b6a9e9484337fcb39a92ffc87 @@ -24,7 +24,17 @@ #include "AutomateCommand.h" #include +#include +struct CommandQueueEntry +{ + MonotoneDelegate *del; + QStringList cmd; + QStringList opts; +}; + +typedef QQueue CommandQueue; + class MonotoneDelegate : public QObject { Q_OBJECT @@ -34,13 +44,15 @@ public: ~MonotoneDelegate(); bool triggerCommand(const QStringList &); bool triggerCommand(const QStringList &, const QStringList &); + bool doTriggerCommand(const QStringList &, const QStringList &); private: + static CommandQueue commandQueue; + static bool mtnFree; AutomateCommand * cmdModel; - int commandNumber; private slots: - void commandFinished(); + void commandFinished(int); }; #endif ============================================================ --- guitone/src/monotone/Monotone.cpp 31361a604f3edb7120e0d751c0fd015f66e9e460 +++ guitone/src/monotone/Monotone.cpp 2b27c8a0d3a2b569cb7f5924f94d8a4a9a3a6312 @@ -36,24 +36,34 @@ // triggerCommand() executeCommand() // | | // V V +// setupNewCommand() setupNewCommand() +// | | +// V V // writeStdin() writeStdin() -// ... | +// | | // ... V -// wait for signalling, readAndParseStdout() and put it into the -// output map (mapped by command number) - if we receive an "end -// command" token, signal that the next command has been finished +// (wait for signalling) readAndParseStdout() // ... | -// ... V -// signal the caller catch the signal locally -// to read out the data and immediately return to -// the caller +// | V +// V caller can immediately +// readAndProcessCommand() retrieve the queried output +// | +// V +// readAndParseStdout() +// | +// V +// signal caller that +// command finished // +// In the synchronous case there also happens a waiting for output, however this +// is done with the SignalWaiter class which triggers the main event loop until +// data become available. +// // Since there is only one instance of the monotone process running at a time, // pending commands need to be queued somehow in order to avoid confusion -// (data are sent to the wrong caller, etc.) - this is done by assigning each -// caller a command number by which it can evaluate if the commandFinished() -// signal it just catched was for the command which it triggered or not -// (using isCommandCompleted(commandNumber) +// (data are sent to the wrong caller, etc.) - this is done in setupNewCommand(). +// A QMutex hinders a second parallel request on entering the wait loop of a +// previous command. // // If a command is about to be executed, its data are written to STDIN of the // running process, whilst encoded into stdio format. @@ -63,13 +73,8 @@ // it at the right time. // // After parsing the output is made available to the calling process by -// get[Raw|Decoded]Data() - these functions also remove the cached data for the -// command so they should always be called, regardless if output is expected -// or even further processed. -// The getReturnCode() method returns the return code for the specified command -// and resets the command as well (isCommandCompleted() then returns false, if -// this is needed at a later time, one could think about keeping this info, -// though it should not make a difference now) +// get*DataAndReset() - this functions also reset the "processing" flag which +// blocks further attempts to use the stdio communication.. // #include "Monotone.h" @@ -80,7 +85,6 @@ #include #include #include -#include Monotone* Monotone::instance = 0; @@ -94,21 +98,20 @@ const int Monotone::StdioBufferSize = 50 const QString Monotone::RequiredInterfaceVersion = "4.0"; const int Monotone::StdioBufferSize = 50 * 1024 * 1024; -const int Monotone::WaitForMonotoneStart = 15000; // milliseconds +const int Monotone::TimeoutWaitForOtherCommand = 5000; // milliseconds Monotone::Monotone(QObject * parent) : QObject(parent), process(0) { - // the count of the commands fired at the current process instance - commandCounter = -1; + // true if a command is currently processed + isProcessingData = false; // true if the process is destroyed in the destructor isCleanExit = false; // inialize the process var process = 0; // path to the monotone binary mtnBinaryPath = ""; - // the current working directory (if in workspace mode) + workDir = ""; - // the current database file (if in database mode) databaseFile = ""; } @@ -174,8 +177,7 @@ void Monotone::shutdownCurrentProcess() if (process) { isCleanExit = true; - completedCommands.clear(); - commandCounter = -1; + isProcessingData = false; // close the pipes process->close(); @@ -186,21 +188,6 @@ void Monotone::shutdownCurrentProcess() // block until the process has really been finished process->waitForFinished(); - disconnect( - process, SIGNAL(readyRead()), - this, SLOT(readAndParseStdout()) - ); - - disconnect( - process, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(processError(QProcess::ProcessError)) - ); - - disconnect( - process, SIGNAL(finished(int, QProcess::ExitStatus)), - this, SLOT(processFinished(int, QProcess::ExitStatus)) - ); - delete process; } } @@ -220,17 +207,11 @@ void Monotone::setupNewProcess() this, SLOT(processFinished(int, QProcess::ExitStatus)) ); - // monitor process errors + // check if there occurs an startup error connect( process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError)) ); - - // parse output from the process - connect( - process, SIGNAL(readyRead()), - this, SLOT(readAndParseStdout()) - ); if (mode == Workspace) { @@ -247,11 +228,8 @@ void Monotone::setupNewProcess() args << "--db" << databaseFile; } - qDebug("Monotone::setupNewProcess: %s %s", - qPrintable(mtnBinaryPath), - qPrintable(args.join(" ")) - ); - + qDebug("Executing %s %s", qPrintable(mtnBinaryPath), qPrintable(args.join(" "))); + process->start(mtnBinaryPath, args); } @@ -329,54 +307,103 @@ void Monotone::processFinished(int code, ); } -bool Monotone::executeCommand(const QStringList & command, int & commandNumber) +void Monotone::timedOut() { + timeout = true; +} + +// FIXME: this method should get some QMutex code to ensure that +// concurring requests to setupNewCommand() do not overwrite variables each +// other, however it occurs that at some places deadlocks pop up, i.e. the +// mutex is tried to be locked by the same process twice, obviously tryLock() +// of QMutex is used. I don't know how to fix this for now, but the whole thing +// will definitely get an issue as soon as we impement asynchronous updates +// of the inventory which run side-by-side normal user actions, so we *need* +// some kind of locking here definitely +bool Monotone::setupNewCommand() +{ + if (process->state() != QProcess::Running && !process->waitForStarted(15000)) + { + qDebug("Monotone::setupNewCommand: Process is not running"); + return false; + } + + timeout = false; + QTimer::singleShot(TimeoutWaitForOtherCommand, this, SLOT(timedOut())); + while(isProcessingData && !timeout) QCoreApplication::processEvents(); + + if (timeout) + { + qDebug("Monotone::setupNewCommand: Timed out waiting for other process"); + return false; + } + + isProcessingData = true; + input.clear(); + output.clear(); + + return true; +} + +bool Monotone::executeCommand(const QStringList & command, int & retCode) +{ QStringList opts; - return executeCommand(command, opts, commandNumber); + return executeCommand(command, opts, retCode); } -bool Monotone::executeCommand(const QStringList & command, const QStringList & options, int & commandNumber) +bool Monotone::executeCommand(const QStringList & command, const QStringList & options, int & retCode) { - if (process->state() != QProcess::Running && - !process->waitForStarted(WaitForMonotoneStart)) + if (!setupNewCommand()) { - qDebug("Monotone::executeCommand: Process is not running"); - return false; + return false; } - commandNumber = writeStdin(command, options); - SignalWaiter waiter(this, SIGNAL(commandFinished())); + writeStdin(command, options); + + bool parsed; + SignalWaiter waiter(process, SIGNAL(readyRead())); do { waiter.wait(500); + + // check if we already could parse the complete stdio output + parsed = readAndParseStdout(retCode); + qDebug("parsed: %d", parsed); + if (!parsed) + qWarning("Monotone::executeCommand: Contents incomplete/invalid."); } - while (!isCommandFinished(commandNumber)); - + while (!parsed); + return true; } -bool Monotone::triggerCommand(const QStringList & command, int & commandNumber) +bool Monotone::triggerCommand(const QStringList & command) { - return triggerCommand(command, QStringList(), commandNumber); + return triggerCommand(command, QStringList()); } -bool Monotone::triggerCommand(const QStringList & command, const QStringList & options, int & commandNumber) +bool Monotone::triggerCommand(const QStringList & command, const QStringList & options) { - if (process->state() != QProcess::Running && - !process->waitForStarted(WaitForMonotoneStart)) + if (!setupNewCommand()) { - qDebug("Monotone::triggerCommand: Process is not running"); - return false; + return false; } - - commandNumber = writeStdin(command, options); + + // read & parse mtn's output as soon as it gets available + connect( + process, SIGNAL(readyRead()), + this, SLOT(readAndProcessCommand()) + ); + + writeStdin(command, options); + return true; } -int Monotone::writeStdin(const QStringList & command, const QStringList & options) +void Monotone::writeStdin(const QStringList & command, const QStringList & options) { - QString commandLine = ""; + commandLine = ""; if (options.size() > 0) { @@ -424,16 +451,31 @@ int Monotone::writeStdin(const QStringLi QTextStream streamStdIn(process); streamStdIn << commandLine; streamStdIn.flush(); - - return ++commandCounter; } -void Monotone::readAndParseStdout() +void Monotone::readAndProcessCommand() { // don't do anything if there is nothing to read if (process->bytesAvailable() == 0) return; - // append any new output and try to parse it + int retCode = 0; + // check if we already could parse the complete stdio output + if (!readAndParseStdout(retCode)) + { + qWarning("Monotone::readAndProcessInput: Contents incomplete/invalid."); + return; + } + + disconnect( + process, SIGNAL(readyRead()), + this, SLOT(readAndProcessCommand()) + ); + + emit commandFinished(retCode); +} + +bool Monotone::readAndParseStdout(int & retCode) +{ input.append(process->readAllStandardOutput()); while (input.size() > 0) @@ -443,25 +485,12 @@ void Monotone::readAndParseStdout() // if the chunk is not yet complete, try again later if (!parser.parse()) { - qWarning("Monotone::readAndParseStdout: Contents invalid."); - return; + return false; } input = input.mid(parser.getProcessedBytes()); - int commandNumber = parser.getCommandNumber(); - int returnCode = parser.getErrorCode(); + output.append(parser.getPayload()); - // TODO: we could theoretically inform the calling process that - // parts of the data could have been queried already - if (output.contains(parser.getCommandNumber())) - { - output[commandNumber].append(parser.getPayload()); - } - else - { - output.insert(commandNumber, parser.getPayload()); - } - // check if this was the last output if (parser.getChunkType() == 'l') { @@ -469,61 +498,35 @@ void Monotone::readAndParseStdout() { qWarning("Monotone::readAndParseStdout: input left: %s", input.data()); } - - // remember the return value of the command - completedCommands.insert(commandNumber, returnCode); - emit commandFinished(); - return; + retCode = parser.getErrorCode(); + // command successfully parsed + return true; } } // if this was not the last output, we need to wait for more data - qWarning("Monotone::readAndParseStdout: Contents incomplete."); - return; + return false; } -QByteArray Monotone::getRawData(int commandNumber) +QByteArray Monotone::getRawDataAndReset() { - QMutexLocker locker(&lock); - - if (!output.contains(commandNumber)) - { - qWarning("Monotone::getRawData: no data for %d available", commandNumber); - return QByteArray(); - } - - QByteArray temp(output.value(commandNumber)); - output.remove(commandNumber); - return temp; + QByteArray data(output); + reset(); + return data; } -QString Monotone::getDecodedData(int commandNumber) +QString Monotone::getDecodedDataAndReset() { - return QString::fromUtf8(getRawData(commandNumber).data()); + QString data = QString::fromUtf8(output.data()); + reset(); + return data; } -int Monotone::getReturnCode(int commandNumber) +void Monotone::reset() { - QMutexLocker locker(&lock); - - if (!completedCommands.contains(commandNumber)) - { - qWarning("Monotone::getReturnCode: no return code for %d", commandNumber); - return -1; - } - - int returnCode = completedCommands.value(commandNumber); - // FIXME: shall we really remove this one here? I.e. isCommandFinished() - // will return false if it is called afterwards - completedCommands.remove(commandNumber); - return returnCode; + isProcessingData = false; } -bool Monotone::isCommandFinished(int commandNumber) const -{ - return completedCommands.contains(commandNumber); -} - bool Monotone::runCommand(const QString & path, const QStringList & params, QString & output) { QProcess proc; ============================================================ --- guitone/src/monotone/Monotone.h acbb4071e26433ba787703de88d9a8b67425eaa5 +++ guitone/src/monotone/Monotone.h 44a8a36ea6ac22617789013e6fc7a71b9ed4db07 @@ -22,9 +22,6 @@ #define MONOTONE_H #include -#include -#include -#include class Monotone : public QObject { @@ -39,7 +36,7 @@ class Monotone : public QObject static const QString RequiredProgramVersion; static const QString RequiredInterfaceVersion; static const int StdioBufferSize; - static const int WaitForMonotoneStart; + static const int TimeoutWaitForOtherCommand; bool loadWorkspace(QString); bool loadDatabase(QString); @@ -48,16 +45,15 @@ class Monotone : public QObject QString getNormalizedWorkspacePath() const; QString getDatabaseFilePath() const; - bool triggerCommand(const QStringList &, int &); - bool triggerCommand(const QStringList &, const QStringList &, int &); + bool triggerCommand(const QStringList &); + bool triggerCommand(const QStringList &, const QStringList &); bool executeCommand(const QStringList &, int &); bool executeCommand(const QStringList &, const QStringList &, int &); - QString getDecodedData(int); - QByteArray getRawData(int); - int getReturnCode(int); - bool isCommandFinished(int) const; + QString getDecodedDataAndReset(); + QByteArray getRawDataAndReset(); + void reset(); private: enum Mode { Workspace, Database } mode; @@ -68,27 +64,29 @@ class Monotone : public QObject bool setupNewCommand(); void setupNewProcess(); void shutdownCurrentProcess(); - int writeStdin(const QStringList &, const QStringList &); + void writeStdin(const QStringList &, const QStringList &); + bool readAndParseStdout(int &); + bool timeout; QByteArray input; - QMap output; - QMap completedCommands; - int commandCounter; + QByteArray output; + QString commandLine; + bool isProcessingData; bool isCleanExit; static Monotone* instance; QProcess * process; QString workDir; QString databaseFile; QString mtnBinaryPath; - QMutex lock; private slots: - void readAndParseStdout(); + void readAndProcessCommand(); void processFinished(int, QProcess::ExitStatus); void processError(QProcess::ProcessError); - + void timedOut(); + signals: - void commandFinished(); + void commandFinished(int); void criticalError(const QString &); }; ============================================================ --- guitone/src/view/dialogs/GenerateKeypair.cpp c28127273be56000aa5799437874a633c408b2a0 +++ guitone/src/view/dialogs/GenerateKeypair.cpp b01d54d34c43b82259dd4015fa7896cb57cbbb7c @@ -59,13 +59,13 @@ void GenerateKeypair::accept() return; } - int commandNumber; QStringList cmd; cmd << "genkey" << lineKeyName->text() << lineKeyPasswd->text(); Monotone * mtn = Monotone::singleton(); - if (!mtn->executeCommand(cmd, commandNumber)) + int retCode; + if (!mtn->executeCommand(cmd, retCode)) { QMessageBox::critical( this, @@ -76,18 +76,17 @@ void GenerateKeypair::accept() return; } - QString msg = mtn->getDecodedData(commandNumber); - - if (mtn->getReturnCode(commandNumber) > 0) + if (retCode != 0) { QMessageBox::critical( this, tr("Error creating keypair"), - tr("There was an error creating the keypair:\n%1").arg(msg), + tr("There was an error creating the keypair:\n%1").arg(mtn->getDecodedDataAndReset()), QMessageBox::Ok, 0, 0 ); return; } + mtn->reset(); done(QDialog::Accepted); } ============================================================ --- guitone/src/view/dialogs/RevisionManifest.cpp 87c413184dee30b528d784b2f67d448d45f10ff7 +++ guitone/src/view/dialogs/RevisionManifest.cpp 8d8d61614f41b1de70cb302354e0153170668cd8 @@ -103,15 +103,13 @@ void RevisionManifest::openFile(const QM if (entry->is_directory) return; Monotone * mtn = Monotone::singleton(); - - int commandNumber; QStringList cmd; cmd << "get_file" << entry->hash; - - if (!mtn->executeCommand(cmd, commandNumber) || - mtn->getReturnCode(commandNumber) > 0) + + int retCode; + if (!mtn->executeCommand(cmd, retCode)) { - qCritical("RevisionManifest::openFile: cannot execute get_file"); + qCritical("RevisionManifest::openFile: cannot execute get_file (return code %d)", retCode); return; } @@ -144,7 +142,7 @@ void RevisionManifest::openFile(const QM // FIXME: we must write the original QByteArray here instead of messing // around with strings, but for this the Monotone wrapper has - once again - // to be rewritten first... - file.write(mtn->getRawData(commandNumber)); + file.write(mtn->getRawDataAndReset()); file.close(); if (!Platform::openFile(this, tempPath))