PiperVampPluginFactory.cpp
Go to the documentation of this file.
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4  Sonic Visualiser
5  An audio file viewer and annotation editor.
6  Centre for Digital Music, Queen Mary, University of London.
7  This file copyright 2006-2016 Chris Cannam and QMUL.
8 
9  This program is free software; you can redistribute it and/or
10  modify it under the terms of the GNU General Public License as
11  published by the Free Software Foundation; either version 2 of the
12  License, or (at your option) any later version. See the file
13  COPYING included with this distribution for more information.
14 */
15 
16 #ifdef HAVE_PIPER
17 
18 #include "PiperVampPluginFactory.h"
19 #include "PluginIdentifier.h"
20 
21 #include "system/System.h"
22 
23 #include "PluginScan.h"
24 
25 #ifdef _WIN32
26 #undef VOID
27 #undef ERROR
28 #define CAPNP_LITE 1
29 #endif
30 
31 #include "vamp-client/qt/PiperAutoPlugin.h"
32 #include "vamp-client/qt/ProcessQtTransport.h"
33 #include "vamp-client/CapnpRRClient.h"
34 
35 #include <QDir>
36 #include <QFile>
37 #include <QFileInfo>
38 #include <QTextStream>
39 #include <QCoreApplication>
40 
41 #include <iostream>
42 
43 #include "base/Profiler.h"
44 #include "base/HelperExecPath.h"
45 
46 using namespace std;
47 
48 //#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1
49 
50 class PiperVampPluginFactory::Logger : public piper_vamp::client::LogCallback {
51 protected:
52  void log(std::string message) const override {
53  SVDEBUG << "PiperVampPluginFactory: " << message << endl;
54  }
55 };
56 
57 PiperVampPluginFactory::PiperVampPluginFactory() :
58  m_logger(new Logger)
59 {
60  QString serverName = "piper-vamp-simple-server";
61  float minimumVersion = 2.0;
62 
64 
65  auto servers = hep.getHelperExecutables(serverName);
66 
67  for (auto n: servers) {
68  SVDEBUG << "NOTE: PiperVampPluginFactory: Found server: "
69  << n.executable << endl;
70  if (serverMeetsMinimumVersion(n, minimumVersion)) {
71  m_servers.push_back(n);
72  } else {
73  SVCERR << "WARNING: PiperVampPluginFactory: Server at "
74  << n.executable
75  << " does not meet minimum version requirement (version >= "
76  << minimumVersion << ")" << endl;
77  }
78  }
79 
80  if (m_servers.empty()) {
81  SVDEBUG << "NOTE: No Piper Vamp servers found in installation;"
82  << " the following paths are either absent or fail "
83  << "minimum-version check:" << endl;
84  for (auto d: hep.getHelperCandidatePaths(serverName)) {
85  SVDEBUG << "NOTE: " << d << endl;
86  }
87  }
88 }
89 
90 PiperVampPluginFactory::~PiperVampPluginFactory()
91 {
92  delete m_logger;
93 }
94 
95 bool
96 PiperVampPluginFactory::serverMeetsMinimumVersion(const HelperExecPath::HelperExec &server,
97  float minimumVersion)
98 {
99  QProcess process;
100  QString executable = server.executable;
101  process.setReadChannel(QProcess::StandardOutput);
102  process.setProcessChannelMode(QProcess::ForwardedErrorChannel);
103  process.start(executable, { "--version" });
104 
105  if (!process.waitForStarted()) {
106  QProcess::ProcessError err = process.error();
107  if (err == QProcess::FailedToStart) {
108  SVCERR << "WARNING: Unable to start server " << executable
109  << " for version check" << endl;
110  } else if (err == QProcess::Crashed) {
111  SVCERR << "WARNING: Server " << executable
112  << " crashed on version check" << endl;
113  } else {
114  SVCERR << "WARNING: Server " << executable
115  << " failed on version check with error code "
116  << err << endl;
117  }
118  return false;
119  }
120  process.waitForFinished();
121 
122  QByteArray output = process.readAllStandardOutput();
123  while (output.endsWith('\n') || output.endsWith('\r')) {
124  output.chop(1);
125  }
126 
127  QString outputString(output);
128  bool ok = false;
129  float version = outputString.toFloat(&ok);
130  if (!ok) {
131  SVCERR << "WARNING: Failed to convert server version response \""
132  << outputString << "\" into one- or two-part version number"
133  << endl;
134  }
135 
136  SVDEBUG << "Server " << executable << " reports version number "
137  << version << endl;
138 
139  float eps = 1e-6f;
140  return (version >= minimumVersion ||
141  fabsf(version - minimumVersion) < eps); // arf
142 }
143 
144 vector<QString>
145 PiperVampPluginFactory::getPluginIdentifiers(QString &errorMessage)
146 {
147  Profiler profiler("PiperVampPluginFactory::getPluginIdentifiers");
148 
149  QMutexLocker locker(&m_mutex);
150 
151  if (m_servers.empty()) {
152  errorMessage = QObject::tr("External plugin host executable does not appear to be installed");
153  return {};
154  }
155 
156  if (m_pluginData.empty()) {
157  populate(errorMessage);
158  }
159 
160  vector<QString> rv;
161 
162  for (const auto &d: m_pluginData) {
163  rv.push_back(QString("vamp:") + QString::fromStdString(d.second.pluginKey));
164  }
165 
166  return rv;
167 }
168 
169 std::shared_ptr<Vamp::Plugin>
170 PiperVampPluginFactory::instantiatePlugin(QString identifier,
171  sv_samplerate_t inputSampleRate)
172 {
173  Profiler profiler("PiperVampPluginFactory::instantiatePlugin");
174 
175  if (m_origins.find(identifier) == m_origins.end()) {
176  SVCERR << "ERROR: No known server for identifier " << identifier << endl;
177  return nullptr;
178  }
179 
180  auto psd = getPluginStaticData(identifier);
181  if (psd.pluginKey == "") {
182  return nullptr;
183  }
184 
185  SVDEBUG << "PiperVampPluginFactory: Creating PiperAutoPlugin for server "
186  << m_origins[identifier] << ", identifier " << identifier << endl;
187 
188  auto ap = new piper_vamp::client::PiperAutoPlugin
189  (m_origins[identifier].toStdString(),
190  psd.pluginKey,
191  float(inputSampleRate),
192  0,
193  m_logger);
194 
195  if (!ap->isOK()) {
196  delete ap;
197  return nullptr;
198  }
199 
200  return std::shared_ptr<Vamp::Plugin>(ap);
201 }
202 
203 piper_vamp::PluginStaticData
204 PiperVampPluginFactory::getPluginStaticData(QString identifier)
205 {
206  if (m_pluginData.find(identifier) != m_pluginData.end()) {
207  return m_pluginData[identifier];
208  } else {
209  return {};
210  }
211 }
212 
213 QString
214 PiperVampPluginFactory::getPluginCategory(QString identifier)
215 {
216  if (m_taxonomy.find(identifier) != m_taxonomy.end()) {
217  return m_taxonomy[identifier];
218  } else {
219  return {};
220  }
221 }
222 
223 QString
224 PiperVampPluginFactory::getPluginLibraryPath(QString identifier)
225 {
226  // What we want to return here is the file path of the library in
227  // which the plugin was actually found -- we want to be paranoid
228  // about that and not just query
229  // Vamp::HostExt::PluginLoader::getLibraryPathForPlugin to return
230  // what the SDK thinks the likely location would be (in case our
231  // search order turns out to have been different)
232 
233  QStringList bits = identifier.split(':');
234  if (bits.size() > 1) {
235  QString soname = bits[bits.size() - 2];
236  auto i = m_libraries.find(soname);
237  if (i != m_libraries.end()) {
238  return i->second;
239  }
240  }
241  return QString();
242 }
243 
244 void
245 PiperVampPluginFactory::populate(QString &errorMessage)
246 {
247  QString someError;
248 
249  for (auto s: m_servers) {
250 
251  populateFrom(s, someError);
252 
253  if (someError != "" && errorMessage == "") {
254  errorMessage = someError;
255  }
256  }
257 }
258 
259 void
260 PiperVampPluginFactory::populateFrom(const HelperExecPath::HelperExec &server,
261  QString &errorMessage)
262 {
263  QString tag = server.tag;
264  string executable = server.executable.toStdString();
265 
267  auto candidateLibraries =
269 
270  SVDEBUG << "PiperVampPluginFactory: Populating from " << executable << endl;
271  SVDEBUG << "INFO: Have " << candidateLibraries.size()
272  << " candidate Vamp plugin libraries from scanner" << endl;
273 
274  vector<string> from;
275  for (const auto &c: candidateLibraries) {
276  if (c.helperTag == tag) {
277  string soname = QFileInfo(c.libraryPath).baseName().toStdString();
278  SVDEBUG << "INFO: For tag \"" << tag << "\" giving library " << soname << endl;
279  from.push_back(soname);
280  QString qsoname = QString::fromStdString(soname);
281  if (m_libraries.find(qsoname) == m_libraries.end()) {
282  m_libraries[qsoname] = c.libraryPath;
283  }
284  }
285  }
286 
287  if (from.empty()) {
288  SVDEBUG << "PiperVampPluginFactory: No candidate libraries for tag \""
289  << tag << "\"";
290  if (scan->scanSucceeded()) {
291  // we have to assume that they all failed to load (i.e. we
292  // exclude them all) rather than sending an empty list
293  // (which would mean no exclusions)
294  SVDEBUG << ", skipping" << endl;
295  return;
296  } else {
297  SVDEBUG << ", but it seems the scan failed, so bumbling on anyway" << endl;
298  }
299  }
300 
301  piper_vamp::client::ProcessQtTransport transport(executable, "capnp", m_logger);
302  if (!transport.isOK()) {
303  SVDEBUG << "PiperVampPluginFactory: Failed to start Piper process transport" << endl;
304  errorMessage = QObject::tr("Could not start external plugin host");
305  return;
306  }
307 
308  piper_vamp::client::CapnpRRClient client(&transport, m_logger);
309 
310  piper_vamp::ListRequest req;
311  req.from = from;
312 
313  piper_vamp::ListResponse resp;
314 
315  try {
316  resp = client.list(req);
317  } catch (const piper_vamp::client::ServerCrashed &) {
318  SVDEBUG << "PiperVampPluginFactory: Piper server crashed" << endl;
319  errorMessage = QObject::tr
320  ("External plugin host exited unexpectedly while listing plugins");
321  return;
322  } catch (const std::exception &e) {
323  SVDEBUG << "PiperVampPluginFactory: Exception caught: " << e.what() << endl;
324  errorMessage = QObject::tr("External plugin host invocation failed: %1")
325  .arg(e.what());
326  return;
327  }
328 
329  SVDEBUG << "PiperVampPluginFactory: server \"" << executable << "\" lists "
330  << resp.available.size() << " plugin(s)" << endl;
331 
332  for (const auto &pd: resp.available) {
333 
334  QString identifier =
335  QString("vamp:") + QString::fromStdString(pd.pluginKey);
336 
337  if (m_origins.find(identifier) != m_origins.end()) {
338  // have it already, from a higher-priority server
339  // (e.g. 64-bit instead of 32-bit)
340  continue;
341  }
342 
343  m_origins[identifier] = server.executable;
344 
345  m_pluginData[identifier] = pd;
346 
347  QStringList catlist;
348  for (const auto &cs: pd.category) {
349  catlist.push_back(QString::fromStdString(cs));
350  }
351 
352  m_taxonomy[identifier] = catlist.join(" > ");
353  }
354 }
355 
356 #endif
double sv_samplerate_t
Sample rate.
Definition: BaseTypes.h:51
QList< Candidate > getCandidateLibrariesFor(PluginType) const
Return the candidate plugin libraries of the given type that were found by helpers during the startup...
Definition: PluginScan.cpp:132
static PluginScan * getInstance()
Definition: PluginScan.cpp:43
Class to find helper executables that have been installed alongside the application.
#define SVDEBUG
Definition: Debug.h:106
#define SVCERR
Definition: Debug.h:109
bool scanSucceeded() const
Return true if scan() completed successfully.
Definition: PluginScan.cpp:115
Profile point instance class.
Definition: Profiler.h:93