view plugin/PiperVampPluginFactory.cpp @ 1377:f1c4ca3ccaa3 dev/refactor-piper-related

Change setDesiredExtractor signature to take ServerDescription aggregate.
author Lucas Thompson <lucas.thompson@qmul.ac.uk>
date Fri, 10 Feb 2017 11:30:29 +0000
parents d9511f9e04d7
children
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */

/*
    Sonic Visualiser
    An audio file viewer and annotation editor.
    Centre for Digital Music, Queen Mary, University of London.
    This file copyright 2006-2016 Chris Cannam and QMUL.
    
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.  See the file
    COPYING included with this distribution for more information.
*/

#ifdef HAVE_PIPER

#include "PiperVampPluginFactory.h"
#include "PluginIdentifier.h"

#include "system/System.h"

#include "PluginScan.h"

#ifdef _WIN32
#undef VOID
#undef ERROR
#define CAPNP_LITE 1
#endif

#include "vamp-client/qt/AutoPlugin.h"
#include "vamp-client/qt/ProcessQtTransport.h"
#include "vamp-client/CapnpRRClient.h"

#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <QCoreApplication>

#include <iostream>
#include <algorithm>

#include "base/Profiler.h"
#include "base/HelperExecPath.h"

using namespace std;

//#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1

class PiperVampPluginFactory::Logger : public piper_vamp::client::LogCallback {
protected:
    void log(std::string message) const override {
        SVDEBUG << "PiperVampPluginFactory: " << message << endl;
    }
};

PiperVampPluginFactory::PiperVampPluginFactory(std::initializer_list<ServerDescription> servers) :
    m_logger(new Logger)
{
    HelperExecPath hep(HelperExecPath::AllInstalled);

    for (auto server: servers) {
        for (auto platformHelper: hep.getHelperExecutables(QString::fromStdString(server.name)))
            m_servers.push_back(platformHelper);
        if (server.hasDesiredExtractors) {
            setDesiredExtractors(server);
        }
    }

    for (auto n: m_servers) {
        SVDEBUG << "NOTE: PiperVampPluginFactory: Found server: " 
                << n.executable << endl;
    }

    if (m_servers.empty()) {
        SVDEBUG << "NOTE: No Piper Vamp servers found in installation;" 
                << " found none of the following:" << endl;
        for (auto server: servers)
            for (auto d: hep.getHelperCandidatePaths(QString::fromStdString(server.name))) {
                SVDEBUG << "NOTE: " << d << endl;
            }
    }
}

PiperVampPluginFactory::PiperVampPluginFactory() :
    PiperVampPluginFactory({{"piper-vamp-simple-server"}}) {}

PiperVampPluginFactory::~PiperVampPluginFactory()
{
    delete m_logger;
}

void 
PiperVampPluginFactory::setDesiredExtractors(ServerDescription description)
{
    const bool isValidServerName = std::find_if(
        m_servers.begin(), 
        m_servers.end(), 
        [&description](const HelperExecPath::HelperExec &h) -> bool {
            return QFileInfo(h.executable).fileName().toStdString() == description.name;       
        }) != m_servers.end();
    if ( isValidServerName && description.hasDesiredExtractors ) {
        m_overrideDesiredExtractors[description.name] = description.extractors;
    }
}

vector<QString>
PiperVampPluginFactory::getPluginIdentifiers(QString &errorMessage)
{
    Profiler profiler("PiperVampPluginFactory::getPluginIdentifiers");

    QMutexLocker locker(&m_mutex);

    if (m_servers.empty()) {
        errorMessage = QObject::tr("External plugin host executable does not appear to be installed");
        return {};
    }
    
    if (m_pluginData.empty()) {
        populate(errorMessage);
    }

    vector<QString> rv;

    for (const auto &d: m_pluginData) {
        rv.push_back(QString("vamp:") + QString::fromStdString(d.second.pluginKey));
    }

    return rv;
}

Vamp::Plugin *
PiperVampPluginFactory::instantiatePlugin(QString identifier,
                                          sv_samplerate_t inputSampleRate)
{
    Profiler profiler("PiperVampPluginFactory::instantiatePlugin");

    if (m_origins.find(identifier) == m_origins.end()) {
        cerr << "ERROR: No known server for identifier " << identifier << endl;
        SVDEBUG << "ERROR: No known server for identifier " << identifier << endl;
        return 0;
    }
    
    auto psd = getPluginStaticData(identifier);
    if (psd.pluginKey == "") {
        return 0;
    }

    SVDEBUG << "PiperVampPluginFactory: Creating Piper AutoPlugin for server "
        << m_origins[identifier] << ", identifier " << identifier << endl;
    
    auto ap = new piper_vamp::client::AutoPlugin
        (m_origins[identifier].toStdString(),
         psd.pluginKey,
         float(inputSampleRate),
         0,
         m_logger);
    
    if (!ap->isOK()) {
        delete ap;
        return 0;
    }

    return ap;
}

