annotate vamp-client/ProcessQtTransport.h @ 116:d15cb1151d76

Add JSON support directly to the server. Had hoped to avoid this (using Capnp as canonical in the server and then converting externally as necessary) but it's just too useful for debugging purposes when bundled with client app
author Chris Cannam <c.cannam@qmul.ac.uk>
date Thu, 27 Oct 2016 11:39:41 +0100
parents 5a716f08e4be
children ff3fd8d1b2dc
rev   line source
cannam@111 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
cannam@111 2
c@94 3 #ifndef PIPER_PROCESS_QT_TRANSPORT_H
c@94 4 #define PIPER_PROCESS_QT_TRANSPORT_H
c@94 5
c@94 6 #include "SynchronousTransport.h"
c@94 7
c@94 8 #include <QProcess>
c@94 9 #include <QString>
c@100 10 #include <QMutex>
c@94 11
c@94 12 #include <iostream>
c@94 13
c@97 14 namespace piper_vamp {
c@97 15 namespace client {
c@94 16
c@100 17 /**
c@100 18 * A SynchronousTransport implementation that spawns a sub-process
c@100 19 * using Qt's QProcess abstraction and talks to it via stdin/stdout
c@100 20 * channels. Calls are completely serialized; the protocol only
c@100 21 * supports one call in process at a time, and therefore the transport
c@100 22 * only allows one at a time. This class is thread-safe because it
c@100 23 * serializes explicitly using a mutex.
c@100 24 */
c@94 25 class ProcessQtTransport : public SynchronousTransport
c@94 26 {
c@94 27 public:
c@101 28 ProcessQtTransport(std::string processName) :
c@94 29 m_completenessChecker(0) {
c@94 30 m_process = new QProcess();
c@94 31 m_process->setReadChannel(QProcess::StandardOutput);
c@94 32 m_process->setProcessChannelMode(QProcess::ForwardedErrorChannel);
cannam@114 33 QString name(QString::fromStdString(processName));
cannam@114 34
cannam@114 35 // The second argument here is vital, otherwise we get a
cannam@114 36 // different start() overload which parses all command args
cannam@114 37 // out of its first argument only and therefore fails when
cannam@114 38 // name has a space in it. This is such a gotcha that Qt5.6
cannam@114 39 // even introduced a QT_NO_PROCESS_COMBINED_ARGUMENT_START
cannam@114 40 // build flag to disable that overload. Unfortunately I only
cannam@114 41 // discovered that after wasting almost a day on it.
cannam@114 42 m_process->start(name, QStringList());
cannam@114 43
c@94 44 if (!m_process->waitForStarted()) {
cannam@113 45 if (m_process->state() == QProcess::NotRunning) {
cannam@113 46 QProcess::ProcessError err = m_process->error();
cannam@113 47 if (err == QProcess::FailedToStart) {
cannam@113 48 std::cerr << "Unable to start server process "
cannam@113 49 << processName << std::endl;
cannam@113 50 } else if (err == QProcess::Crashed) {
cannam@113 51 std::cerr << "Server process " << processName
cannam@113 52 << " crashed on startup" << std::endl;
cannam@113 53 } else {
cannam@113 54 std::cerr << "Server process " << processName
cannam@113 55 << " failed on startup with error code "
cannam@113 56 << err << std::endl;
cannam@113 57 }
cannam@113 58 delete m_process;
cannam@113 59 m_process = nullptr;
cannam@111 60 }
c@94 61 }
c@94 62 }
c@94 63
c@94 64 ~ProcessQtTransport() {
c@94 65 if (m_process) {
c@94 66 if (m_process->state() != QProcess::NotRunning) {
c@94 67 m_process->closeWriteChannel();
c@94 68 m_process->waitForFinished(200);
c@94 69 m_process->close();
c@94 70 m_process->waitForFinished();
c@94 71 std::cerr << "server exited" << std::endl;
c@94 72 }
c@94 73 delete m_process;
c@94 74 }
c@94 75 }
c@94 76
c@94 77 void
cannam@111 78 setCompletenessChecker(MessageCompletenessChecker *checker) override {
c@94 79 //!!! ownership?
c@94 80 m_completenessChecker = checker;
c@94 81 }
c@94 82
c@94 83 bool
c@94 84 isOK() const override {
c@94 85 return m_process != nullptr;
c@94 86 }
c@94 87
c@94 88 std::vector<char>
c@94 89 call(const char *ptr, size_t size) override {
c@94 90
c@100 91 QMutexLocker locker(&m_mutex);
c@100 92
c@94 93 if (!m_completenessChecker) {
c@94 94 throw std::logic_error("No completeness checker set on transport");
c@94 95 }
c@94 96
c@115 97 std::cerr << "writing " << size << " bytes to server" << std::endl;
c@94 98 m_process->write(ptr, size);
c@94 99
c@94 100 std::vector<char> buffer;
c@94 101 bool complete = false;
c@94 102
c@94 103 while (!complete) {
c@94 104
c@94 105 qint64 byteCount = m_process->bytesAvailable();
c@94 106
c@101 107 if (!byteCount) {
c@108 108 std::cerr << "waiting for data from server..." << std::endl;
c@101 109 m_process->waitForReadyRead(1000);
c@94 110 if (m_process->state() == QProcess::NotRunning) {
c@115 111 QProcess::ProcessError err = m_process->error();
c@115 112 if (err == QProcess::Crashed) {
c@115 113 std::cerr << "Server crashed during request" << std::endl;
c@115 114 } else {
c@115 115 std::cerr << "Server failed during request with error code "
c@115 116 << err << std::endl;
c@115 117 }
c@115 118 //!!! + catch
c@94 119 throw std::runtime_error("Piper server exited unexpectedly");
c@94 120 }
c@94 121 } else {
c@94 122 size_t formerSize = buffer.size();
c@94 123 buffer.resize(formerSize + byteCount);
c@94 124 m_process->read(buffer.data() + formerSize, byteCount);
c@94 125 complete = m_completenessChecker->isComplete(buffer);
c@94 126 }
c@94 127 }
c@94 128
c@94 129 return buffer;
c@94 130 }
c@94 131
c@94 132 private:
c@94 133 MessageCompletenessChecker *m_completenessChecker; //!!! I don't own this (currently)
c@94 134 QProcess *m_process; // I own this
c@100 135 QMutex m_mutex;
c@94 136 };
c@94 137
c@94 138 }
c@94 139 }
c@94 140
c@94 141 #endif