Mercurial > hg > vamp-plugin-load-checker
diff src/plugincandidates.cpp @ 8:25e00373f597
Much renaming to ease inclusion into another project
author | Chris Cannam |
---|---|
date | Thu, 14 Apr 2016 16:52:19 +0100 |
parents | plugincandidates.cpp@61dbb18f2369 |
children | 9f62684e1911 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugincandidates.cpp Thu Apr 14 16:52:19 2016 +0100 @@ -0,0 +1,235 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + Copyright (c) 2016 Queen Mary, University of London + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music and Queen Mary, University of London shall not be + used in advertising or otherwise to promote the sale, use or other + dealings in this Software without prior written authorization. +*/ + +#include "plugincandidates.h" + +#include <set> +#include <stdexcept> +#include <iostream> + +#include <QProcess> +#include <QDir> +#include <QTime> + +#if defined(_WIN32) +#define PLUGIN_GLOB "*.dll" +#elif defined(__APPLE__) +#define PLUGIN_GLOB "*.dylib *.so" +#else +#define PLUGIN_GLOB "*.so" +#endif + +using namespace std; + +PluginCandidates::PluginCandidates(string helperExecutableName) : + m_helper(helperExecutableName), + m_logCallback(0) +{ +} + +void +PluginCandidates::setLogCallback(LogCallback *cb) +{ + m_logCallback = cb; +} + +vector<string> +PluginCandidates::getCandidateLibrariesFor(string tag) const +{ + if (m_candidates.find(tag) == m_candidates.end()) return {}; + else return m_candidates.at(tag); +} + +vector<PluginCandidates::FailureRec> +PluginCandidates::getFailedLibrariesFor(string tag) const +{ + if (m_failures.find(tag) == m_failures.end()) return {}; + else return m_failures.at(tag); +} + +void +PluginCandidates::log(string message) +{ + if (m_logCallback) m_logCallback->log("PluginCandidates: " + message); +} + +vector<string> +PluginCandidates::getLibrariesInPath(vector<string> path) +{ + vector<string> candidates; + + for (string dirname: path) { + + log("scanning directory " + dirname + "\n"); + + QDir dir(dirname.c_str(), PLUGIN_GLOB, + QDir::Name | QDir::IgnoreCase, + QDir::Files | QDir::Readable); + + for (unsigned int i = 0; i < dir.count(); ++i) { + QString soname = dir.filePath(dir[i]); + candidates.push_back(soname.toStdString()); + } + } + + return candidates; +} + +void +PluginCandidates::scan(string tag, + vector<string> pluginPath, + string descriptorSymbolName) +{ + vector<string> libraries = getLibrariesInPath(pluginPath); + vector<string> remaining = libraries; + + int runlimit = 20; + int runcount = 0; + + vector<string> result; + + while (result.size() < libraries.size() && runcount < runlimit) { + vector<string> output = runHelper(remaining, descriptorSymbolName); + result.insert(result.end(), output.begin(), output.end()); + int shortfall = int(remaining.size()) - int(output.size()); + if (shortfall > 0) { + // Helper bailed out for some reason presumably associated + // with the plugin following the last one it reported + // on. Add a failure entry for that one and continue with + // the following ones. + string failed = *(remaining.rbegin() + shortfall - 1); + log("helper output ended before result for plugin " + failed + "\n"); + result.push_back("FAILURE|" + failed + "|Plugin load check failed or timed out"); + remaining = vector<string> + (remaining.rbegin(), remaining.rbegin() + shortfall - 1); + } + ++runcount; + } + + recordResult(tag, result); +} + +vector<string> +PluginCandidates::runHelper(vector<string> libraries, string descriptor) +{ + vector<string> output; + + log("running helper with following library list:\n"); + for (auto &lib: libraries) log(lib + "\n"); + + QProcess process; + process.setReadChannel(QProcess::StandardOutput); + process.setProcessChannelMode(QProcess::ForwardedErrorChannel); + process.start(m_helper.c_str(), { descriptor.c_str() }); + if (!process.waitForStarted()) { + cerr << "helper failed to start" << endl; + throw runtime_error("plugin load helper failed to start"); + } + for (auto &lib: libraries) { + process.write(lib.c_str(), lib.size()); + process.write("\n", 1); + } + + QTime t; + t.start(); + int timeout = 3000; // ms + + int buflen = 4096; + bool done = false; + + while (!done) { + char buf[buflen]; + qint64 linelen = process.readLine(buf, buflen); + if (linelen > 0) { + output.push_back(buf); + done = (output.size() == libraries.size()); + } else if (linelen < 0) { + // error case + log("received error code while reading from helper\n"); + done = true; + } else { + // no error, but no line read (could just be between + // lines, or could be eof) + done = (process.state() == QProcess::NotRunning); + if (!done) { + if (t.elapsed() > timeout) { + // this is purely an emergency measure + log("timeout: helper took too long, killing it\n"); + process.kill(); + done = true; + } else { + process.waitForReadyRead(200); + } + } + } + } + + if (process.state() != QProcess::NotRunning) { + process.close(); + process.waitForFinished(); + } + + return output; +} + +void +PluginCandidates::recordResult(string tag, vector<string> result) +{ + for (auto &r: result) { + + QString s(r.c_str()); + QStringList bits = s.split("|"); + + log("read output line from helper: " + r); + + if (bits.size() < 2 || bits.size() > 3) { + log("invalid output line (wrong number of |-separated fields)\n"); + continue; + } + + string status = bits[0].toStdString(); + + string library = bits[1].toStdString(); + if (bits.size() == 2) library = bits[1].trimmed().toStdString(); + + string message = ""; + if (bits.size() > 2) message = bits[2].trimmed().toStdString(); + + if (status == "SUCCESS") { + m_candidates[tag].push_back(library); + + } else if (status == "FAILURE") { + m_failures[tag].push_back({ library, message }); + + } else { + log("unexpected status \"" + status + "\" in output line\n"); + } + } +} +