piper_vamp::PluginStaticData
PiperVampPluginFactory::getPluginStaticData(QString identifier)
{
    if (m_pluginData.find(identifier) != m_pluginData.end()) {
        return m_pluginData[identifier];
    } else {
        return {};
    }
}

QString
PiperVampPluginFactory::getPluginCategory(QString identifier)
{
    if (m_taxonomy.find(identifier) != m_taxonomy.end()) {
        return m_taxonomy[identifier];
    } else {
        return {};
    }
}

void
PiperVampPluginFactory::populate(QString &errorMessage)
{
    QString someError;

    for (auto s: m_servers) {

        populateFrom(s, someError);

        if (someError != "" && errorMessage == "") {
            errorMessage = someError;
        }
    }
}

void
PiperVampPluginFactory::populateFrom(const HelperExecPath::HelperExec &server,
                                     QString &errorMessage)
{
    QString tag = server.tag;
    string executable = server.executable.toStdString();
    const string serverName = QFileInfo(server.executable).fileName().toStdString();
        
    DesiredSubset from;
    
    if (m_overrideDesiredExtractors.find(serverName) != m_overrideDesiredExtractors.end()) {
        const auto desired = m_overrideDesiredExtractors.at(serverName);
        if (desired.allAvailable) {
            if (desired.from.empty()) {
                from = {};
            } else {
                // ambiguous struct
                return;
            }
        } else {
            if (desired.from.empty()) {
                // ambigous
                return;
            } else {
                from = desired.from;
            }
        }
    } else {
        PluginScan *scan = PluginScan::getInstance();
        auto candidateLibraries =
            scan->getCandidateLibrariesFor(PluginScan::VampPlugin);

        SVDEBUG << "PiperVampPluginFactory: Populating from " << executable << endl;
        SVDEBUG << "INFO: Have " << candidateLibraries.size()
                << " candidate Vamp plugin libraries from scanner" << endl;
        for (const auto &c: candidateLibraries) {
            if (c.helperTag == tag) {
                string soname = QFileInfo(c.libraryPath).baseName().toStdString();
                SVDEBUG << "INFO: For tag \"" << tag << "\" giving library " << soname << endl;
                from.push_back(soname);
            }
        }

        if (from.empty()) {
            SVDEBUG << "PiperVampPluginFactory: No candidate libraries for tag \""
                 << tag << "\"";
            if (scan->scanSucceeded()) {
                // we have to assume that they all failed to load (i.e. we
                // exclude them all) rather than sending an empty list
                // (which would mean no exclusions)
                SVDEBUG << ", skipping" << endl;
                return;
            } else {
                SVDEBUG << ", but it seems the scan failed, so bumbling on anyway" << endl;
            }
        }
    }
    
    piper_vamp::client::ProcessQtTransport transport(executable, "capnp", m_logger);
    if (!transport.isOK()) {
        SVDEBUG << "PiperVampPluginFactory: Failed to start Piper process transport" << endl;
        errorMessage = QObject::tr("Could not start external plugin host");
        return;
    }

    piper_vamp::client::CapnpRRClient client(&transport, m_logger);

    piper_vamp::ListRequest req;
    req.from = from;
    
    piper_vamp::ListResponse resp;

    try {
        resp = client.listPluginData(req);
    } catch (piper_vamp::client::ServerCrashed) {
        SVDEBUG << "PiperVampPluginFactory: Piper server crashed" << endl;
        errorMessage = QObject::tr
            ("External plugin host exited unexpectedly while listing plugins");
        return;
    } catch (const std::exception &e) {
        SVDEBUG << "PiperVampPluginFactory: Exception caught: " << e.what() << endl;
        errorMessage = QObject::tr("External plugin host invocation failed: %1")
            .arg(e.what());
        return;
    }

    SVDEBUG << "PiperVampPluginFactory: server \"" << executable << "\" lists "
            << resp.available.size() << " plugin(s)" << endl;

    for (const auto &pd: resp.available) {
        
        QString identifier =
            QString("vamp:") + QString::fromStdString(pd.pluginKey);

        if (m_origins.find(identifier) != m_origins.end()) {
            // have it already, from a higher-priority server
            // (e.g. 64-bit instead of 32-bit)
            continue;
        }

        m_origins[identifier] = server.executable;
        
        m_pluginData[identifier] = pd;

        QStringList catlist;
        for (const auto &cs: pd.category) {
            catlist.push_back(QString::fromStdString(cs));
        }

        m_taxonomy[identifier] = catlist.join(" > ");
    }
}

#endif