cannam@111: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ cannam@111: c@94: #ifndef PIPER_PROCESS_QT_TRANSPORT_H c@94: #define PIPER_PROCESS_QT_TRANSPORT_H c@94: c@94: #include "SynchronousTransport.h" c@94: c@94: #include c@94: #include c@100: #include c@94: c@94: #include c@94: c@97: namespace piper_vamp { c@97: namespace client { c@94: c@100: /** c@100: * A SynchronousTransport implementation that spawns a sub-process c@100: * using Qt's QProcess abstraction and talks to it via stdin/stdout c@100: * channels. Calls are completely serialized; the protocol only c@100: * supports one call in process at a time, and therefore the transport c@100: * only allows one at a time. This class is thread-safe because it c@100: * serializes explicitly using a mutex. c@100: */ c@94: class ProcessQtTransport : public SynchronousTransport c@94: { c@94: public: c@101: ProcessQtTransport(std::string processName) : c@94: m_completenessChecker(0) { c@94: m_process = new QProcess(); c@94: m_process->setReadChannel(QProcess::StandardOutput); c@94: m_process->setProcessChannelMode(QProcess::ForwardedErrorChannel); cannam@114: QString name(QString::fromStdString(processName)); cannam@114: cannam@114: // The second argument here is vital, otherwise we get a cannam@114: // different start() overload which parses all command args cannam@114: // out of its first argument only and therefore fails when cannam@114: // name has a space in it. This is such a gotcha that Qt5.6 cannam@114: // even introduced a QT_NO_PROCESS_COMBINED_ARGUMENT_START cannam@114: // build flag to disable that overload. Unfortunately I only cannam@114: // discovered that after wasting almost a day on it. cannam@114: m_process->start(name, QStringList()); cannam@114: c@94: if (!m_process->waitForStarted()) { cannam@113: if (m_process->state() == QProcess::NotRunning) { cannam@113: QProcess::ProcessError err = m_process->error(); cannam@113: if (err == QProcess::FailedToStart) { cannam@113: std::cerr << "Unable to start server process " cannam@113: << processName << std::endl; cannam@113: } else if (err == QProcess::Crashed) { cannam@113: std::cerr << "Server process " << processName cannam@113: << " crashed on startup" << std::endl; cannam@113: } else { cannam@113: std::cerr << "Server process " << processName cannam@113: << " failed on startup with error code " cannam@113: << err << std::endl; cannam@113: } cannam@113: delete m_process; cannam@113: m_process = nullptr; cannam@111: } c@94: } c@94: } c@94: c@94: ~ProcessQtTransport() { c@94: if (m_process) { c@94: if (m_process->state() != QProcess::NotRunning) { c@94: m_process->closeWriteChannel(); c@94: m_process->waitForFinished(200); c@94: m_process->close(); c@94: m_process->waitForFinished(); c@94: std::cerr << "server exited" << std::endl; c@94: } c@94: delete m_process; c@94: } c@94: } c@94: c@94: void cannam@111: setCompletenessChecker(MessageCompletenessChecker *checker) override { c@94: //!!! ownership? c@94: m_completenessChecker = checker; c@94: } c@94: c@94: bool c@94: isOK() const override { c@94: return m_process != nullptr; c@94: } c@94: c@94: std::vector c@94: call(const char *ptr, size_t size) override { c@94: c@100: QMutexLocker locker(&m_mutex); c@100: c@94: if (!m_completenessChecker) { c@94: throw std::logic_error("No completeness checker set on transport"); c@94: } c@94: c@115: std::cerr << "writing " << size << " bytes to server" << std::endl; c@94: m_process->write(ptr, size); c@94: c@94: std::vector buffer; c@94: bool complete = false; c@94: c@94: while (!complete) { c@94: c@94: qint64 byteCount = m_process->bytesAvailable(); c@94: c@101: if (!byteCount) { c@108: std::cerr << "waiting for data from server..." << std::endl; c@101: m_process->waitForReadyRead(1000); c@94: if (m_process->state() == QProcess::NotRunning) { c@115: QProcess::ProcessError err = m_process->error(); c@115: if (err == QProcess::Crashed) { c@115: std::cerr << "Server crashed during request" << std::endl; c@115: } else { c@115: std::cerr << "Server failed during request with error code " c@115: << err << std::endl; c@115: } c@115: //!!! + catch c@94: throw std::runtime_error("Piper server exited unexpectedly"); c@94: } c@94: } else { c@94: size_t formerSize = buffer.size(); c@94: buffer.resize(formerSize + byteCount); c@94: m_process->read(buffer.data() + formerSize, byteCount); c@94: complete = m_completenessChecker->isComplete(buffer); c@94: } c@94: } c@94: c@94: return buffer; c@94: } c@94: c@94: private: c@94: MessageCompletenessChecker *m_completenessChecker; //!!! I don't own this (currently) c@94: QProcess *m_process; // I own this c@100: QMutex m_mutex; c@94: }; c@94: c@94: } c@94: } c@94: c@94: #endif