| c@125 | 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */ | 
| c@125 | 2 /* | 
| c@125 | 3     Piper C++ | 
| c@125 | 4 | 
| c@125 | 5     An API for audio analysis and feature extraction plugins. | 
| c@125 | 6 | 
| c@125 | 7     Centre for Digital Music, Queen Mary, University of London. | 
| c@125 | 8     Copyright 2006-2016 Chris Cannam and QMUL. | 
| c@125 | 9 | 
| c@125 | 10     Permission is hereby granted, free of charge, to any person | 
| c@125 | 11     obtaining a copy of this software and associated documentation | 
| c@125 | 12     files (the "Software"), to deal in the Software without | 
| c@125 | 13     restriction, including without limitation the rights to use, copy, | 
| c@125 | 14     modify, merge, publish, distribute, sublicense, and/or sell copies | 
| c@125 | 15     of the Software, and to permit persons to whom the Software is | 
| c@125 | 16     furnished to do so, subject to the following conditions: | 
| c@125 | 17 | 
| c@125 | 18     The above copyright notice and this permission notice shall be | 
| c@125 | 19     included in all copies or substantial portions of the Software. | 
| c@125 | 20 | 
| c@125 | 21     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | 
| c@125 | 22     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | 
| c@125 | 23     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | 
| c@125 | 24     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR | 
| c@125 | 25     ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF | 
| c@125 | 26     CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | 
| c@125 | 27     WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | 
| c@125 | 28 | 
| c@125 | 29     Except as contained in this notice, the names of the Centre for | 
| c@125 | 30     Digital Music; Queen Mary, University of London; and Chris Cannam | 
| c@125 | 31     shall not be used in advertising or otherwise to promote the sale, | 
| c@125 | 32     use or other dealings in this Software without prior written | 
| c@125 | 33     authorization. | 
| c@125 | 34 */ | 
| c@125 | 35 | 
| c@125 | 36 #include "vamp-json/VampJson.h" | 
| c@125 | 37 #include "vamp-capnp/VampnProto.h" | 
| c@125 | 38 #include "vamp-support/RequestOrResponse.h" | 
| c@125 | 39 #include "vamp-support/CountingPluginHandleMapper.h" | 
| c@125 | 40 #include "vamp-support/LoaderRequests.h" | 
| cannam@222 | 41 #include "vamp-support/RdfTypes.h" | 
| c@125 | 42 | 
| c@125 | 43 #include <iostream> | 
| c@125 | 44 #include <sstream> | 
| c@125 | 45 #include <stdexcept> | 
| c@125 | 46 | 
| c@125 | 47 #include <capnp/serialize.h> | 
| c@125 | 48 | 
| c@125 | 49 #include <map> | 
| c@125 | 50 #include <set> | 
| c@125 | 51 | 
| c@125 | 52 // pid for logging | 
| c@125 | 53 #ifdef _WIN32 | 
| c@125 | 54 #include <process.h> | 
| c@125 | 55 static int pid = _getpid(); | 
| c@125 | 56 #else | 
| c@125 | 57 #include <unistd.h> | 
| c@125 | 58 static int pid = getpid(); | 
| c@125 | 59 #endif | 
| c@125 | 60 | 
| c@138 | 61 // for _setmode stuff and _dup | 
| c@125 | 62 #ifdef _WIN32 | 
| c@125 | 63 #include <io.h> | 
| c@125 | 64 #include <fcntl.h> | 
| c@125 | 65 #endif | 
| c@125 | 66 | 
| c@138 | 67 // for dup, open etc | 
| c@138 | 68 #ifndef _WIN32 | 
| c@138 | 69 #include <fcntl.h> | 
| c@138 | 70 #include <unistd.h> | 
| c@138 | 71 #endif | 
| c@138 | 72 | 
| c@125 | 73 using namespace std; | 
| c@125 | 74 using namespace json11; | 
| c@125 | 75 using namespace piper_vamp; | 
| c@125 | 76 using namespace Vamp; | 
| c@125 | 77 | 
| c@125 | 78 static string myname = "piper-vamp-simple-server"; | 
| c@125 | 79 | 
| c@125 | 80 static void version() | 
| c@125 | 81 { | 
| c@125 | 82     cout << "1.0" << endl; | 
| c@125 | 83     exit(0); | 
| c@125 | 84 } | 
| c@125 | 85 | 
| c@125 | 86 static void usage(bool successful = false) | 
| c@125 | 87 { | 
| c@125 | 88     cerr << "\n" << myname << | 
| c@125 | 89         ": Load & run Vamp plugins in response to Piper messages\n\n" | 
| c@125 | 90         "    Usage: " << myname << " [-d] <format>\n" | 
| c@125 | 91         "           " << myname << " -v\n" | 
| c@125 | 92         "           " << myname << " -h\n\n" | 
| c@125 | 93         "    where\n" | 
| c@125 | 94         "       <format>: the format to read and write messages in (\"json\" or \"capnp\")\n" | 
| c@125 | 95         "       -d: also print debug information to stderr\n" | 
| c@125 | 96         "       -v: print version number to stdout and exit\n" | 
| c@125 | 97         "       -h: print this text to stderr and exit\n\n" | 
| c@125 | 98         "Expects Piper request messages in either Cap'n Proto or JSON format on stdin,\n" | 
| c@125 | 99         "and writes response messages in the same format to stdout.\n\n" | 
| c@125 | 100         "This server is intended for simple process separation. It's only suitable for\n" | 
| c@125 | 101         "use with a single trusted client per server invocation.\n\n" | 
| c@125 | 102         "The two formats behave differently in case of parser errors. JSON messages are\n" | 
| c@125 | 103         "expected one per input line; because the JSON support is really intended for\n" | 
| c@125 | 104         "interactive troubleshooting, any unparseable message is reported and discarded\n" | 
| c@125 | 105         "and the server waits for another message. In contrast, because of the assumption\n" | 
| c@125 | 106         "that the client is trusted and coupled to the server instance, a mangled\n" | 
| c@125 | 107         "Cap'n Proto message causes the server to exit.\n\n"; | 
| c@125 | 108     if (successful) exit(0); | 
| c@125 | 109     else exit(2); | 
| c@125 | 110 } | 
| c@125 | 111 | 
| c@125 | 112 static CountingPluginHandleMapper mapper; | 
| c@125 | 113 | 
| c@138 | 114 // We write our output to stdout, but want to ensure that the plugin | 
| c@138 | 115 // doesn't write anything itself. To do this we open a null file | 
| c@138 | 116 // descriptor and dup2() it into place of stdout in the gaps between | 
| c@138 | 117 // our own output activity. | 
| c@138 | 118 | 
| c@138 | 119 static int normalFd = -1; | 
| c@138 | 120 static int suspendedFd = -1; | 
| c@138 | 121 | 
| c@138 | 122 static void initFds(bool binary) | 
| c@138 | 123 { | 
| c@138 | 124 #ifdef _WIN32 | 
| c@138 | 125     if (binary) { | 
| c@138 | 126         int result = _setmode(0, _O_BINARY); | 
| c@138 | 127         if (result == -1) { | 
| c@138 | 128             throw runtime_error("Failed to set binary mode on stdin"); | 
| c@138 | 129         } | 
| c@138 | 130         result = _setmode(1, _O_BINARY); | 
| c@138 | 131         if (result == -1) { | 
| c@138 | 132             throw runtime_error("Failed to set binary mode on stdout"); | 
| c@138 | 133         } | 
| c@138 | 134     } | 
| c@138 | 135     normalFd = _dup(1); | 
| c@138 | 136     suspendedFd = _open("NUL", _O_WRONLY); | 
| c@138 | 137 #else | 
| c@141 | 138     (void)binary; | 
| c@138 | 139     normalFd = dup(1); | 
| c@138 | 140     suspendedFd = open("/dev/null", O_WRONLY); | 
| c@138 | 141 #endif | 
| c@138 | 142 | 
| c@138 | 143     if (normalFd < 0 || suspendedFd < 0) { | 
| c@138 | 144         throw runtime_error("Failed to initialise fds for stdio suspend/resume"); | 
| c@138 | 145     } | 
| c@138 | 146 } | 
| c@138 | 147 | 
| c@138 | 148 static void suspendOutput() | 
| c@138 | 149 { | 
| c@138 | 150 #ifdef _WIN32 | 
| c@138 | 151     _dup2(suspendedFd, 1); | 
| c@138 | 152 #else | 
| c@138 | 153     dup2(suspendedFd, 1); | 
| c@138 | 154 #endif | 
| c@138 | 155 } | 
| c@138 | 156 | 
| c@138 | 157 static void resumeOutput() | 
| c@138 | 158 { | 
| c@138 | 159 #ifdef _WIN32 | 
| c@138 | 160     _dup2(normalFd, 1); | 
| c@138 | 161 #else | 
| c@138 | 162     dup2(normalFd, 1); | 
| c@138 | 163 #endif | 
| c@138 | 164 } | 
| c@138 | 165 | 
| c@125 | 166 static RequestOrResponse::RpcId | 
| c@125 | 167 readId(const piper::RpcRequest::Reader &r) | 
| c@125 | 168 { | 
| c@125 | 169     int number; | 
| c@125 | 170     string tag; | 
| c@125 | 171     switch (r.getId().which()) { | 
| c@125 | 172     case piper::RpcRequest::Id::Which::NUMBER: | 
| c@125 | 173         number = r.getId().getNumber(); | 
| c@125 | 174         return { RequestOrResponse::RpcId::Number, number, "" }; | 
| c@125 | 175     case piper::RpcRequest::Id::Which::TAG: | 
| c@125 | 176         tag = r.getId().getTag(); | 
| c@125 | 177         return { RequestOrResponse::RpcId::Tag, 0, tag }; | 
| c@125 | 178     case piper::RpcRequest::Id::Which::NONE: | 
| c@125 | 179         return { RequestOrResponse::RpcId::Absent, 0, "" }; | 
| c@125 | 180     } | 
| cannam@154 | 181     return { RequestOrResponse::RpcId::Absent, 0, "" }; | 
| c@125 | 182 } | 
| c@125 | 183 | 
| c@125 | 184 static void | 
| c@125 | 185 buildId(piper::RpcResponse::Builder &b, const RequestOrResponse::RpcId &id) | 
| c@125 | 186 { | 
| c@125 | 187     switch (id.type) { | 
| c@125 | 188     case RequestOrResponse::RpcId::Number: | 
| c@125 | 189         b.getId().setNumber(id.number); | 
| c@125 | 190         break; | 
| c@125 | 191     case RequestOrResponse::RpcId::Tag: | 
| c@125 | 192         b.getId().setTag(id.tag); | 
| c@125 | 193         break; | 
| c@125 | 194     case RequestOrResponse::RpcId::Absent: | 
| c@125 | 195         b.getId().setNone(); | 
| c@125 | 196         break; | 
| c@125 | 197     } | 
| c@125 | 198 } | 
| c@125 | 199 | 
| c@125 | 200 static RequestOrResponse::RpcId | 
| c@125 | 201 readJsonId(const Json &j) | 
| c@125 | 202 { | 
| c@125 | 203     RequestOrResponse::RpcId id; | 
| c@125 | 204 | 
| c@125 | 205     if (j["id"].is_number()) { | 
| c@125 | 206         id.type = RequestOrResponse::RpcId::Number; | 
| c@140 | 207         id.number = int(round(j["id"].number_value())); | 
| c@125 | 208     } else if (j["id"].is_string()) { | 
| c@125 | 209         id.type = RequestOrResponse::RpcId::Tag; | 
| c@125 | 210         id.tag = j["id"].string_value(); | 
| c@125 | 211     } else { | 
| c@125 | 212         id.type = RequestOrResponse::RpcId::Absent; | 
| c@125 | 213     } | 
| c@125 | 214 | 
| c@125 | 215     return id; | 
| c@125 | 216 } | 
| c@125 | 217 | 
| c@125 | 218 static Json | 
| c@125 | 219 writeJsonId(const RequestOrResponse::RpcId &id) | 
| c@125 | 220 { | 
| c@125 | 221     if (id.type == RequestOrResponse::RpcId::Number) { | 
| c@125 | 222         return id.number; | 
| c@125 | 223     } else if (id.type == RequestOrResponse::RpcId::Tag) { | 
| c@125 | 224         return id.tag; | 
| c@125 | 225     } else { | 
| c@125 | 226         return Json(); | 
| c@125 | 227     } | 
| c@125 | 228 } | 
| c@125 | 229 | 
| c@125 | 230 static Json | 
| c@125 | 231 convertRequestJson(string input, string &err) | 
| c@125 | 232 { | 
| c@125 | 233     Json j = Json::parse(input, err); | 
| c@125 | 234     if (err != "") { | 
| c@125 | 235         err = "invalid json: " + err; | 
| c@125 | 236         return {}; | 
| c@125 | 237     } | 
| c@125 | 238     if (!j.is_object()) { | 
| c@125 | 239         err = "object expected at top level"; | 
| c@125 | 240     } else if (!j["method"].is_string()) { | 
| c@125 | 241         err = "string expected for method field"; | 
| c@125 | 242     } else if (!j["params"].is_null() && !j["params"].is_object()) { | 
| c@125 | 243         err = "object expected for params field"; | 
| c@125 | 244     } | 
| c@125 | 245     return j; | 
| c@125 | 246 } | 
| c@125 | 247 | 
| c@125 | 248 RequestOrResponse | 
| cannam@158 | 249 readRequestJson(string &err, bool &eof) | 
| c@125 | 250 { | 
| c@125 | 251     RequestOrResponse rr; | 
| c@125 | 252     rr.direction = RequestOrResponse::Request; | 
| c@125 | 253 | 
| c@125 | 254     string input; | 
| c@125 | 255     if (!getline(cin, input)) { | 
| c@125 | 256         // the EOF case, not actually an error | 
| cannam@158 | 257         eof = true; | 
| c@125 | 258         return rr; | 
| c@125 | 259     } | 
| c@125 | 260 | 
| c@125 | 261     Json j = convertRequestJson(input, err); | 
| c@125 | 262     if (err != "") return {}; | 
| c@125 | 263 | 
| c@125 | 264     rr.type = VampJson::getRequestResponseType(j, err); | 
| c@125 | 265     if (err != "") return {}; | 
| c@125 | 266 | 
| c@125 | 267     rr.id = readJsonId(j); | 
| c@125 | 268 | 
| c@125 | 269     VampJson::BufferSerialisation serialisation = | 
| c@125 | 270         VampJson::BufferSerialisation::Array; | 
| c@125 | 271 | 
| c@125 | 272     switch (rr.type) { | 
| c@125 | 273 | 
| c@125 | 274     case RRType::List: | 
| c@130 | 275         rr.listRequest = VampJson::toRpcRequest_List(j, err); | 
| c@125 | 276         break; | 
| c@125 | 277     case RRType::Load: | 
| c@125 | 278         rr.loadRequest = VampJson::toRpcRequest_Load(j, err); | 
| c@125 | 279         break; | 
| c@125 | 280     case RRType::Configure: | 
| c@125 | 281         rr.configurationRequest = VampJson::toRpcRequest_Configure(j, mapper, err); | 
| c@125 | 282         break; | 
| c@125 | 283     case RRType::Process: | 
| c@125 | 284         rr.processRequest = VampJson::toRpcRequest_Process(j, mapper, serialisation, err); | 
| c@125 | 285         break; | 
| c@125 | 286     case RRType::Finish: | 
| c@125 | 287         rr.finishRequest = VampJson::toRpcRequest_Finish(j, mapper, err); | 
| c@125 | 288         break; | 
| c@125 | 289     case RRType::NotValid: | 
| c@125 | 290         break; | 
| c@125 | 291     } | 
| c@125 | 292 | 
| c@125 | 293     return rr; | 
| c@125 | 294 } | 
| c@125 | 295 | 
| c@125 | 296 void | 
| c@125 | 297 writeResponseJson(RequestOrResponse &rr, bool useBase64) | 
| c@125 | 298 { | 
| c@125 | 299     Json j; | 
| c@125 | 300 | 
| c@125 | 301     VampJson::BufferSerialisation serialisation = | 
| c@125 | 302         (useBase64 ? | 
| c@125 | 303          VampJson::BufferSerialisation::Base64 : | 
| c@125 | 304          VampJson::BufferSerialisation::Array); | 
| c@125 | 305 | 
| c@125 | 306     Json id = writeJsonId(rr.id); | 
| c@125 | 307 | 
| c@125 | 308     if (!rr.success) { | 
| c@125 | 309 | 
| c@125 | 310         j = VampJson::fromError(rr.errorText, rr.type, id); | 
| c@125 | 311 | 
| c@125 | 312     } else { | 
| c@125 | 313 | 
| c@125 | 314         switch (rr.type) { | 
| c@125 | 315 | 
| c@125 | 316         case RRType::List: | 
| c@125 | 317             j = VampJson::fromRpcResponse_List(rr.listResponse, id); | 
| c@125 | 318             break; | 
| c@125 | 319         case RRType::Load: | 
| c@125 | 320             j = VampJson::fromRpcResponse_Load(rr.loadResponse, mapper, id); | 
| c@125 | 321             break; | 
| c@125 | 322         case RRType::Configure: | 
| c@125 | 323             j = VampJson::fromRpcResponse_Configure(rr.configurationResponse, | 
| c@125 | 324                                                     mapper, id); | 
| c@125 | 325             break; | 
| c@125 | 326         case RRType::Process: | 
| c@125 | 327             j = VampJson::fromRpcResponse_Process | 
| c@125 | 328                 (rr.processResponse, mapper, serialisation, id); | 
| c@125 | 329             break; | 
| c@125 | 330         case RRType::Finish: | 
| c@125 | 331             j = VampJson::fromRpcResponse_Finish | 
| c@125 | 332                 (rr.finishResponse, mapper, serialisation, id); | 
| c@125 | 333             break; | 
| c@125 | 334         case RRType::NotValid: | 
| c@125 | 335             break; | 
| c@125 | 336         } | 
| c@125 | 337     } | 
| c@138 | 338 | 
| c@125 | 339     cout << j.dump() << endl; | 
| c@125 | 340 } | 
| c@125 | 341 | 
| c@125 | 342 void | 
| cannam@158 | 343 writeExceptionJson(const exception &e, RRType type, RequestOrResponse::RpcId id) | 
| c@125 | 344 { | 
| cannam@158 | 345     Json jid = writeJsonId(id); | 
| cannam@158 | 346     Json j = VampJson::fromError(e.what(), type, jid); | 
| c@125 | 347     cout << j.dump() << endl; | 
| c@125 | 348 } | 
| c@125 | 349 | 
| c@125 | 350 RequestOrResponse | 
| cannam@158 | 351 readRequestCapnp(bool &eof) | 
| c@125 | 352 { | 
| c@125 | 353     RequestOrResponse rr; | 
| c@125 | 354     rr.direction = RequestOrResponse::Request; | 
| c@125 | 355 | 
| c@125 | 356     static kj::FdInputStream stream(0); // stdin | 
| c@125 | 357     static kj::BufferedInputStreamWrapper buffered(stream); | 
| c@125 | 358 | 
| c@125 | 359     if (buffered.tryGetReadBuffer() == nullptr) { | 
| cannam@158 | 360         eof = true; | 
| c@125 | 361         return rr; | 
| c@125 | 362     } | 
| c@125 | 363 | 
| c@125 | 364     capnp::InputStreamMessageReader message(buffered); | 
| c@125 | 365     piper::RpcRequest::Reader reader = message.getRoot<piper::RpcRequest>(); | 
| c@125 | 366 | 
| c@125 | 367     rr.type = VampnProto::getRequestResponseType(reader); | 
| c@125 | 368     rr.id = readId(reader); | 
| c@125 | 369 | 
| c@125 | 370     switch (rr.type) { | 
| c@125 | 371 | 
| c@125 | 372     case RRType::List: | 
| c@127 | 373         VampnProto::readRpcRequest_List(rr.listRequest, reader); | 
| c@125 | 374         break; | 
| c@125 | 375     case RRType::Load: | 
| c@125 | 376         VampnProto::readRpcRequest_Load(rr.loadRequest, reader); | 
| c@125 | 377         break; | 
| c@125 | 378     case RRType::Configure: | 
| c@125 | 379         VampnProto::readRpcRequest_Configure(rr.configurationRequest, | 
| c@125 | 380                                              reader, mapper); | 
| c@125 | 381         break; | 
| c@125 | 382     case RRType::Process: | 
| c@125 | 383         VampnProto::readRpcRequest_Process(rr.processRequest, reader, mapper); | 
| c@125 | 384         break; | 
| c@125 | 385     case RRType::Finish: | 
| c@125 | 386         VampnProto::readRpcRequest_Finish(rr.finishRequest, reader, mapper); | 
| c@125 | 387         break; | 
| c@125 | 388     case RRType::NotValid: | 
| c@125 | 389         break; | 
| c@125 | 390     } | 
| c@125 | 391 | 
| c@125 | 392     return rr; | 
| c@125 | 393 } | 
| c@125 | 394 | 
| c@125 | 395 void | 
| c@125 | 396 writeResponseCapnp(RequestOrResponse &rr) | 
| c@125 | 397 { | 
| c@125 | 398     capnp::MallocMessageBuilder message; | 
| c@125 | 399     piper::RpcResponse::Builder builder = message.initRoot<piper::RpcResponse>(); | 
| c@125 | 400 | 
| c@125 | 401     buildId(builder, rr.id); | 
| c@125 | 402 | 
| c@125 | 403     if (!rr.success) { | 
| c@125 | 404 | 
| c@125 | 405         VampnProto::buildRpcResponse_Error(builder, rr.errorText, rr.type); | 
| c@125 | 406 | 
| c@125 | 407     } else { | 
| c@125 | 408 | 
| c@125 | 409         switch (rr.type) { | 
| c@125 | 410 | 
| c@125 | 411         case RRType::List: | 
| c@125 | 412             VampnProto::buildRpcResponse_List(builder, rr.listResponse); | 
| c@125 | 413             break; | 
| c@125 | 414         case RRType::Load: | 
| c@125 | 415             VampnProto::buildRpcResponse_Load(builder, rr.loadResponse, mapper); | 
| c@125 | 416             break; | 
| c@125 | 417         case RRType::Configure: | 
| c@125 | 418             VampnProto::buildRpcResponse_Configure(builder, rr.configurationResponse, mapper); | 
| c@125 | 419             break; | 
| c@125 | 420         case RRType::Process: | 
| c@125 | 421             VampnProto::buildRpcResponse_Process(builder, rr.processResponse, mapper); | 
| c@125 | 422             break; | 
| c@125 | 423         case RRType::Finish: | 
| c@125 | 424             VampnProto::buildRpcResponse_Finish(builder, rr.finishResponse, mapper); | 
| c@125 | 425             break; | 
| c@125 | 426         case RRType::NotValid: | 
| c@125 | 427             break; | 
| c@125 | 428         } | 
| c@125 | 429     } | 
| c@125 | 430 | 
| c@125 | 431     writeMessageToFd(1, message); | 
| c@125 | 432 } | 
| c@125 | 433 | 
| c@125 | 434 void | 
| cannam@158 | 435 writeExceptionCapnp(const exception &e, RRType type, RequestOrResponse::RpcId id) | 
| c@125 | 436 { | 
| c@125 | 437     capnp::MallocMessageBuilder message; | 
| c@125 | 438     piper::RpcResponse::Builder builder = message.initRoot<piper::RpcResponse>(); | 
| cannam@158 | 439 | 
| cannam@158 | 440     buildId(builder, id); | 
| c@125 | 441     VampnProto::buildRpcResponse_Exception(builder, e, type); | 
| c@125 | 442 | 
| c@125 | 443     writeMessageToFd(1, message); | 
| c@125 | 444 } | 
| c@125 | 445 | 
| c@125 | 446 RequestOrResponse | 
| c@125 | 447 handleRequest(const RequestOrResponse &request, bool debug) | 
| c@125 | 448 { | 
| c@125 | 449     RequestOrResponse response; | 
| c@125 | 450     response.direction = RequestOrResponse::Response; | 
| c@125 | 451     response.type = request.type; | 
| c@125 | 452 | 
| c@125 | 453     switch (request.type) { | 
| c@125 | 454 | 
| c@125 | 455     case RRType::List: | 
| c@127 | 456         response.listResponse = | 
| c@127 | 457             LoaderRequests().listPluginData(request.listRequest); | 
| c@125 | 458         response.success = true; | 
| c@125 | 459         break; | 
| c@125 | 460 | 
| c@125 | 461     case RRType::Load: | 
| c@127 | 462         response.loadResponse = | 
| c@127 | 463             LoaderRequests().loadPlugin(request.loadRequest); | 
| c@125 | 464         if (response.loadResponse.plugin != nullptr) { | 
| c@125 | 465             mapper.addPlugin(response.loadResponse.plugin); | 
| c@125 | 466             if (debug) { | 
| c@127 | 467                 cerr << "piper-vamp-server " << pid | 
| c@127 | 468                      << ": loaded plugin, handle = " | 
| c@127 | 469                      << mapper.pluginToHandle(response.loadResponse.plugin) | 
| c@127 | 470                      << endl; | 
| c@125 | 471             } | 
| c@125 | 472             response.success = true; | 
| c@125 | 473         } | 
| c@125 | 474         break; | 
| c@125 | 475 | 
| c@125 | 476     case RRType::Configure: | 
| c@125 | 477     { | 
| c@125 | 478         auto &creq = request.configurationRequest; | 
| cannam@158 | 479         if (!creq.plugin) { | 
| cannam@158 | 480             throw runtime_error("unknown plugin handle supplied to configure"); | 
| cannam@158 | 481         } | 
| cannam@158 | 482 | 
| c@125 | 483         auto h = mapper.pluginToHandle(creq.plugin); | 
| c@125 | 484         if (mapper.isConfigured(h)) { | 
| c@125 | 485             throw runtime_error("plugin has already been configured"); | 
| c@125 | 486         } | 
| c@125 | 487 | 
| cannam@185 | 488         if (creq.configuration.framing.stepSize == 0 || | 
| cannam@185 | 489             creq.configuration.framing.blockSize == 0) { | 
| cannam@185 | 490             throw runtime_error("step and block size must be non-zero"); | 
| cannam@185 | 491         } | 
| cannam@185 | 492 | 
| c@125 | 493         response.configurationResponse = LoaderRequests().configurePlugin(creq); | 
| c@125 | 494 | 
| c@125 | 495         if (!response.configurationResponse.outputs.empty()) { | 
| c@125 | 496             mapper.markConfigured | 
| cannam@185 | 497                 (h, | 
| cannam@185 | 498                  creq.configuration.channelCount, | 
| cannam@186 | 499                  response.configurationResponse.framing.blockSize); | 
| c@125 | 500             response.success = true; | 
| c@125 | 501         } | 
| c@125 | 502         break; | 
| c@125 | 503     } | 
| c@125 | 504 | 
| c@125 | 505     case RRType::Process: | 
| c@125 | 506     { | 
| c@125 | 507         auto &preq = request.processRequest; | 
| cannam@158 | 508         if (!preq.plugin) { | 
| cannam@158 | 509             throw runtime_error("unknown plugin handle supplied to process"); | 
| cannam@158 | 510         } | 
| cannam@158 | 511 | 
| c@125 | 512         auto h = mapper.pluginToHandle(preq.plugin); | 
| c@125 | 513         if (!mapper.isConfigured(h)) { | 
| c@125 | 514             throw runtime_error("plugin has not been configured"); | 
| c@125 | 515         } | 
| c@125 | 516 | 
| c@125 | 517         int channels = int(preq.inputBuffers.size()); | 
| c@125 | 518         if (channels != mapper.getChannelCount(h)) { | 
| c@125 | 519             throw runtime_error("wrong number of channels supplied to process"); | 
| c@125 | 520         } | 
| c@125 | 521 | 
| c@125 | 522         const float **fbuffers = new const float *[channels]; | 
| c@125 | 523         for (int i = 0; i < channels; ++i) { | 
| c@125 | 524             if (int(preq.inputBuffers[i].size()) != mapper.getBlockSize(h)) { | 
| cannam@186 | 525                 ostringstream os; | 
| cannam@186 | 526                 os << "wrong block size supplied to process (" | 
| cannam@186 | 527                    << preq.inputBuffers[i].size() | 
| cannam@186 | 528                    << ", expecting " << mapper.getBlockSize(h) << ")" << ends; | 
| c@125 | 529                 delete[] fbuffers; | 
| cannam@186 | 530                 throw runtime_error(os.str()); | 
| c@125 | 531             } | 
| c@125 | 532             fbuffers[i] = preq.inputBuffers[i].data(); | 
| c@125 | 533         } | 
| c@125 | 534 | 
| c@125 | 535         response.processResponse.plugin = preq.plugin; | 
| c@125 | 536         response.processResponse.features = | 
| c@125 | 537             preq.plugin->process(fbuffers, preq.timestamp); | 
| c@125 | 538         response.success = true; | 
| c@125 | 539 | 
| c@125 | 540         delete[] fbuffers; | 
| c@125 | 541         break; | 
| c@125 | 542     } | 
| c@125 | 543 | 
| c@125 | 544     case RRType::Finish: | 
| c@125 | 545     { | 
| c@125 | 546         auto &freq = request.finishRequest; | 
| cannam@158 | 547         if (!freq.plugin) { | 
| cannam@158 | 548             throw runtime_error("unknown plugin handle supplied to finish"); | 
| cannam@158 | 549         } | 
| cannam@158 | 550 | 
| c@125 | 551         response.finishResponse.plugin = freq.plugin; | 
| c@125 | 552 | 
| c@125 | 553         auto h = mapper.pluginToHandle(freq.plugin); | 
| c@125 | 554         // Finish can be called (to unload the plugin) even if the | 
| c@125 | 555         // plugin has never been configured or used. But we want to | 
| c@125 | 556         // make sure we call getRemainingFeatures only if we have | 
| c@125 | 557         // actually configured the plugin. | 
| c@125 | 558         if (mapper.isConfigured(h)) { | 
| c@125 | 559             response.finishResponse.features = freq.plugin->getRemainingFeatures(); | 
| c@125 | 560         } | 
| c@125 | 561 | 
| c@125 | 562         // We do not delete the plugin here -- we need it in the | 
| c@125 | 563         // mapper when converting the features. It gets deleted in the | 
| c@125 | 564         // calling function. | 
| c@125 | 565         response.success = true; | 
| c@125 | 566         break; | 
| c@125 | 567     } | 
| c@125 | 568 | 
| c@125 | 569     case RRType::NotValid: | 
| c@125 | 570         break; | 
| c@125 | 571     } | 
| c@125 | 572 | 
| c@125 | 573     return response; | 
| c@125 | 574 } | 
| c@125 | 575 | 
| c@125 | 576 RequestOrResponse | 
| cannam@158 | 577 readRequest(string format, bool &eof) | 
| c@125 | 578 { | 
| c@125 | 579     if (format == "capnp") { | 
| cannam@158 | 580         return readRequestCapnp(eof); | 
| c@125 | 581     } else if (format == "json") { | 
| c@125 | 582         string err; | 
| cannam@158 | 583         auto result = readRequestJson(err, eof); | 
| c@125 | 584         if (err != "") throw runtime_error(err); | 
| c@125 | 585         else return result; | 
| c@125 | 586     } else { | 
| c@125 | 587         throw runtime_error("unknown input format \"" + format + "\""); | 
| c@125 | 588     } | 
| c@125 | 589 } | 
| c@125 | 590 | 
| c@125 | 591 void | 
| c@125 | 592 writeResponse(string format, RequestOrResponse &rr) | 
| c@125 | 593 { | 
| c@138 | 594     resumeOutput(); | 
| c@125 | 595     if (format == "capnp") { | 
| c@125 | 596         writeResponseCapnp(rr); | 
| c@125 | 597     } else if (format == "json") { | 
| c@125 | 598         writeResponseJson(rr, false); | 
| c@125 | 599     } else { | 
| c@125 | 600         throw runtime_error("unknown output format \"" + format + "\""); | 
| c@125 | 601     } | 
| c@138 | 602     suspendOutput(); | 
| c@125 | 603 } | 
| c@125 | 604 | 
| c@125 | 605 void | 
| cannam@158 | 606 writeException(string format, const exception &e, RRType type, RequestOrResponse::RpcId id) | 
| c@125 | 607 { | 
| c@138 | 608     resumeOutput(); | 
| c@125 | 609     if (format == "capnp") { | 
| cannam@158 | 610         writeExceptionCapnp(e, type, id); | 
| c@125 | 611     } else if (format == "json") { | 
| cannam@158 | 612         writeExceptionJson(e, type, id); | 
| c@125 | 613     } else { | 
| c@125 | 614         throw runtime_error("unknown output format \"" + format + "\""); | 
| c@125 | 615     } | 
| c@138 | 616     suspendOutput(); | 
| c@125 | 617 } | 
| c@125 | 618 | 
| c@125 | 619 int main(int argc, char **argv) | 
| c@125 | 620 { | 
| c@125 | 621     if (argc != 2 && argc != 3) { | 
| c@125 | 622         usage(); | 
| c@125 | 623     } | 
| c@125 | 624 | 
| c@125 | 625     bool debug = false; | 
| c@125 | 626 | 
| c@125 | 627     string arg = argv[1]; | 
| c@125 | 628     if (arg == "-h") { | 
| c@125 | 629         if (argc == 2) { | 
| c@125 | 630             usage(true); | 
| c@125 | 631         } else { | 
| c@125 | 632             usage(); | 
| c@125 | 633         } | 
| c@125 | 634     } else if (arg == "-v") { | 
| c@125 | 635         if (argc == 2) { | 
| c@125 | 636             version(); | 
| c@125 | 637         } else { | 
| c@125 | 638             usage(); | 
| c@125 | 639         } | 
| c@125 | 640     } else if (arg == "-d") { | 
| c@125 | 641         if (argc == 2) { | 
| c@125 | 642             usage(); | 
| c@125 | 643         } else { | 
| c@125 | 644             debug = true; | 
| c@125 | 645             arg = argv[2]; | 
| c@125 | 646         } | 
| c@125 | 647     } | 
| c@125 | 648 | 
| c@125 | 649     string format = arg; | 
| c@125 | 650 | 
| c@125 | 651     if (format != "capnp" && format != "json") { | 
| c@125 | 652         usage(); | 
| c@125 | 653     } | 
| c@125 | 654 | 
| c@138 | 655     try { | 
| c@138 | 656         initFds(format == "capnp"); | 
| c@138 | 657     } catch (exception &e) { | 
| c@138 | 658         cerr << "ERROR: " << e.what() << endl; | 
| c@138 | 659         exit(1); | 
| c@125 | 660     } | 
| c@138 | 661 | 
| c@138 | 662     suspendOutput(); | 
| c@125 | 663 | 
| c@125 | 664     if (debug) { | 
| c@125 | 665         cerr << myname << " " << pid << ": waiting for format: " << format << endl; | 
| c@125 | 666     } | 
| c@138 | 667 | 
| c@125 | 668     while (true) { | 
| c@125 | 669 | 
| c@125 | 670         RequestOrResponse request; | 
| c@125 | 671 | 
| c@125 | 672         try { | 
| c@125 | 673 | 
| cannam@158 | 674             bool eof = false; | 
| cannam@158 | 675             request = readRequest(format, eof); | 
| c@125 | 676 | 
| cannam@158 | 677             if (eof) { | 
| c@125 | 678                 if (debug) { | 
| c@125 | 679                     cerr << myname << " " << pid << ": eof reached, exiting" << endl; | 
| c@125 | 680                 } | 
| c@125 | 681                 break; | 
| c@125 | 682             } | 
| c@125 | 683 | 
| c@125 | 684             if (debug) { | 
| c@125 | 685                 cerr << myname << " " << pid << ": request received, of type " | 
| c@125 | 686                      << int(request.type) | 
| c@125 | 687                      << endl; | 
| c@125 | 688             } | 
| cannam@158 | 689 | 
| cannam@158 | 690         } catch (exception &e) { | 
| c@125 | 691 | 
| cannam@158 | 692             if (debug) { | 
| cannam@158 | 693                 cerr << myname << " " << pid << ": error: " << e.what() << endl; | 
| cannam@158 | 694             } | 
| cannam@158 | 695 | 
| cannam@158 | 696             writeException(format, e, request.type, request.id); | 
| cannam@158 | 697 | 
| cannam@158 | 698             if (format == "capnp") { | 
| cannam@158 | 699                 // Don't try to continue; we can't recover from a | 
| cannam@158 | 700                 // mangled input stream. However, we can return a | 
| cannam@158 | 701                 // successful error code because we are reporting the | 
| cannam@158 | 702                 // status in our Capnp output stream instead | 
| cannam@158 | 703                 if (debug) { | 
| cannam@158 | 704                     cerr << myname << " " << pid << ": not attempting to recover from capnp parse problems, exiting" << endl; | 
| cannam@158 | 705                 } | 
| cannam@158 | 706                 exit(0); | 
| cannam@158 | 707             } | 
| cannam@158 | 708         } | 
| cannam@158 | 709 | 
| cannam@158 | 710         try { | 
| c@125 | 711             RequestOrResponse response = handleRequest(request, debug); | 
| c@125 | 712             response.id = request.id; | 
| c@125 | 713 | 
| c@125 | 714             if (debug) { | 
| c@125 | 715                 cerr << myname << " " << pid << ": request handled, writing response" | 
| c@125 | 716                      << endl; | 
| c@125 | 717             } | 
| c@125 | 718 | 
| c@125 | 719             writeResponse(format, response); | 
| c@125 | 720 | 
| c@125 | 721             if (debug) { | 
| c@125 | 722                 cerr << myname << " " << pid << ": response written" << endl; | 
| c@125 | 723             } | 
| c@125 | 724 | 
| c@125 | 725             if (request.type == RRType::Finish) { | 
| c@125 | 726                 auto h = mapper.pluginToHandle(request.finishRequest.plugin); | 
| c@125 | 727                 if (debug) { | 
| c@125 | 728                     cerr << myname << " " << pid << ": deleting the plugin with handle " << h << endl; | 
| c@125 | 729                 } | 
| c@125 | 730                 mapper.removePlugin(h); | 
| c@125 | 731                 delete request.finishRequest.plugin; | 
| c@125 | 732             } | 
| c@125 | 733 | 
| c@138 | 734         } catch (exception &e) { | 
| c@125 | 735 | 
| c@125 | 736             if (debug) { | 
| c@125 | 737                 cerr << myname << " " << pid << ": error: " << e.what() << endl; | 
| c@125 | 738             } | 
| c@125 | 739 | 
| cannam@158 | 740             writeException(format, e, request.type, request.id); | 
| c@125 | 741         } | 
| c@125 | 742     } | 
| c@125 | 743 | 
| c@125 | 744     exit(0); | 
| c@125 | 745 } |