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@94
|
97 m_process->write(ptr, size);
|
c@94
|
98
|
c@94
|
99 std::vector<char> buffer;
|
c@94
|
100 bool complete = false;
|
c@94
|
101
|
c@94
|
102 while (!complete) {
|
c@94
|
103
|
c@94
|
104 qint64 byteCount = m_process->bytesAvailable();
|
c@94
|
105
|
c@101
|
106 if (!byteCount) {
|
c@108
|
107 std::cerr << "waiting for data from server..." << std::endl;
|
c@101
|
108 m_process->waitForReadyRead(1000);
|
c@94
|
109 if (m_process->state() == QProcess::NotRunning) {
|
c@94
|
110 std::cerr << "ERROR: Subprocess exited: Load failed" << std::endl;
|
c@94
|
111 throw std::runtime_error("Piper server exited unexpectedly");
|
c@94
|
112 }
|
c@94
|
113 } else {
|
c@94
|
114 size_t formerSize = buffer.size();
|
c@94
|
115 buffer.resize(formerSize + byteCount);
|
c@94
|
116 m_process->read(buffer.data() + formerSize, byteCount);
|
c@94
|
117 complete = m_completenessChecker->isComplete(buffer);
|
c@94
|
118 }
|
c@94
|
119 }
|
c@94
|
120
|
c@94
|
121 return buffer;
|
c@94
|
122 }
|
c@94
|
123
|
c@94
|
124 private:
|
c@94
|
125 MessageCompletenessChecker *m_completenessChecker; //!!! I don't own this (currently)
|
c@94
|
126 QProcess *m_process; // I own this
|
c@100
|
127 QMutex m_mutex;
|
c@94
|
128 };
|
c@94
|
129
|
c@94
|
130 }
|
c@94
|
131 }
|
c@94
|
132
|
c@94
|
133 #endif
|