Chris@49: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@0: 
Chris@0: /*
Chris@52:     Sonic Visualiser
Chris@52:     An audio file viewer and annotation editor.
Chris@52:     Centre for Digital Music, Queen Mary, University of London.
Chris@1241:     This file copyright 2006-2016 Chris Cannam and QMUL.
Chris@0:     
Chris@52:     This program is free software; you can redistribute it and/or
Chris@52:     modify it under the terms of the GNU General Public License as
Chris@52:     published by the Free Software Foundation; either version 2 of the
Chris@52:     License, or (at your option) any later version.  See the file
Chris@52:     COPYING included with this distribution for more information.
Chris@0: */
Chris@0: 
Chris@1249: #ifdef HAVE_PIPER
Chris@1249: 
Chris@1225: #include "PiperVampPluginFactory.h"
Chris@0: #include "PluginIdentifier.h"
Chris@0: 
Chris@150: #include "system/System.h"
Chris@66: 
Chris@1179: #include "PluginScan.h"
Chris@1179: 
Chris@1224: #ifdef _WIN32
Chris@1224: #undef VOID
Chris@1224: #undef ERROR
Chris@1224: #define CAPNP_LITE 1
Chris@1224: #endif
Chris@1225: 
Chris@1378: #include "vamp-client/qt/PiperAutoPlugin.h"
Chris@1370: #include "vamp-client/qt/ProcessQtTransport.h"
Chris@1370: #include "vamp-client/CapnpRRClient.h"
Chris@1210: 
Chris@66: #include <QDir>
Chris@66: #include <QFile>
Chris@66: #include <QFileInfo>
Chris@165: #include <QTextStream>
Chris@1227: #include <QCoreApplication>
Chris@66: 
Chris@0: #include <iostream>
Chris@0: 
Chris@408: #include "base/Profiler.h"
Chris@1241: #include "base/HelperExecPath.h"
Chris@408: 
Chris@1164: using namespace std;
Chris@1164: 
Chris@249: //#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1
Chris@249: 
Chris@1264: class PiperVampPluginFactory::Logger : public piper_vamp::client::LogCallback {
Chris@1264: protected:
Chris@1264:     void log(std::string message) const override {
Chris@1264:         SVDEBUG << "PiperVampPluginFactory: " << message << endl;
Chris@1264:     }
Chris@1264: };
Chris@1264: 
Chris@1264: PiperVampPluginFactory::PiperVampPluginFactory() :
Chris@1264:     m_logger(new Logger)
Chris@66: {
Chris@1241:     QString serverName = "piper-vamp-simple-server";
Chris@1240: 
Chris@1246:     HelperExecPath hep(HelperExecPath::AllInstalled);
Chris@1246:     m_servers = hep.getHelperExecutables(serverName);
Chris@1240: 
Chris@1246:     for (auto n: m_servers) {
Chris@1247:         SVDEBUG << "NOTE: PiperVampPluginFactory: Found server: "
Chris@1247:                 << n.executable << endl;
Chris@1246:     }
Chris@1246:     
Chris@1240:     if (m_servers.empty()) {
Chris@1247:         SVDEBUG << "NOTE: No Piper Vamp servers found in installation;"
Chris@1247:                 << " found none of the following:" << endl;
Chris@1246:         for (auto d: hep.getHelperCandidatePaths(serverName)) {
Chris@1247:             SVDEBUG << "NOTE: " << d << endl;
Chris@1241:         }
Chris@1240:     }
Chris@1240: }
Chris@1240: 
Chris@1264: PiperVampPluginFactory::~PiperVampPluginFactory()
Chris@1264: {
Chris@1264:     delete m_logger;
Chris@1264: }
Chris@1264: 
Chris@1164: vector<QString>
Chris@1227: PiperVampPluginFactory::getPluginIdentifiers(QString &errorMessage)
Chris@0: {
Chris@1225:     Profiler profiler("PiperVampPluginFactory::getPluginIdentifiers");
Chris@408: 
Chris@1209:     QMutexLocker locker(&m_mutex);
Chris@1209: 
Chris@1240:     if (m_servers.empty()) {
Chris@1227:         errorMessage = QObject::tr("External plugin host executable does not appear to be installed");
Chris@1227:         return {};
Chris@1227:     }
Chris@1227:     
Chris@1209:     if (m_pluginData.empty()) {
Chris@1227:         populate(errorMessage);
Chris@1209:     }
Chris@1209: 
Chris@1164:     vector<QString> rv;
Chris@1179: 
Chris@1209:     for (const auto &d: m_pluginData) {
Chris@1225:         rv.push_back(QString("vamp:") + QString::fromStdString(d.second.pluginKey));
Chris@66:     }
Chris@66: 
Chris@0:     return rv;
Chris@0: }
Chris@0: 
Chris@66: Vamp::Plugin *
Chris@1225: PiperVampPluginFactory::instantiatePlugin(QString identifier,
Chris@1225:                                           sv_samplerate_t inputSampleRate)
Chris@0: {
Chris@1225:     Profiler profiler("PiperVampPluginFactory::instantiatePlugin");
Chris@1225: 
Chris@1240:     if (m_origins.find(identifier) == m_origins.end()) {
Chris@1428:         SVCERR << "ERROR: No known server for identifier " << identifier << endl;
Chris@1240:         return 0;
Chris@1240:     }
Chris@1240:     
Chris@1225:     auto psd = getPluginStaticData(identifier);
Chris@1225:     if (psd.pluginKey == "") {
Chris@1225:         return 0;
Chris@1225:     }
Chris@1264: 
Chris@1378:     SVDEBUG << "PiperVampPluginFactory: Creating PiperAutoPlugin for server "
Chris@1264:         << m_origins[identifier] << ", identifier " << identifier << endl;
Chris@1210:     
Chris@1378:     auto ap = new piper_vamp::client::PiperAutoPlugin
Chris@1240:         (m_origins[identifier].toStdString(),
Chris@1264:          psd.pluginKey,
Chris@1264:          float(inputSampleRate),
Chris@1264:          0,
Chris@1264:          m_logger);
Chris@1240:     
Chris@1210:     if (!ap->isOK()) {
Chris@1210:         delete ap;
Chris@1210:         return 0;
Chris@1225:     }
Chris@1225: 
Chris@1225:     return ap;
Chris@1225: }
Chris@1225: 
Chris@1225: piper_vamp::PluginStaticData
Chris@1225: PiperVampPluginFactory::getPluginStaticData(QString identifier)
Chris@1225: {
Chris@1225:     if (m_pluginData.find(identifier) != m_pluginData.end()) {
Chris@1225:         return m_pluginData[identifier];
Chris@1210:     } else {
Chris@1225:         return {};
Chris@1210:     }
Chris@298: }
Chris@298: 
Chris@165: QString
Chris@1225: PiperVampPluginFactory::getPluginCategory(QString identifier)
Chris@165: {
Chris@1223:     if (m_taxonomy.find(identifier) != m_taxonomy.end()) {
Chris@1223:         return m_taxonomy[identifier];
Chris@1223:     } else {
Chris@1223:         return {};
Chris@1223:     }
Chris@165: }
Chris@165: 
Chris@165: void
Chris@1227: PiperVampPluginFactory::populate(QString &errorMessage)
Chris@165: {
Chris@1240:     QString someError;
Chris@1227: 
Chris@1246:     for (auto s: m_servers) {
Chris@1240: 
Chris@1240:         populateFrom(s, someError);
Chris@1240: 
Chris@1240:         if (someError != "" && errorMessage == "") {
Chris@1240:             errorMessage = someError;
Chris@1240:         }
Chris@1240:     }
Chris@1240: }
Chris@1240: 
Chris@1240: void
Chris@1246: PiperVampPluginFactory::populateFrom(const HelperExecPath::HelperExec &server,
Chris@1246:                                      QString &errorMessage)
Chris@1240: {
Chris@1246:     QString tag = server.tag;
Chris@1246:     string executable = server.executable.toStdString();
Chris@1246: 
Chris@1246:     PluginScan *scan = PluginScan::getInstance();
Chris@1246:     auto candidateLibraries =
Chris@1246:         scan->getCandidateLibrariesFor(PluginScan::VampPlugin);
Chris@1246: 
Chris@1264:     SVDEBUG << "PiperVampPluginFactory: Populating from " << executable << endl;
Chris@1250:     SVDEBUG << "INFO: Have " << candidateLibraries.size()
Chris@1264:             << " candidate Vamp plugin libraries from scanner" << endl;
Chris@1249:         
Chris@1246:     vector<string> from;
Chris@1246:     for (const auto &c: candidateLibraries) {
Chris@1246:         if (c.helperTag == tag) {
Chris@1246:             string soname = QFileInfo(c.libraryPath).baseName().toStdString();
Chris@1247:             SVDEBUG << "INFO: For tag \"" << tag << "\" giving library " << soname << endl;
Chris@1246:             from.push_back(soname);
Chris@1246:         }
Chris@1246:     }
Chris@1246: 
Chris@1246:     if (from.empty()) {
Chris@1247:         SVDEBUG << "PiperVampPluginFactory: No candidate libraries for tag \""
Chris@1246:              << tag << "\"";
Chris@1246:         if (scan->scanSucceeded()) {
Chris@1246:             // we have to assume that they all failed to load (i.e. we
Chris@1246:             // exclude them all) rather than sending an empty list
Chris@1246:             // (which would mean no exclusions)
Chris@1247:             SVDEBUG << ", skipping" << endl;
Chris@1246:             return;
Chris@1246:         } else {
Chris@1247:             SVDEBUG << ", but it seems the scan failed, so bumbling on anyway" << endl;
Chris@1246:         }
Chris@1246:     }
Chris@1246:     
Chris@1264:     piper_vamp::client::ProcessQtTransport transport(executable, "capnp", m_logger);
Chris@1227:     if (!transport.isOK()) {
Chris@1264:         SVDEBUG << "PiperVampPluginFactory: Failed to start Piper process transport" << endl;
Chris@1227:         errorMessage = QObject::tr("Could not start external plugin host");
Chris@1227:         return;
Chris@1227:     }
Chris@1234: 
Chris@1264:     piper_vamp::client::CapnpRRClient client(&transport, m_logger);
Chris@1248: 
Chris@1248:     piper_vamp::ListRequest req;
Chris@1248:     req.from = from;
Chris@1248:     
Chris@1248:     piper_vamp::ListResponse resp;
Chris@1234: 
Chris@1234:     try {
Chris@1378:         resp = client.list(req);
Chris@1234:     } catch (piper_vamp::client::ServerCrashed) {
Chris@1264:         SVDEBUG << "PiperVampPluginFactory: Piper server crashed" << endl;
Chris@1234:         errorMessage = QObject::tr
Chris@1234:             ("External plugin host exited unexpectedly while listing plugins");
Chris@1234:         return;
Chris@1235:     } catch (const std::exception &e) {
Chris@1264:         SVDEBUG << "PiperVampPluginFactory: Exception caught: " << e.what() << endl;
Chris@1235:         errorMessage = QObject::tr("External plugin host invocation failed: %1")
Chris@1235:             .arg(e.what());
Chris@1235:         return;
Chris@1234:     }
Chris@1213: 
Chris@1247:     SVDEBUG << "PiperVampPluginFactory: server \"" << executable << "\" lists "
Chris@1248:             << resp.available.size() << " plugin(s)" << endl;
Chris@1244: 
Chris@1248:     for (const auto &pd: resp.available) {
Chris@1240:         
Chris@1213:         QString identifier =
Chris@1213:             QString("vamp:") + QString::fromStdString(pd.pluginKey);
Chris@1213: 
Chris@1240:         if (m_origins.find(identifier) != m_origins.end()) {
Chris@1240:             // have it already, from a higher-priority server
Chris@1240:             // (e.g. 64-bit instead of 32-bit)
Chris@1240:             continue;
Chris@1240:         }
Chris@1240: 
Chris@1246:         m_origins[identifier] = server.executable;
Chris@1240:         
Chris@1225:         m_pluginData[identifier] = pd;
Chris@1225: 
Chris@1213:         QStringList catlist;
Chris@1213:         for (const auto &cs: pd.category) {
Chris@1213:             catlist.push_back(QString::fromStdString(cs));
Chris@1213:         }
Chris@1223: 
Chris@1213:         m_taxonomy[identifier] = catlist.join(" > ");
Chris@1213:     }
Chris@1209: }
Chris@165: 
Chris@1249: #endif