cannam@111
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
c@118
|
2 /*
|
c@118
|
3 Piper C++
|
c@118
|
4
|
c@118
|
5 An API for audio analysis and feature extraction plugins.
|
c@118
|
6
|
c@118
|
7 Centre for Digital Music, Queen Mary, University of London.
|
c@118
|
8 Copyright 2006-2016 Chris Cannam and QMUL.
|
c@118
|
9
|
c@118
|
10 Permission is hereby granted, free of charge, to any person
|
c@118
|
11 obtaining a copy of this software and associated documentation
|
c@118
|
12 files (the "Software"), to deal in the Software without
|
c@118
|
13 restriction, including without limitation the rights to use, copy,
|
c@118
|
14 modify, merge, publish, distribute, sublicense, and/or sell copies
|
c@118
|
15 of the Software, and to permit persons to whom the Software is
|
c@118
|
16 furnished to do so, subject to the following conditions:
|
c@118
|
17
|
c@118
|
18 The above copyright notice and this permission notice shall be
|
c@118
|
19 included in all copies or substantial portions of the Software.
|
c@118
|
20
|
c@118
|
21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
c@118
|
22 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
c@118
|
23 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
c@118
|
24 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
|
c@118
|
25 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
c@118
|
26 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
c@118
|
27 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
c@118
|
28
|
c@118
|
29 Except as contained in this notice, the names of the Centre for
|
c@118
|
30 Digital Music; Queen Mary, University of London; and Chris Cannam
|
c@118
|
31 shall not be used in advertising or otherwise to promote the sale,
|
c@118
|
32 use or other dealings in this Software without prior written
|
c@118
|
33 authorization.
|
c@118
|
34 */
|
cannam@111
|
35
|
c@94
|
36 #ifndef PIPER_PROCESS_QT_TRANSPORT_H
|
c@94
|
37 #define PIPER_PROCESS_QT_TRANSPORT_H
|
c@94
|
38
|
c@94
|
39 #include "SynchronousTransport.h"
|
c@94
|
40
|
c@94
|
41 #include <QProcess>
|
c@94
|
42 #include <QString>
|
c@100
|
43 #include <QMutex>
|
c@94
|
44
|
c@94
|
45 #include <iostream>
|
c@94
|
46
|
c@124
|
47 //#define DEBUG_TRANSPORT 1
|
c@124
|
48
|
c@97
|
49 namespace piper_vamp {
|
c@97
|
50 namespace client {
|
c@94
|
51
|
c@100
|
52 /**
|
c@100
|
53 * A SynchronousTransport implementation that spawns a sub-process
|
c@100
|
54 * using Qt's QProcess abstraction and talks to it via stdin/stdout
|
c@100
|
55 * channels. Calls are completely serialized; the protocol only
|
c@100
|
56 * supports one call in process at a time, and therefore the transport
|
c@125
|
57 * only allows one at a time.
|
c@125
|
58 *
|
c@125
|
59 * This class is thread-safe, but in practice you can only use it from
|
c@125
|
60 * within a single thread, because the underlying QProcess does not
|
c@125
|
61 * support switching threads.
|
c@100
|
62 */
|
c@94
|
63 class ProcessQtTransport : public SynchronousTransport
|
c@94
|
64 {
|
c@94
|
65 public:
|
c@134
|
66 ProcessQtTransport(std::string processName,
|
c@134
|
67 std::string formatArg,
|
c@134
|
68 LogCallback *logger) : // logger may be nullptr for cerr
|
c@134
|
69 m_logger(logger),
|
c@126
|
70 m_completenessChecker(0),
|
c@126
|
71 m_crashed(false) {
|
c@119
|
72
|
c@94
|
73 m_process = new QProcess();
|
c@94
|
74 m_process->setReadChannel(QProcess::StandardOutput);
|
c@94
|
75 m_process->setProcessChannelMode(QProcess::ForwardedErrorChannel);
|
cannam@114
|
76
|
c@119
|
77 m_process->start(QString::fromStdString(processName),
|
c@124
|
78 { QString::fromStdString(formatArg) });
|
cannam@114
|
79
|
c@94
|
80 if (!m_process->waitForStarted()) {
|
cannam@113
|
81 if (m_process->state() == QProcess::NotRunning) {
|
cannam@113
|
82 QProcess::ProcessError err = m_process->error();
|
cannam@113
|
83 if (err == QProcess::FailedToStart) {
|
c@134
|
84 log("Unable to start server process " + processName);
|
cannam@113
|
85 } else if (err == QProcess::Crashed) {
|
c@134
|
86 log("Server process " + processName + " crashed on startup");
|
cannam@113
|
87 } else {
|
c@134
|
88 QString e = QString("%1").arg(err);
|
c@134
|
89 log("Server process " + processName +
|
c@134
|
90 " failed on startup with error code " + e.toStdString());
|
cannam@113
|
91 }
|
cannam@113
|
92 delete m_process;
|
cannam@113
|
93 m_process = nullptr;
|
cannam@111
|
94 }
|
c@94
|
95 }
|
c@134
|
96
|
c@134
|
97 if (m_process) {
|
c@134
|
98 log("Server process " + processName + " started OK");
|
c@134
|
99 }
|
c@94
|
100 }
|
c@94
|
101
|
c@94
|
102 ~ProcessQtTransport() {
|
c@94
|
103 if (m_process) {
|
c@94
|
104 if (m_process->state() != QProcess::NotRunning) {
|
c@118
|
105 m_process->closeWriteChannel();
|
c@94
|
106 m_process->waitForFinished(200);
|
c@94
|
107 m_process->close();
|
c@94
|
108 m_process->waitForFinished();
|
c@134
|
109 log("Server process exited normally");
|
c@94
|
110 }
|
c@94
|
111 delete m_process;
|
c@94
|
112 }
|
c@94
|
113 }
|
c@94
|
114
|
c@94
|
115 void
|
cannam@111
|
116 setCompletenessChecker(MessageCompletenessChecker *checker) override {
|
c@94
|
117 m_completenessChecker = checker;
|
c@94
|
118 }
|
c@94
|
119
|
c@94
|
120 bool
|
c@94
|
121 isOK() const override {
|
c@126
|
122 return (m_process != nullptr) && !m_crashed;
|
c@94
|
123 }
|
c@94
|
124
|
c@94
|
125 std::vector<char>
|
c@134
|
126 call(const char *ptr, size_t size, std::string type, bool slow) override {
|
c@94
|
127
|
c@118
|
128 QMutexLocker locker(&m_mutex);
|
c@118
|
129
|
c@94
|
130 if (!m_completenessChecker) {
|
c@134
|
131 log("call: No completeness checker set on transport");
|
c@94
|
132 throw std::logic_error("No completeness checker set on transport");
|
c@94
|
133 }
|
c@126
|
134 if (!isOK()) {
|
c@134
|
135 log("call: Transport is not OK");
|
c@126
|
136 throw std::logic_error("Transport is not OK");
|
c@126
|
137 }
|
c@94
|
138
|
c@124
|
139 #ifdef DEBUG_TRANSPORT
|
c@115
|
140 std::cerr << "writing " << size << " bytes to server" << std::endl;
|
c@124
|
141 #endif
|
c@94
|
142 m_process->write(ptr, size);
|
c@126
|
143 m_process->waitForBytesWritten();
|
c@94
|
144
|
c@94
|
145 std::vector<char> buffer;
|
c@94
|
146 bool complete = false;
|
c@94
|
147
|
c@94
|
148 while (!complete) {
|
c@94
|
149
|
c@94
|
150 qint64 byteCount = m_process->bytesAvailable();
|
c@94
|
151
|
c@118
|
152 if (!byteCount) {
|
c@124
|
153 #ifdef DEBUG_TRANSPORT
|
c@118
|
154 std::cerr << "waiting for data from server..." << std::endl;
|
c@124
|
155 #endif
|
c@126
|
156 if (slow) {
|
c@126
|
157 m_process->waitForReadyRead(1000);
|
c@126
|
158 } else {
|
c@126
|
159 #ifdef _WIN32
|
c@126
|
160 // This is most unsatisfactory -- if we give a non-zero
|
c@126
|
161 // arg here, then we end up sleeping way beyond the arrival
|
c@126
|
162 // of the data to read -- can end up using less than 10%
|
c@126
|
163 // CPU during processing which is crazy. So for Windows
|
c@126
|
164 // only, we busy-wait during "fast" calls. It works out
|
c@126
|
165 // much faster in the end. Could do with a simpler native
|
c@126
|
166 // blocking API really.
|
c@126
|
167 m_process->waitForReadyRead(0);
|
c@126
|
168 #else
|
c@126
|
169 m_process->waitForReadyRead(100);
|
c@126
|
170 #endif
|
c@126
|
171 }
|
c@126
|
172 if (m_process->state() == QProcess::NotRunning &&
|
c@126
|
173 // don't give up until we've read all that's been buffered!
|
c@126
|
174 !m_process->bytesAvailable()) {
|
c@115
|
175 QProcess::ProcessError err = m_process->error();
|
c@115
|
176 if (err == QProcess::Crashed) {
|
c@134
|
177 log("Server crashed during " + type + " request");
|
c@115
|
178 } else {
|
c@134
|
179 QString e = QString("%1").arg(err);
|
c@134
|
180 log("Server failed during " + type
|
c@134
|
181 + " request with error code " + e.toStdString());
|
c@115
|
182 }
|
c@126
|
183 m_crashed = true;
|
c@121
|
184 throw ServerCrashed();
|
c@94
|
185 }
|
c@94
|
186 } else {
|
c@94
|
187 size_t formerSize = buffer.size();
|
c@94
|
188 buffer.resize(formerSize + byteCount);
|
c@94
|
189 m_process->read(buffer.data() + formerSize, byteCount);
|
c@94
|
190 complete = m_completenessChecker->isComplete(buffer);
|
c@94
|
191 }
|
c@94
|
192 }
|
c@94
|
193
|
c@94
|
194 return buffer;
|
c@94
|
195 }
|
c@94
|
196
|
c@94
|
197 private:
|
c@134
|
198 LogCallback *m_logger;
|
c@94
|
199 MessageCompletenessChecker *m_completenessChecker; //!!! I don't own this (currently)
|
c@94
|
200 QProcess *m_process; // I own this
|
c@100
|
201 QMutex m_mutex;
|
c@126
|
202 bool m_crashed;
|
c@134
|
203
|
c@134
|
204 void log(std::string message) const {
|
c@134
|
205 if (m_logger) m_logger->log(message);
|
c@134
|
206 else std::cerr << message << std::endl;
|
c@134
|
207 }
|
c@94
|
208 };
|
c@94
|
209
|
c@94
|
210 }
|
c@94
|
211 }
|
c@94
|
212
|
c@94
|
213 #endif
|