annotate plugin/PluginScan.cpp @ 1838:403e961ec6ab

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