PluginScan.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 
8  This program is free software; you can redistribute it and/or
9  modify it under the terms of the GNU General Public License as
10  published by the Free Software Foundation; either version 2 of the
11  License, or (at your option) any later version. See the file
12  COPYING included with this distribution for more information.
13 */
14 
15 #include "PluginScan.h"
16 
17 #include "base/Debug.h"
18 #include "base/Preferences.h"
19 #include "base/HelperExecPath.h"
20 
21 #include <sstream>
22 
23 #include <QMutex>
24 #include <QCoreApplication>
25 
26 using std::string;
27 
29 #ifdef HAVE_PLUGIN_CHECKER_HELPER
30  : public PluginCandidates::LogCallback
31 #endif
32 {
33 protected:
34  void log(std::string message)
35 #ifdef HAVE_PLUGIN_CHECKER_HELPER
36  override
37 #endif
38  {
39  SVDEBUG << "PluginScan: " << message << endl;
40  }
41 };
42 
44 {
45  static QMutex mutex;
46  static PluginScan *m_instance = nullptr;
47  mutex.lock();
48  if (!m_instance) m_instance = new PluginScan();
49  mutex.unlock();
50  return m_instance;
51 }
52 
54 }
55 
57  QMutexLocker locker(&m_mutex);
58  clear();
59  delete m_logger;
60  SVDEBUG << "PluginScan::~PluginScan completed" << endl;
61 }
62 
63 void
65 {
66 #ifdef HAVE_PLUGIN_CHECKER_HELPER
67 
68  QMutexLocker locker(&m_mutex);
69 
71 
72  HelperExecPath hep(inProcess ?
75 
76  QString helperName("vamp-plugin-load-checker");
77  auto helpers = hep.getHelperExecutables(helperName);
78 
79  clear();
80 
81  for (auto p: helpers) {
82  SVDEBUG << "NOTE: PluginScan: Found helper: " << p.executable << endl;
83  }
84 
85  if (helpers.empty()) {
86  SVDEBUG << "NOTE: No plugin checker helpers found in installation;"
87  << " found none of the following:" << endl;
88  for (auto d: hep.getHelperCandidatePaths(helperName)) {
89  SVDEBUG << "NOTE: " << d << endl;
90  }
91  }
92 
93  for (auto p: helpers) {
94  try {
96  (p.executable.toStdString(), m_logger);
97  if (m_kp.find(p.tag) != m_kp.end()) {
98  SVDEBUG << "WARNING: PluginScan::scan: Duplicate tag " << p.tag
99  << " for helpers" << endl;
100  continue;
101  }
102  m_kp[p.tag] = kp;
103  m_succeeded = true;
104  } catch (const std::exception &e) {
105  SVDEBUG << "ERROR: PluginScan::scan: " << e.what()
106  << " (with helper path = " << p.executable << ")" << endl;
107  }
108  }
109 
110  SVDEBUG << "PluginScan::scan complete" << endl;
111 #endif
112 }
113 
114 bool
116 {
117  QMutexLocker locker(&m_mutex);
118  return m_succeeded;
119 }
120 
121 void
123 {
124  for (auto &p: m_kp) {
125  delete p.second;
126  }
127  m_kp.clear();
128  m_succeeded = false;
129 }
130 
131 QList<PluginScan::Candidate>
133 #ifdef HAVE_PLUGIN_CHECKER_HELPER
134  type
135 #endif
136  ) const
137 {
138 #ifdef HAVE_PLUGIN_CHECKER_HELPER
139 
140  QMutexLocker locker(&m_mutex);
141 
142  KnownPlugins::PluginType kpt;
143  switch (type) {
144  case VampPlugin: kpt = KnownPlugins::VampPlugin; break;
145  case LADSPAPlugin: kpt = KnownPlugins::LADSPAPlugin; break;
146  case DSSIPlugin: kpt = KnownPlugins::DSSIPlugin; break;
147  default: throw std::logic_error("Inconsistency in plugin type enums");
148  }
149 
150  QList<Candidate> candidates;
151 
152  for (auto rec: m_kp) {
153 
154  KnownPluginCandidates *kp = rec.second;
155 
156  auto c = kp->getCandidateLibrariesFor(kpt);
157 
158  SVDEBUG << "PluginScan: helper \"" << kp->getHelperExecutableName()
159  << "\" likes " << c.size() << " libraries of type "
160  << kp->getTagFor(kpt) << endl;
161 
162  for (auto s: c) {
163  candidates.push_back({ s.c_str(), rec.first });
164  }
165 
166  if (type != VampPlugin) {
167  // We are only interested in querying multiple helpers
168  // when dealing with Vamp plugins, for which we can use
169  // external servers and so in some cases can support
170  // additional architectures. Other plugin formats are
171  // loaded directly and so must match the host, which is
172  // what the first helper is supposed to handle -- so
173  // break after the first one if not querying Vamp
174  break;
175  }
176  }
177 
178  return candidates;
179 
180 #else
181  return {};
182 #endif
183 }
184 
185 #ifdef HAVE_PLUGIN_CHECKER_HELPER
186 QString
187 PluginScan::formatFailureReport(QString tag,
188  std::vector<PluginCandidates::FailureRec> failures) const
189 {
190  int n = int(failures.size());
191  int i = 0;
192 
193  std::ostringstream os;
194 
195  os << "<ul>";
196  for (auto f: failures) {
197  os << "<li><code>" + f.library + "</code>";
198 
199  SVDEBUG << "PluginScan::formatFailureReport: tag is \"" << tag
200  << "\", failure code is " << int(f.code) << ", message is \""
201  << f.message << "\"" << endl;
202 
203  QString userMessage = QString::fromStdString(f.message);
204 
205  switch (f.code) {
206 
207  case PluginCheckCode::FAIL_LIBRARY_NOT_FOUND:
208  userMessage = QObject::tr("Library file could not be opened");
209  break;
210 
211  case PluginCheckCode::FAIL_WRONG_ARCHITECTURE:
212  if (tag == "64" || (sizeof(void *) == 8 && tag == "")) {
213  userMessage = QObject::tr
214  ("Library has wrong architecture - possibly a 32-bit plugin installed in a 64-bit plugin folder");
215  } else if (tag == "32" || (sizeof(void *) == 4 && tag == "")) {
216  userMessage = QObject::tr
217  ("Library has wrong architecture - possibly a 64-bit plugin installed in a 32-bit plugin folder");
218  }
219  break;
220 
221  case PluginCheckCode::FAIL_DEPENDENCY_MISSING:
222  userMessage = QObject::tr
223  ("Library depends on another library that cannot be found: %1")
224  .arg(userMessage);
225  break;
226 
227  case PluginCheckCode::FAIL_NOT_LOADABLE:
228  userMessage = QObject::tr
229  ("Library cannot be loaded: %1").arg(userMessage);
230  break;
231 
232  case PluginCheckCode::FAIL_FORBIDDEN:
233  userMessage = QObject::tr
234  ("Permission to load library was refused");
235  break;
236 
237  case PluginCheckCode::FAIL_DESCRIPTOR_MISSING:
238  userMessage = QObject::tr
239  ("Not a valid plugin library (no descriptor found)");
240  break;
241 
242  case PluginCheckCode::FAIL_NO_PLUGINS:
243  userMessage = QObject::tr
244  ("Library contains no plugins");
245  break;
246 
247  case PluginCheckCode::FAIL_OTHER:
248  if (userMessage == "") {
249  userMessage = QObject::tr
250  ("Unknown error");
251  }
252  break;
253 
254  case PluginCheckCode::SUCCESS:
255  // success shouldn't happen here!
256  break;
257  }
258 
259  os << "<br><i>" + userMessage.toStdString() + "</i>";
260  os << "</li>";
261 
262  if (n > 10) {
263  if (++i == 5) {
264  os << "<li>";
265  os << QObject::tr("... and %n further failure(s)",
266  "", n - i)
267  .toStdString();
268  os << "</li>";
269  break;
270  }
271  }
272  }
273  os << "</ul>";
274 
275  return QString::fromStdString(os.str());
276 }
277 #endif
278 
279 QString
281 {
282 #ifdef HAVE_PLUGIN_CHECKER_HELPER
283 
284  QMutexLocker locker(&m_mutex);
285 
286  if (!m_succeeded) {
287  return QObject::tr("<b>Failed to scan for plugins</b>"
288  "<p>Failed to scan for plugins at startup. Possibly "
289  "the plugin checker program was not correctly "
290  "installed alongside %1?</p>")
291  .arg(QCoreApplication::applicationName());
292  }
293  if (m_kp.empty()) {
294  return QObject::tr("<b>Did not scan for plugins</b>"
295  "<p>Apparently no scan for plugins was attempted "
296  "(internal error?)</p>");
297  }
298 
299  QString report;
300  for (auto kp: m_kp) {
301  auto failures = kp.second->getFailures();
302  if (!failures.empty()) {
303  report += formatFailureReport(kp.first, failures);
304  }
305  }
306  if (report == "") {
307  return report;
308  }
309 
310  return QObject::tr("<p>Failed to load one or more plugin libraries:</p>")
311  + report
312  + QObject::tr("<p>These plugins may be incompatible with the system, "
313  "and will be ignored during this run of %1.</p>")
314  .arg(QCoreApplication::applicationName());
315 
316 #else
317  return "";
318 #endif
319 }
320 
void log(std::string message)
Definition: PluginScan.cpp:34
void clear()
Definition: PluginScan.cpp:122
QMutex m_mutex
Definition: PluginScan.h:86
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
std::map< QString, KnownPluginCandidates * > m_kp
Definition: PluginScan.h:88
static QMutex mutex
Definition: Debug.cpp:30
bool getRunPluginsInProcess() const
Definition: Preferences.h:53
static Preferences * getInstance()
Definition: Preferences.cpp:31
static PluginScan * getInstance()
Definition: PluginScan.cpp:43
Class to find helper executables that have been installed alongside the application.
void scan()
Carry out startup scan of available plugins.
Definition: PluginScan.cpp:64
bool m_succeeded
Definition: PluginScan.h:89
#define SVDEBUG
Definition: Debug.h:106
QStringList getHelperCandidatePaths(QString basename)
Return the list of executable paths examined in the search for the helper executable with the given b...
QList< HelperExec > getHelperExecutables(QString basename)
Find all helper executables with the given base name in the bundle directory or installation location...
bool scanSucceeded() const
Return true if scan() completed successfully.
Definition: PluginScan.cpp:115
Logger * m_logger
Definition: PluginScan.h:91
QString getStartupFailureReport() const
Definition: PluginScan.cpp:280