# # # patch "guitone/res/i18n/guitone_de.ts" # from [ac2a0f08d44b8a489160fbf170a8e7aae07f2702] # to [64fec5387de467d2a14bcb25ce60076264750b0e] # # patch "guitone/src/model/ContentDiff.cpp" # from [ff90490d569d7e0ecdf52720a47172a39ee6310e] # to [3966a4c4626f4e44043f1cefd986b3b8f03bd498] # # patch "guitone/src/model/GetFile.cpp" # from [cca2f1587219c6967e807541bf215cef57968b98] # to [847161c06efe93bed700432f94b5e358b8d62ebf] # # patch "guitone/src/model/MonotoneDelegate.cpp" # from [7717d175700657e27fe81db82595a0bfb6469db7] # to [a199c4fbcb62aba28a5ce3f8dac52923337a9781] # # patch "guitone/src/model/MonotoneDelegate.h" # from [61fc188fc86544101896e814b779003e670dc2b7] # to [63b9b9a1f636c8e6cded8e14250d90cb8c307628] # # patch "guitone/src/monotone/Monotone.cpp" # from [2b27c8a0d3a2b569cb7f5924f94d8a4a9a3a6312] # to [882785c94f5ead9cd672391f7c7b62f0455efa0f] # # patch "guitone/src/monotone/Monotone.h" # from [44a8a36ea6ac22617789013e6fc7a71b9ed4db07] # to [86adeb6b5c05935100b714ef9659d1e4fa27f4b9] # # patch "guitone/src/view/dialogs/GenerateKeypair.cpp" # from [b01d54d34c43b82259dd4015fa7896cb57cbbb7c] # to [c28127273be56000aa5799437874a633c408b2a0] # # patch "guitone/src/view/dialogs/RevisionManifest.cpp" # from [b4676e478d3937d42c6b94e17a79abf62bc5c460] # to [124c563333148bd821655d05486a6eaa23e813b5] # ============================================================ --- guitone/res/i18n/guitone_de.ts ac2a0f08d44b8a489160fbf170a8e7aae07f2702 +++ guitone/res/i18n/guitone_de.ts 64fec5387de467d2a14bcb25ce60076264750b0e @@ -128,22 +128,22 @@ ContentDiff - + %1 (binary) %1 (binär) - + %1 (%2 hunks) %1 (%2 Bereiche) - + Line Zeile - + File/Content Datei/Inhalt @@ -257,12 +257,12 @@ Die eingegebenen Passwörter stimmen nicht überein. - + Error creating keypair Fehler beim Erzeugen des Schlüsselpaares - + There was an error creating the keypair: %1 Bei der Erzeugung des Schlüsselpaares ist ein Fehler aufgetreten: @@ -282,12 +282,12 @@ GetFile - + Line Zeile - + Content Inhalt @@ -949,7 +949,7 @@ monotone gab zurück: %2 - + The monotone process exited unexpectedly (return code %1). Please reconfigure the path to the monotone binary in the Preferences dialog or check if the version of the database you try to load matches the monotone version you are using. monotone returned: @@ -1118,22 +1118,22 @@ monotone gab zurück: RevisionManifest - + Error Fehler - + Unable to open files on your platform - please contact the author about this problem. Kann keine Dateien auf Ihrer Plattform öffnen - bitte kontaktieren Sie den Autor über dieses Problem. - + Please close this message to remove the temporary file. Bitte schliessen Sie diesen Dialog, um die temporäre Datei zu entfernen. - + Information Information ============================================================ --- guitone/src/model/ContentDiff.cpp ff90490d569d7e0ecdf52720a47172a39ee6310e +++ guitone/src/model/ContentDiff.cpp 3966a4c4626f4e44043f1cefd986b3b8f03bd498 @@ -63,16 +63,18 @@ bool ContentDiff::readDiff( if (leftRevision.length() == 0) { - int retCode; - - if (!mtn->executeCommand(QStringList() << "get_base_revision_id", retCode) - || retCode != 0) + int commandNumber; + QStringList cmd; + cmd << "get_base_revision_id"; + + if (!mtn->executeCommand(cmd, commandNumber) || + mtn->getReturnCode(commandNumber) > 0) { qWarning("ContentDiff::readDiff: could not execute get_base_revision_id"); return false; } - leftRevision = mtn->getDecodedDataAndReset(); + leftRevision = mtn->getDecodedData(commandNumber); leftRevision.chop(1); } ============================================================ --- guitone/src/model/GetFile.cpp cca2f1587219c6967e807541bf215cef57968b98 +++ guitone/src/model/GetFile.cpp 847161c06efe93bed700432f94b5e358b8d62ebf @@ -37,17 +37,18 @@ bool GetFile::readFileByName(QString fil { Monotone * mtn = Monotone::singleton(); + int commandNumber; QStringList cmd; cmd << "get_base_revision_id"; - int retCode; - if (!mtn->executeCommand(cmd, retCode) || retCode != 0) + if (!mtn->executeCommand(cmd, commandNumber) || + mtn->getReturnCode(commandNumber) > 0) { qWarning("GetFile::readFileByName: could not execute get_base_revision_id"); return false; } - QString baseRevision = mtn->getDecodedDataAndReset(); + QString baseRevision = mtn->getDecodedData(commandNumber); baseRevision.chop(1); return readFileByName(fileName, baseRevision); ============================================================ --- guitone/src/model/MonotoneDelegate.cpp 7717d175700657e27fe81db82595a0bfb6469db7 +++ guitone/src/model/MonotoneDelegate.cpp a199c4fbcb62aba28a5ce3f8dac52923337a9781 @@ -23,8 +23,10 @@ #include -MonotoneDelegate::MonotoneDelegate(AutomateCommand * cmd) : cmdModel(cmd) -{} +MonotoneDelegate::MonotoneDelegate(AutomateCommand * cmd) + : cmdModel(cmd), commandNumber(-1) +{ +} MonotoneDelegate::~MonotoneDelegate() {} @@ -38,36 +40,40 @@ bool MonotoneDelegate::triggerCommand(co Monotone * mtn = Monotone::singleton(); connect( - mtn, SIGNAL(commandFinished(int)), - this, SLOT(commandFinished(int)) + mtn, SIGNAL(commandFinished()), + this, SLOT(commandFinished()) ); qApp->setOverrideCursor(Qt::WaitCursor); - return mtn->triggerCommand(cmd, opts); + return mtn->triggerCommand(cmd, opts, commandNumber); } -void MonotoneDelegate::commandFinished(int retCode) +void MonotoneDelegate::commandFinished() { Monotone * mtn = Monotone::singleton(); - + + // check if this is the command which is interesting for us + if (mtn->isCommandFinished(commandNumber)) return; + disconnect( - mtn, SIGNAL(commandFinished(int)), - this, SLOT(commandFinished(int)) + mtn, SIGNAL(commandFinished()), + this, SLOT(commandFinished()) ); // FIXME: does any of our models ever need the raw data? - cmdModel->setAutomateData(mtn->getDecodedDataAndReset()); + cmdModel->setAutomateData(mtn->getDecodedData(commandNumber)); + int returnCode = mtn->getReturnCode(commandNumber); - if (retCode == 0) + if (returnCode == 0) { cmdModel->parseOutput(); } else { - if (!cmdModel->handleError(retCode)) + if (!cmdModel->handleError(returnCode)) { - qCritical("MonotoneDelegate::commandFinished: couldn't handle error %d", retCode); + qCritical("MonotoneDelegate::commandFinished: couldn't handle error %d", returnCode); } } ============================================================ --- guitone/src/model/MonotoneDelegate.h 61fc188fc86544101896e814b779003e670dc2b7 +++ guitone/src/model/MonotoneDelegate.h 63b9b9a1f636c8e6cded8e14250d90cb8c307628 @@ -37,9 +37,10 @@ private: private: AutomateCommand * cmdModel; + int commandNumber; private slots: - void commandFinished(int); + void commandFinished(); }; #endif ============================================================ --- guitone/src/monotone/Monotone.cpp 2b27c8a0d3a2b569cb7f5924f94d8a4a9a3a6312 +++ guitone/src/monotone/Monotone.cpp 882785c94f5ead9cd672391f7c7b62f0455efa0f @@ -85,6 +85,8 @@ #include #include #include +#include +#include Monotone* Monotone::instance = 0; @@ -98,20 +100,21 @@ const int Monotone::StdioBufferSize = 50 const QString Monotone::RequiredInterfaceVersion = "4.0"; const int Monotone::StdioBufferSize = 50 * 1024 * 1024; -const int Monotone::TimeoutWaitForOtherCommand = 5000; // milliseconds +const int Monotone::WaitForMonotoneStart = 15000; // milliseconds Monotone::Monotone(QObject * parent) : QObject(parent), process(0) { - // true if a command is currently processed - isProcessingData = false; + // the count of the commands fired at the current process instance + commandCounter = -1; // 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 = ""; } @@ -177,7 +180,8 @@ void Monotone::shutdownCurrentProcess() if (process) { isCleanExit = true; - isProcessingData = false; + completedCommands.clear(); + commandCounter = -1; // close the pipes process->close(); @@ -188,6 +192,21 @@ 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; } } @@ -207,11 +226,17 @@ void Monotone::setupNewProcess() this, SLOT(processFinished(int, QProcess::ExitStatus)) ); - // check if there occurs an startup error + // monitor process errors 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) { @@ -228,8 +253,11 @@ void Monotone::setupNewProcess() args << "--db" << databaseFile; } - qDebug("Executing %s %s", qPrintable(mtnBinaryPath), qPrintable(args.join(" "))); - + qDebug("Monotone::setupNewProcess: %s %s", + qPrintable(mtnBinaryPath), + qPrintable(args.join(" ")) + ); + process->start(mtnBinaryPath, args); } @@ -307,103 +335,54 @@ void Monotone::processFinished(int code, ); } -void Monotone::timedOut() +bool Monotone::executeCommand(const QStringList & command, int & commandNumber) { - 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, retCode); + return executeCommand(command, opts, commandNumber); } -bool Monotone::executeCommand(const QStringList & command, const QStringList & options, int & retCode) +bool Monotone::executeCommand(const QStringList & command, const QStringList & options, int & commandNumber) { - if (!setupNewCommand()) + if (process->state() != QProcess::Running && + !process->waitForStarted(WaitForMonotoneStart)) { - return false; + qDebug("Monotone::executeCommand: Process is not running"); + return false; } - writeStdin(command, options); - - bool parsed; - SignalWaiter waiter(process, SIGNAL(readyRead())); + commandNumber = writeStdin(command, options); + SignalWaiter waiter(this, SIGNAL(commandFinished(int, int))); 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 (!parsed); - + while (!isCommandFinished(commandNumber)); + return true; } -bool Monotone::triggerCommand(const QStringList & command) +bool Monotone::triggerCommand(const QStringList & command, int & commandNumber) { - return triggerCommand(command, QStringList()); + return triggerCommand(command, QStringList(), commandNumber); } -bool Monotone::triggerCommand(const QStringList & command, const QStringList & options) +bool Monotone::triggerCommand(const QStringList & command, const QStringList & options, int & commandNumber) { - if (!setupNewCommand()) + if (process->state() != QProcess::Running && + !process->waitForStarted(WaitForMonotoneStart)) { - return false; + qDebug("Monotone::triggerCommand: Process is not running"); + return false; } - - // read & parse mtn's output as soon as it gets available - connect( - process, SIGNAL(readyRead()), - this, SLOT(readAndProcessCommand()) - ); - - writeStdin(command, options); - + + commandNumber = writeStdin(command, options); return true; } -void Monotone::writeStdin(const QStringList & command, const QStringList & options) +int Monotone::writeStdin(const QStringList & command, const QStringList & options) { - commandLine = ""; + QString commandLine = ""; if (options.size() > 0) { @@ -451,31 +430,16 @@ void Monotone::writeStdin(const QStringL QTextStream streamStdIn(process); streamStdIn << commandLine; streamStdIn.flush(); + + return ++commandCounter; } -void Monotone::readAndProcessCommand() +void Monotone::readAndParseStdout() { // don't do anything if there is nothing to read if (process->bytesAvailable() == 0) return; - 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) -{ + // append any new output and try to parse it input.append(process->readAllStandardOutput()); while (input.size() > 0) @@ -485,12 +449,25 @@ bool Monotone::readAndParseStdout(int & // if the chunk is not yet complete, try again later if (!parser.parse()) { - return false; + qWarning("Monotone::readAndParseStdout: Contents invalid."); + return; } input = input.mid(parser.getProcessedBytes()); - output.append(parser.getPayload()); + int commandNumber = parser.getCommandNumber(); + int returnCode = parser.getErrorCode(); + // 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') { @@ -498,35 +475,61 @@ bool Monotone::readAndParseStdout(int & { qWarning("Monotone::readAndParseStdout: input left: %s", input.data()); } - retCode = parser.getErrorCode(); - // command successfully parsed - return true; + + // remember the return value of the command + completedCommands.insert(commandNumber, returnCode); + emit commandFinished(); + return; } } // if this was not the last output, we need to wait for more data - return false; + qWarning("Monotone::readAndParseStdout: Contents incomplete."); + return; } -QByteArray Monotone::getRawDataAndReset() +QByteArray Monotone::getRawData(int commandNumber) { - QByteArray data(output); - reset(); - return data; + QMutex lock; + QMutexLocker locker(&lock); + lock.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; } -QString Monotone::getDecodedDataAndReset() +QString Monotone::getDecodedData(int commandNumber) { - QString data = QString::fromUtf8(output.data()); - reset(); - return data; + return QString::fromUtf8(getRawData(commandNumber).data()); } -void Monotone::reset() +int Monotone::getReturnCode(int commandNumber) { - isProcessingData = false; + 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; } +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 44a8a36ea6ac22617789013e6fc7a71b9ed4db07 +++ guitone/src/monotone/Monotone.h 86adeb6b5c05935100b714ef9659d1e4fa27f4b9 @@ -22,6 +22,8 @@ #define MONOTONE_H #include +#include +#include class Monotone : public QObject { @@ -36,7 +38,7 @@ class Monotone : public QObject static const QString RequiredProgramVersion; static const QString RequiredInterfaceVersion; static const int StdioBufferSize; - static const int TimeoutWaitForOtherCommand; + static const int WaitForMonotoneStart; bool loadWorkspace(QString); bool loadDatabase(QString); @@ -45,15 +47,16 @@ class Monotone : public QObject QString getNormalizedWorkspacePath() const; QString getDatabaseFilePath() const; - bool triggerCommand(const QStringList &); - bool triggerCommand(const QStringList &, const QStringList &); + bool triggerCommand(const QStringList &, int &); + bool triggerCommand(const QStringList &, const QStringList &, int &); bool executeCommand(const QStringList &, int &); bool executeCommand(const QStringList &, const QStringList &, int &); - QString getDecodedDataAndReset(); - QByteArray getRawDataAndReset(); - void reset(); + QString getDecodedData(int); + QByteArray getRawData(int); + int getReturnCode(int); + bool isCommandFinished(int) const; private: enum Mode { Workspace, Database } mode; @@ -64,14 +67,12 @@ class Monotone : public QObject bool setupNewCommand(); void setupNewProcess(); void shutdownCurrentProcess(); - void writeStdin(const QStringList &, const QStringList &); - bool readAndParseStdout(int &); + int writeStdin(const QStringList &, const QStringList &); - bool timeout; QByteArray input; - QByteArray output; - QString commandLine; - bool isProcessingData; + QMap output; + QMap completedCommands; + int commandCounter; bool isCleanExit; static Monotone* instance; QProcess * process; @@ -80,13 +81,12 @@ class Monotone : public QObject QString mtnBinaryPath; private slots: - void readAndProcessCommand(); + void readAndParseStdout(); void processFinished(int, QProcess::ExitStatus); void processError(QProcess::ProcessError); - void timedOut(); - + signals: - void commandFinished(int); + void commandFinished(); void criticalError(const QString &); }; ============================================================ --- guitone/src/view/dialogs/GenerateKeypair.cpp b01d54d34c43b82259dd4015fa7896cb57cbbb7c +++ guitone/src/view/dialogs/GenerateKeypair.cpp c28127273be56000aa5799437874a633c408b2a0 @@ -59,13 +59,13 @@ void GenerateKeypair::accept() return; } + int commandNumber; QStringList cmd; cmd << "genkey" << lineKeyName->text() << lineKeyPasswd->text(); Monotone * mtn = Monotone::singleton(); - int retCode; - if (!mtn->executeCommand(cmd, retCode)) + if (!mtn->executeCommand(cmd, commandNumber)) { QMessageBox::critical( this, @@ -76,17 +76,18 @@ void GenerateKeypair::accept() return; } - if (retCode != 0) + QString msg = mtn->getDecodedData(commandNumber); + + if (mtn->getReturnCode(commandNumber) > 0) { QMessageBox::critical( this, tr("Error creating keypair"), - tr("There was an error creating the keypair:\n%1").arg(mtn->getDecodedDataAndReset()), + tr("There was an error creating the keypair:\n%1").arg(msg), QMessageBox::Ok, 0, 0 ); return; } - mtn->reset(); done(QDialog::Accepted); } ============================================================ --- guitone/src/view/dialogs/RevisionManifest.cpp b4676e478d3937d42c6b94e17a79abf62bc5c460 +++ guitone/src/view/dialogs/RevisionManifest.cpp 124c563333148bd821655d05486a6eaa23e813b5 @@ -104,13 +104,16 @@ void RevisionManifest::openFile(const QM } Monotone * mtn = Monotone::singleton(); + + int commandNumber; + int returnCode; QStringList cmd; cmd << "get_file" << entry->hash; - - int retCode; - if (!mtn->executeCommand(cmd, retCode)) + + if (!mtn->executeCommand(cmd, commandNumber) || + mtn->getReturnCode(commandNumber) > 0) { - qCritical("RevisionManifest::openFile: cannot execute get_file (return code %d)", retCode); + qCritical("RevisionManifest::openFile: cannot execute get_file"); return; } @@ -143,7 +146,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->getRawDataAndReset()); + file.write(mtn->getRawData(commandNumber)); file.close(); if (!Platform::openFile(this, tempPath))