comparison 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
comparison
equal deleted inserted replaced
7:846464771d06 8:25e00373f597
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 /*
3 Copyright (c) 2016 Queen Mary, University of London
4
5 Permission is hereby granted, free of charge, to any person
6 obtaining a copy of this software and associated documentation
7 files (the "Software"), to deal in the Software without
8 restriction, including without limitation the rights to use, copy,
9 modify, merge, publish, distribute, sublicense, and/or sell copies
10 of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12
13 The above copyright notice and this permission notice shall be
14 included in all copies or substantial portions of the Software.
15
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
20 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
21 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
24 Except as contained in this notice, the names of the Centre for
25 Digital Music and Queen Mary, University of London shall not be
26 used in advertising or otherwise to promote the sale, use or other
27 dealings in this Software without prior written authorization.
28 */
29
30 #include "plugincandidates.h"
31
32 #include <set>
33 #include <stdexcept>
34 #include <iostream>
35
36 #include <QProcess>
37 #include <QDir>
38 #include <QTime>
39
40 #if defined(_WIN32)
41 #define PLUGIN_GLOB "*.dll"
42 #elif defined(__APPLE__)
43 #define PLUGIN_GLOB "*.dylib *.so"
44 #else
45 #define PLUGIN_GLOB "*.so"
46 #endif
47
48 using namespace std;
49
50 PluginCandidates::PluginCandidates(string helperExecutableName) :
51 m_helper(helperExecutableName),
52 m_logCallback(0)
53 {
54 }
55
56 void
57 PluginCandidates::setLogCallback(LogCallback *cb)
58 {
59 m_logCallback = cb;
60 }
61
62 vector<string>
63 PluginCandidates::getCandidateLibrariesFor(string tag) const
64 {
65 if (m_candidates.find(tag) == m_candidates.end()) return {};
66 else return m_candidates.at(tag);
67 }
68
69 vector<PluginCandidates::FailureRec>
70 PluginCandidates::getFailedLibrariesFor(string tag) const
71 {
72 if (m_failures.find(tag) == m_failures.end()) return {};
73 else return m_failures.at(tag);
74 }
75
76 void
77 PluginCandidates::log(string message)
78 {
79 if (m_logCallback) m_logCallback->log("PluginCandidates: " + message);
80 }
81
82 vector<string>
83 PluginCandidates::getLibrariesInPath(vector<string> path)
84 {
85 vector<string> candidates;
86
87 for (string dirname: path) {
88
89 log("scanning directory " + dirname + "\n");
90
91 QDir dir(dirname.c_str(), PLUGIN_GLOB,
92 QDir::Name | QDir::IgnoreCase,
93 QDir::Files | QDir::Readable);
94
95 for (unsigned int i = 0; i < dir.count(); ++i) {
96 QString soname = dir.filePath(dir[i]);
97 candidates.push_back(soname.toStdString());
98 }
99 }
100
101 return candidates;
102 }
103
104 void
105 PluginCandidates::scan(string tag,
106 vector<string> pluginPath,
107 string descriptorSymbolName)
108 {
109 vector<string> libraries = getLibrariesInPath(pluginPath);
110 vector<string> remaining = libraries;
111
112 int runlimit = 20;
113 int runcount = 0;
114
115 vector<string> result;
116
117 while (result.size() < libraries.size() && runcount < runlimit) {
118 vector<string> output = runHelper(remaining, descriptorSymbolName);
119 result.insert(result.end(), output.begin(), output.end());
120 int shortfall = int(remaining.size()) - int(output.size());
121 if (shortfall > 0) {
122 // Helper bailed out for some reason presumably associated
123 // with the plugin following the last one it reported
124 // on. Add a failure entry for that one and continue with
125 // the following ones.
126 string failed = *(remaining.rbegin() + shortfall - 1);
127 log("helper output ended before result for plugin " + failed + "\n");
128 result.push_back("FAILURE|" + failed + "|Plugin load check failed or timed out");
129 remaining = vector<string>
130 (remaining.rbegin(), remaining.rbegin() + shortfall - 1);
131 }
132 ++runcount;
133 }
134
135 recordResult(tag, result);
136 }
137
138 vector<string>
139 PluginCandidates::runHelper(vector<string> libraries, string descriptor)
140 {
141 vector<string> output;
142
143 log("running helper with following library list:\n");
144 for (auto &lib: libraries) log(lib + "\n");
145
146 QProcess process;
147 process.setReadChannel(QProcess::StandardOutput);
148 process.setProcessChannelMode(QProcess::ForwardedErrorChannel);
149 process.start(m_helper.c_str(), { descriptor.c_str() });
150 if (!process.waitForStarted()) {
151 cerr << "helper failed to start" << endl;
152 throw runtime_error("plugin load helper failed to start");
153 }
154 for (auto &lib: libraries) {
155 process.write(lib.c_str(), lib.size());
156 process.write("\n", 1);
157 }
158
159 QTime t;
160 t.start();
161 int timeout = 3000; // ms
162
163 int buflen = 4096;
164 bool done = false;
165
166 while (!done) {
167 char buf[buflen];
168 qint64 linelen = process.readLine(buf, buflen);
169 if (linelen > 0) {
170 output.push_back(buf);
171 done = (output.size() == libraries.size());
172 } else if (linelen < 0) {
173 // error case
174 log("received error code while reading from helper\n");
175 done = true;
176 } else {
177 // no error, but no line read (could just be between
178 // lines, or could be eof)
179 done = (process.state() == QProcess::NotRunning);
180 if (!done) {
181 if (t.elapsed() > timeout) {
182 // this is purely an emergency measure
183 log("timeout: helper took too long, killing it\n");
184 process.kill();
185 done = true;
186 } else {
187 process.waitForReadyRead(200);
188 }
189 }
190 }
191 }
192
193 if (process.state() != QProcess::NotRunning) {
194 process.close();
195 process.waitForFinished();
196 }
197
198 return output;
199 }
200
201 void
202 PluginCandidates::recordResult(string tag, vector<string> result)
203 {
204 for (auto &r: result) {
205
206 QString s(r.c_str());
207 QStringList bits = s.split("|");
208
209 log("read output line from helper: " + r);
210
211 if (bits.size() < 2 || bits.size() > 3) {
212 log("invalid output line (wrong number of |-separated fields)\n");
213 continue;
214 }
215
216 string status = bits[0].toStdString();
217
218 string library = bits[1].toStdString();
219 if (bits.size() == 2) library = bits[1].trimmed().toStdString();
220
221 string message = "";
222 if (bits.size() > 2) message = bits[2].trimmed().toStdString();
223
224 if (status == "SUCCESS") {
225 m_candidates[tag].push_back(library);
226
227 } else if (status == "FAILURE") {
228 m_failures[tag].push_back({ library, message });
229
230 } else {
231 log("unexpected status \"" + status + "\" in output line\n");
232 }
233 }
234 }
235