Chris@66
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@66
|
2 /*
|
Chris@66
|
3 Copyright (c) 2020 Queen Mary, University of London
|
Chris@66
|
4
|
Chris@66
|
5 Permission is hereby granted, free of charge, to any person
|
Chris@66
|
6 obtaining a copy of this software and associated documentation
|
Chris@66
|
7 files (the "Software"), to deal in the Software without
|
Chris@66
|
8 restriction, including without limitation the rights to use, copy,
|
Chris@66
|
9 modify, merge, publish, distribute, sublicense, and/or sell copies
|
Chris@66
|
10 of the Software, and to permit persons to whom the Software is
|
Chris@66
|
11 furnished to do so, subject to the following conditions:
|
Chris@66
|
12
|
Chris@66
|
13 The above copyright notice and this permission notice shall be
|
Chris@66
|
14 included in all copies or substantial portions of the Software.
|
Chris@66
|
15
|
Chris@66
|
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
Chris@66
|
17 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
Chris@66
|
18 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
Chris@66
|
19 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
Chris@66
|
20 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
Chris@66
|
21 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
Chris@66
|
22 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
Chris@66
|
23
|
Chris@66
|
24 Except as contained in this notice, the names of the Centre for
|
Chris@66
|
25 Digital Music and Queen Mary, University of London shall not be
|
Chris@66
|
26 used in advertising or otherwise to promote the sale, use or other
|
Chris@66
|
27 dealings in this Software without prior written authorization.
|
Chris@66
|
28 */
|
Chris@33
|
29
|
Chris@33
|
30 #include <QApplication>
|
Chris@33
|
31 #include <QString>
|
Chris@33
|
32 #include <QFile>
|
Chris@33
|
33 #include <QDir>
|
Chris@33
|
34
|
Chris@42
|
35 #include <QDialog>
|
Chris@42
|
36 #include <QFrame>
|
Chris@42
|
37 #include <QVBoxLayout>
|
Chris@42
|
38 #include <QCheckBox>
|
Chris@42
|
39 #include <QDialogButtonBox>
|
Chris@46
|
40 #include <QLabel>
|
Chris@51
|
41 #include <QFont>
|
Chris@51
|
42 #include <QFontInfo>
|
Chris@67
|
43 #include <QTemporaryFile>
|
Chris@67
|
44 #include <QMutex>
|
Chris@67
|
45 #include <QMutexLocker>
|
Chris@67
|
46 #include <QProcess>
|
Chris@72
|
47 #include <QToolButton>
|
Chris@79
|
48 #include <QPushButton>
|
Chris@72
|
49 #include <QMessageBox>
|
Chris@74
|
50 #include <QSvgRenderer>
|
Chris@74
|
51 #include <QPainter>
|
Chris@74
|
52 #include <QFontMetrics>
|
Chris@79
|
53 #include <QSpacerItem>
|
Chris@42
|
54
|
Chris@41
|
55 #include <vamp-hostsdk/PluginHostAdapter.h>
|
Chris@41
|
56
|
Chris@43
|
57 #include <dataquay/BasicStore.h>
|
Chris@43
|
58 #include <dataquay/RDFException.h>
|
Chris@43
|
59
|
Chris@33
|
60 #include <iostream>
|
Chris@58
|
61 #include <memory>
|
Chris@43
|
62 #include <set>
|
Chris@43
|
63
|
Chris@51
|
64 #include "base/Debug.h"
|
Chris@51
|
65
|
Chris@79
|
66 #include "version.h"
|
Chris@79
|
67
|
Chris@33
|
68 using namespace std;
|
Chris@43
|
69 using namespace Dataquay;
|
Chris@32
|
70
|
Chris@42
|
71 QString
|
Chris@42
|
72 getDefaultInstallDirectory()
|
Chris@32
|
73 {
|
Chris@41
|
74 auto pathList = Vamp::PluginHostAdapter::getPluginPath();
|
Chris@41
|
75 if (pathList.empty()) {
|
Chris@51
|
76 SVCERR << "Failed to look up Vamp plugin path" << endl;
|
Chris@42
|
77 return QString();
|
Chris@41
|
78 }
|
Chris@41
|
79
|
Chris@42
|
80 auto firstPath = *pathList.begin();
|
Chris@42
|
81 QString target = QString::fromUtf8(firstPath.c_str(), firstPath.size());
|
Chris@42
|
82 return target;
|
Chris@42
|
83 }
|
Chris@42
|
84
|
Chris@42
|
85 QStringList
|
Chris@42
|
86 getPluginLibraryList()
|
Chris@42
|
87 {
|
Chris@33
|
88 QDir dir(":out/");
|
Chris@33
|
89 auto entries = dir.entryList({ "*.so", "*.dll", "*.dylib" });
|
Chris@33
|
90
|
Chris@33
|
91 for (auto e: entries) {
|
Chris@51
|
92 SVCERR << e.toStdString() << endl;
|
Chris@33
|
93 }
|
Chris@33
|
94
|
Chris@42
|
95 return entries;
|
Chris@42
|
96 }
|
Chris@33
|
97
|
Chris@50
|
98 void
|
Chris@50
|
99 loadLibraryRdf(BasicStore &store, QString filename)
|
Chris@50
|
100 {
|
Chris@50
|
101 QFile f(filename);
|
Chris@50
|
102 if (!f.open(QFile::ReadOnly | QFile::Text)) {
|
Chris@51
|
103 SVCERR << "Failed to open RDF resource file "
|
Chris@51
|
104 << filename.toStdString() << endl;
|
Chris@50
|
105 return;
|
Chris@50
|
106 }
|
Chris@50
|
107
|
Chris@50
|
108 QByteArray content = f.readAll();
|
Chris@50
|
109 f.close();
|
Chris@50
|
110
|
Chris@50
|
111 try {
|
Chris@50
|
112 store.importString(QString::fromUtf8(content),
|
Chris@50
|
113 Uri("file:" + filename),
|
Chris@50
|
114 BasicStore::ImportIgnoreDuplicates);
|
Chris@50
|
115 } catch (const RDFException &ex) {
|
Chris@51
|
116 SVCERR << "Failed to import RDF resource file "
|
Chris@51
|
117 << filename.toStdString() << ": " << ex.what() << endl;
|
Chris@50
|
118 }
|
Chris@50
|
119 }
|
Chris@50
|
120
|
Chris@43
|
121 unique_ptr<BasicStore>
|
Chris@43
|
122 loadLibrariesRdf()
|
Chris@43
|
123 {
|
Chris@43
|
124 unique_ptr<BasicStore> store(new BasicStore);
|
Chris@43
|
125
|
Chris@50
|
126 vector<QString> dirs { ":rdf/plugins", ":out" };
|
Chris@43
|
127
|
Chris@50
|
128 for (auto d: dirs) {
|
Chris@50
|
129 for (auto e: QDir(d).entryList({ "*.ttl", "*.n3" })) {
|
Chris@50
|
130 loadLibraryRdf(*store, d + "/" + e);
|
Chris@43
|
131 }
|
Chris@43
|
132 }
|
Chris@43
|
133
|
Chris@43
|
134 return store;
|
Chris@43
|
135 }
|
Chris@43
|
136
|
Chris@43
|
137 struct LibraryInfo {
|
Chris@43
|
138 QString id;
|
Chris@43
|
139 QString fileName;
|
Chris@43
|
140 QString title;
|
Chris@43
|
141 QString maker;
|
Chris@43
|
142 QString description;
|
Chris@78
|
143 QString page;
|
Chris@47
|
144 QStringList pluginTitles;
|
Chris@67
|
145 map<QString, int> pluginVersions; // id -> version
|
Chris@74
|
146 QString licence;
|
Chris@43
|
147 };
|
Chris@43
|
148
|
Chris@74
|
149 QString
|
Chris@74
|
150 identifyLicence(QString libraryBasename)
|
Chris@74
|
151 {
|
Chris@74
|
152 QString licenceFile = QString(":out/%1_COPYING.txt").arg(libraryBasename);
|
Chris@74
|
153
|
Chris@74
|
154 QFile f(licenceFile);
|
Chris@74
|
155 if (!f.open(QFile::ReadOnly | QFile::Text)) {
|
Chris@74
|
156 SVCERR << "Failed to open licence file "
|
Chris@74
|
157 << licenceFile.toStdString() << endl;
|
Chris@74
|
158 return {};
|
Chris@74
|
159 }
|
Chris@74
|
160
|
Chris@74
|
161 QByteArray content = f.readAll();
|
Chris@74
|
162 f.close();
|
Chris@74
|
163
|
Chris@74
|
164 QString licenceText = QString::fromUtf8(content);
|
Chris@74
|
165
|
Chris@74
|
166 QString gpl = "GNU General Public License";
|
Chris@74
|
167 QString agpl = "GNU Affero General Public License";
|
Chris@74
|
168 QString apache = "Apache License";
|
Chris@74
|
169 QString mit = "MIT License";
|
Chris@74
|
170
|
Chris@74
|
171 // NB these are not expected to correctly identify any licence! We
|
Chris@74
|
172 // know we have only a limited set here. But we do want to
|
Chris@74
|
173 // determine this from the actual licence text included with the
|
Chris@74
|
174 // plugin distribution, not just from e.g. RDF metadata
|
Chris@74
|
175
|
Chris@74
|
176 if (licenceText.contains(gpl.toUpper(), Qt::CaseSensitive)) {
|
Chris@74
|
177 if (licenceText.contains("Version 3, 29 June 2007")) {
|
Chris@74
|
178 return QString("%1, version 3").arg(gpl);
|
Chris@74
|
179 } else if (licenceText.contains("Version 2, June 1991")) {
|
Chris@74
|
180 return QString("%1, version 2").arg(gpl);
|
Chris@74
|
181 } else {
|
Chris@74
|
182 return gpl;
|
Chris@74
|
183 }
|
Chris@74
|
184 }
|
Chris@74
|
185 if (licenceText.contains(agpl.toUpper(), Qt::CaseSensitive)) {
|
Chris@74
|
186 return agpl;
|
Chris@74
|
187 }
|
Chris@74
|
188 if (licenceText.contains(apache)) {
|
Chris@74
|
189 return apache;
|
Chris@74
|
190 }
|
Chris@74
|
191 if (licenceText.contains("Permission is hereby granted, free of charge, to any person")) {
|
Chris@74
|
192 return mit;
|
Chris@74
|
193 }
|
Chris@74
|
194
|
Chris@74
|
195 SVCERR << "Didn't recognise licence for " << libraryBasename << endl;
|
Chris@74
|
196
|
Chris@74
|
197 return {};
|
Chris@74
|
198 }
|
Chris@74
|
199
|
Chris@43
|
200 vector<LibraryInfo>
|
Chris@43
|
201 getLibraryInfo(const Store &store, QStringList libraries)
|
Chris@43
|
202 {
|
Chris@43
|
203 /* e.g.
|
Chris@43
|
204
|
Chris@43
|
205 plugbase:library a vamp:PluginLibrary ;
|
Chris@43
|
206 vamp:identifier "qm-vamp-plugins" ;
|
Chris@43
|
207 dc:title "Queen Mary plugin set"
|
Chris@43
|
208 */
|
Chris@43
|
209
|
Chris@43
|
210 Triples tt = store.match(Triple(Node(),
|
Chris@43
|
211 Uri("a"),
|
Chris@43
|
212 store.expand("vamp:PluginLibrary")));
|
Chris@43
|
213
|
Chris@67
|
214 map<QString, QString> wanted; // basename -> full lib name
|
Chris@43
|
215 for (auto lib: libraries) {
|
Chris@43
|
216 wanted[QFileInfo(lib).baseName()] = lib;
|
Chris@43
|
217 }
|
Chris@43
|
218
|
Chris@43
|
219 vector<LibraryInfo> results;
|
Chris@43
|
220
|
Chris@43
|
221 for (auto t: tt) {
|
Chris@43
|
222
|
Chris@43
|
223 Node libId = store.complete(Triple(t.subject(),
|
Chris@43
|
224 store.expand("vamp:identifier"),
|
Chris@43
|
225 Node()));
|
Chris@43
|
226 if (libId.type != Node::Literal) {
|
Chris@43
|
227 continue;
|
Chris@43
|
228 }
|
Chris@43
|
229 auto wi = wanted.find(libId.value);
|
Chris@43
|
230 if (wi == wanted.end()) {
|
Chris@43
|
231 continue;
|
Chris@43
|
232 }
|
Chris@74
|
233
|
Chris@50
|
234 Node title = store.complete(Triple(t.subject(),
|
Chris@50
|
235 store.expand("dc:title"),
|
Chris@50
|
236 Node()));
|
Chris@50
|
237 if (title.type != Node::Literal) {
|
Chris@50
|
238 continue;
|
Chris@50
|
239 }
|
Chris@50
|
240
|
Chris@43
|
241 LibraryInfo info;
|
Chris@43
|
242 info.id = wi->first;
|
Chris@43
|
243 info.fileName = wi->second;
|
Chris@50
|
244 info.title = title.value;
|
Chris@43
|
245
|
Chris@43
|
246 Node maker = store.complete(Triple(t.subject(),
|
Chris@43
|
247 store.expand("foaf:maker"),
|
Chris@43
|
248 Node()));
|
Chris@43
|
249 if (maker.type == Node::Literal) {
|
Chris@43
|
250 info.maker = maker.value;
|
Chris@46
|
251 } else if (maker != Node()) {
|
Chris@46
|
252 maker = store.complete(Triple(maker,
|
Chris@46
|
253 store.expand("foaf:name"),
|
Chris@46
|
254 Node()));
|
Chris@46
|
255 if (maker.type == Node::Literal) {
|
Chris@46
|
256 info.maker = maker.value;
|
Chris@46
|
257 }
|
Chris@43
|
258 }
|
Chris@46
|
259
|
Chris@43
|
260 Node desc = store.complete(Triple(t.subject(),
|
Chris@43
|
261 store.expand("dc:description"),
|
Chris@43
|
262 Node()));
|
Chris@43
|
263 if (desc.type == Node::Literal) {
|
Chris@43
|
264 info.description = desc.value;
|
Chris@43
|
265 }
|
Chris@78
|
266
|
Chris@78
|
267 Node page = store.complete(Triple(t.subject(),
|
Chris@78
|
268 store.expand("foaf:page"),
|
Chris@78
|
269 Node()));
|
Chris@79
|
270 if (page.type == Node::URI) {
|
Chris@79
|
271 info.page = page.value;
|
Chris@78
|
272 }
|
Chris@43
|
273
|
Chris@47
|
274 Triples pp = store.match(Triple(t.subject(),
|
Chris@47
|
275 store.expand("vamp:available_plugin"),
|
Chris@47
|
276 Node()));
|
Chris@47
|
277 for (auto p: pp) {
|
Chris@47
|
278 Node ptitle = store.complete(Triple(p.object(),
|
Chris@47
|
279 store.expand("dc:title"),
|
Chris@47
|
280 Node()));
|
Chris@47
|
281 if (ptitle.type == Node::Literal) {
|
Chris@47
|
282 info.pluginTitles.push_back(ptitle.value);
|
Chris@47
|
283 }
|
Chris@67
|
284
|
Chris@67
|
285 Node pident = store.complete(Triple(p.object(),
|
Chris@67
|
286 store.expand("vamp:identifier"),
|
Chris@67
|
287 Node()));
|
Chris@67
|
288 Node pversion = store.complete(Triple(p.object(),
|
Chris@67
|
289 store.expand("owl:versionInfo"),
|
Chris@67
|
290 Node()));
|
Chris@67
|
291 if (pident.type == Node::Literal &&
|
Chris@67
|
292 pversion.type == Node::Literal) {
|
Chris@67
|
293 bool ok = false;
|
Chris@67
|
294 int version = pversion.value.toInt(&ok);
|
Chris@67
|
295 if (ok) {
|
Chris@67
|
296 info.pluginVersions[pident.value] = version;
|
Chris@67
|
297 }
|
Chris@67
|
298 }
|
Chris@47
|
299 }
|
Chris@74
|
300
|
Chris@74
|
301 info.licence = identifyLicence(libId.value);
|
Chris@74
|
302 SVCERR << "licence = " << info.licence << endl;
|
Chris@47
|
303
|
Chris@43
|
304 results.push_back(info);
|
Chris@50
|
305 wanted.erase(libId.value);
|
Chris@43
|
306 }
|
Chris@43
|
307
|
Chris@50
|
308 for (auto wp: wanted) {
|
Chris@51
|
309 SVCERR << "Failed to find any RDF information about library "
|
Chris@51
|
310 << wp.second << endl;
|
Chris@50
|
311 }
|
Chris@50
|
312
|
Chris@43
|
313 return results;
|
Chris@43
|
314 }
|
Chris@43
|
315
|
Chris@67
|
316 struct TempFileDeleter {
|
Chris@67
|
317 ~TempFileDeleter() {
|
Chris@67
|
318 if (tempFile != "") {
|
Chris@67
|
319 QFile(tempFile).remove();
|
Chris@67
|
320 }
|
Chris@67
|
321 }
|
Chris@67
|
322 QString tempFile;
|
Chris@67
|
323 };
|
Chris@67
|
324
|
Chris@67
|
325 map<QString, int>
|
Chris@70
|
326 getLibraryPluginVersions(QString libraryFilePath)
|
Chris@67
|
327 {
|
Chris@67
|
328 static QMutex mutex;
|
Chris@67
|
329 static QString tempFileName;
|
Chris@67
|
330 static TempFileDeleter deleter;
|
Chris@67
|
331 static bool initHappened = false, initSucceeded = false;
|
Chris@67
|
332
|
Chris@67
|
333 QMutexLocker locker (&mutex);
|
Chris@67
|
334
|
Chris@67
|
335 if (!initHappened) {
|
Chris@67
|
336 initHappened = true;
|
Chris@67
|
337
|
Chris@67
|
338 QTemporaryFile tempFile;
|
Chris@67
|
339 tempFile.setAutoRemove(false);
|
Chris@67
|
340 if (!tempFile.open()) {
|
Chris@67
|
341 SVCERR << "ERROR: Failed to open a temporary file" << endl;
|
Chris@67
|
342 return {};
|
Chris@67
|
343 }
|
Chris@67
|
344
|
Chris@67
|
345 // We can't make the QTemporaryFile static, as it will hold
|
Chris@67
|
346 // the file open and that prevents us from executing it. Hence
|
Chris@67
|
347 // the separate deleter.
|
Chris@67
|
348
|
Chris@67
|
349 tempFileName = tempFile.fileName();
|
Chris@67
|
350 deleter.tempFile = tempFileName;
|
Chris@67
|
351
|
Chris@67
|
352 #ifdef Q_OS_WIN32
|
Chris@67
|
353 QString helperPath = ":out/get-version.exe";
|
Chris@67
|
354 #else
|
Chris@67
|
355 QString helperPath = ":out/get-version";
|
Chris@67
|
356 #endif
|
Chris@67
|
357 QFile helper(helperPath);
|
Chris@67
|
358 if (!helper.open(QFile::ReadOnly)) {
|
Chris@67
|
359 SVCERR << "ERROR: Failed to read helper code" << endl;
|
Chris@67
|
360 return {};
|
Chris@67
|
361 }
|
Chris@67
|
362 QByteArray content = helper.readAll();
|
Chris@67
|
363 helper.close();
|
Chris@67
|
364
|
Chris@67
|
365 if (tempFile.write(content) != content.size()) {
|
Chris@67
|
366 SVCERR << "ERROR: Incomplete write to temporary file" << endl;
|
Chris@67
|
367 return {};
|
Chris@67
|
368 }
|
Chris@67
|
369 tempFile.close();
|
Chris@67
|
370
|
Chris@67
|
371 if (!QFile::setPermissions
|
Chris@67
|
372 (tempFileName,
|
Chris@67
|
373 QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)) {
|
Chris@67
|
374 SVCERR << "ERROR: Failed to set execute permission on helper "
|
Chris@67
|
375 << tempFileName << endl;
|
Chris@67
|
376 return {};
|
Chris@67
|
377 }
|
Chris@67
|
378
|
Chris@67
|
379 initSucceeded = true;
|
Chris@67
|
380 }
|
Chris@67
|
381
|
Chris@67
|
382 if (!initSucceeded) {
|
Chris@67
|
383 return {};
|
Chris@67
|
384 }
|
Chris@67
|
385
|
Chris@67
|
386 QProcess process;
|
Chris@67
|
387 process.start(tempFileName, { libraryFilePath });
|
Chris@67
|
388
|
Chris@67
|
389 if (!process.waitForStarted()) {
|
Chris@67
|
390 QProcess::ProcessError err = process.error();
|
Chris@67
|
391 if (err == QProcess::FailedToStart) {
|
Chris@67
|
392 SVCERR << "Unable to start helper process " << tempFileName << endl;
|
Chris@67
|
393 } else if (err == QProcess::Crashed) {
|
Chris@67
|
394 SVCERR << "Helper process " << tempFileName
|
Chris@67
|
395 << " crashed on startup" << endl;
|
Chris@67
|
396 } else {
|
Chris@67
|
397 SVCERR << "Helper process " << tempFileName
|
Chris@67
|
398 << " failed on startup with error code " << err << endl;
|
Chris@67
|
399 }
|
Chris@67
|
400 return {};
|
Chris@67
|
401 }
|
Chris@67
|
402 process.waitForFinished();
|
Chris@67
|
403
|
Chris@67
|
404 QByteArray stdOut = process.readAllStandardOutput();
|
Chris@67
|
405 QByteArray stdErr = process.readAllStandardError();
|
Chris@67
|
406
|
Chris@67
|
407 QString errStr = QString::fromUtf8(stdErr);
|
Chris@67
|
408 if (!errStr.isEmpty()) {
|
Chris@67
|
409 SVCERR << "Note: Helper process stderr follows:" << endl;
|
Chris@67
|
410 SVCERR << errStr << endl;
|
Chris@67
|
411 SVCERR << "Note: Helper process stderr ends" << endl;
|
Chris@67
|
412 }
|
Chris@67
|
413
|
Chris@67
|
414 QStringList lines = QString::fromUtf8(stdOut).split
|
Chris@67
|
415 (QRegExp("[\\r\\n]+"), QString::SkipEmptyParts);
|
Chris@67
|
416 map<QString, int> versions;
|
Chris@67
|
417 for (QString line: lines) {
|
Chris@67
|
418 QStringList parts = line.split(":");
|
Chris@67
|
419 if (parts.size() != 2) {
|
Chris@67
|
420 SVCERR << "Unparseable output line: " << line << endl;
|
Chris@67
|
421 continue;
|
Chris@67
|
422 }
|
Chris@67
|
423 bool ok = false;
|
Chris@67
|
424 int version = parts[1].toInt(&ok);
|
Chris@67
|
425 if (!ok) {
|
Chris@67
|
426 SVCERR << "Unparseable version number in line: " << line << endl;
|
Chris@67
|
427 continue;
|
Chris@67
|
428 }
|
Chris@67
|
429 versions[parts[0]] = version;
|
Chris@67
|
430 }
|
Chris@67
|
431
|
Chris@67
|
432 return versions;
|
Chris@67
|
433 }
|
Chris@67
|
434
|
Chris@67
|
435 bool isLibraryNewer(map<QString, int> a, map<QString, int> b)
|
Chris@67
|
436 {
|
Chris@67
|
437 // a and b are maps from plugin id to plugin version for libraries
|
Chris@67
|
438 // A and B. (There is no overarching library version number.) We
|
Chris@67
|
439 // deem library A to be newer than library B if:
|
Chris@67
|
440 //
|
Chris@67
|
441 // 1. A contains a plugin id that is also in B, whose version in
|
Chris@67
|
442 // A is newer than that in B, or
|
Chris@67
|
443 //
|
Chris@67
|
444 // 2. B is not newer than A according to rule 1, and neither A or
|
Chris@67
|
445 // B is empty, and A contains a plugin id that is not in B, and B
|
Chris@67
|
446 // does not contain any plugin id that is not in A
|
Chris@67
|
447 //
|
Chris@67
|
448 // (The not-empty part of rule 2 is just to avoid false positives
|
Chris@67
|
449 // when a library or its metadata could not be read at all.)
|
Chris@67
|
450
|
Chris@67
|
451 auto containsANewerPlugin = [](const map<QString, int> &m1,
|
Chris@67
|
452 const map<QString, int> &m2) {
|
Chris@67
|
453 for (auto p: m1) {
|
Chris@67
|
454 if (m2.find(p.first) != m2.end() &&
|
Chris@67
|
455 p.second > m2.at(p.first)) {
|
Chris@67
|
456 return true;
|
Chris@67
|
457 }
|
Chris@67
|
458 }
|
Chris@67
|
459 return false;
|
Chris@67
|
460 };
|
Chris@67
|
461
|
Chris@67
|
462 auto containsANovelPlugin = [](const map<QString, int> &m1,
|
Chris@67
|
463 const map<QString, int> &m2) {
|
Chris@67
|
464 for (auto p: m1) {
|
Chris@67
|
465 if (m2.find(p.first) == m2.end()) {
|
Chris@67
|
466 return true;
|
Chris@67
|
467 }
|
Chris@67
|
468 }
|
Chris@67
|
469 return false;
|
Chris@67
|
470 };
|
Chris@67
|
471
|
Chris@67
|
472 if (containsANewerPlugin(a, b)) {
|
Chris@67
|
473 return true;
|
Chris@67
|
474 }
|
Chris@67
|
475
|
Chris@67
|
476 if (!containsANewerPlugin(b, a) &&
|
Chris@67
|
477 !a.empty() &&
|
Chris@67
|
478 !b.empty() &&
|
Chris@67
|
479 containsANovelPlugin(a, b) &&
|
Chris@67
|
480 !containsANovelPlugin(b, a)) {
|
Chris@67
|
481 return true;
|
Chris@67
|
482 }
|
Chris@67
|
483
|
Chris@67
|
484 return false;
|
Chris@67
|
485 }
|
Chris@67
|
486
|
Chris@67
|
487 QString
|
Chris@67
|
488 versionsString(const map<QString, int> &vv)
|
Chris@67
|
489 {
|
Chris@67
|
490 QStringList pv;
|
Chris@67
|
491 for (auto v: vv) {
|
Chris@67
|
492 pv.push_back(QString("%1:%2").arg(v.first).arg(v.second));
|
Chris@67
|
493 }
|
Chris@67
|
494 return "{ " + pv.join(", ") + " }";
|
Chris@67
|
495 }
|
Chris@67
|
496
|
Chris@75
|
497 enum class RelativeStatus {
|
Chris@75
|
498 New,
|
Chris@75
|
499 Same,
|
Chris@75
|
500 Upgrade,
|
Chris@75
|
501 Downgrade,
|
Chris@75
|
502 TargetNotLoadable
|
Chris@75
|
503 };
|
Chris@75
|
504
|
Chris@75
|
505 QString
|
Chris@75
|
506 relativeStatusLabel(RelativeStatus status) {
|
Chris@75
|
507 switch (status) {
|
Chris@76
|
508 case RelativeStatus::New: return QObject::tr("Not yet installed");
|
Chris@76
|
509 case RelativeStatus::Same: return QObject::tr("Already installed");
|
Chris@76
|
510 case RelativeStatus::Upgrade: return QObject::tr("Update");
|
Chris@76
|
511 case RelativeStatus::Downgrade: return QObject::tr("Newer version installed");
|
Chris@76
|
512 case RelativeStatus::TargetNotLoadable: return QObject::tr("<unknown>");
|
Chris@79
|
513 default: return {};
|
Chris@75
|
514 }
|
Chris@75
|
515 }
|
Chris@75
|
516
|
Chris@75
|
517 RelativeStatus
|
Chris@75
|
518 getRelativeStatus(LibraryInfo info, QString targetDir)
|
Chris@75
|
519 {
|
Chris@75
|
520 QString destination = targetDir + "/" + info.fileName;
|
Chris@75
|
521
|
Chris@75
|
522 RelativeStatus status = RelativeStatus::New;
|
Chris@75
|
523
|
Chris@75
|
524 SVCERR << "\ngetRelativeStatus: " << info.fileName << ":\n";
|
Chris@75
|
525
|
Chris@75
|
526 if (QFileInfo(destination).exists()) {
|
Chris@75
|
527
|
Chris@75
|
528 auto installed = getLibraryPluginVersions(destination);
|
Chris@75
|
529
|
Chris@75
|
530 SVCERR << " * installed: " << versionsString(installed)
|
Chris@75
|
531 << "\n * packaged: " << versionsString(info.pluginVersions)
|
Chris@75
|
532 << endl;
|
Chris@75
|
533
|
Chris@75
|
534 status = RelativeStatus::Same;
|
Chris@75
|
535
|
Chris@75
|
536 if (installed.empty()) {
|
Chris@75
|
537 status = RelativeStatus::TargetNotLoadable;
|
Chris@75
|
538 }
|
Chris@75
|
539
|
Chris@75
|
540 if (isLibraryNewer(installed, info.pluginVersions)) {
|
Chris@75
|
541 status = RelativeStatus::Downgrade;
|
Chris@75
|
542 }
|
Chris@75
|
543
|
Chris@75
|
544 if (isLibraryNewer(info.pluginVersions, installed)) {
|
Chris@75
|
545 status = RelativeStatus::Upgrade;
|
Chris@75
|
546 }
|
Chris@75
|
547 }
|
Chris@75
|
548
|
Chris@75
|
549 SVCERR << " - relative status: " << relativeStatusLabel(status) << endl;
|
Chris@75
|
550
|
Chris@75
|
551 return status;
|
Chris@75
|
552 }
|
Chris@75
|
553
|
Chris@42
|
554 void
|
Chris@75
|
555 installLibrary(LibraryInfo info, QString targetDir)
|
Chris@42
|
556 {
|
Chris@75
|
557 QString library = info.fileName;
|
Chris@52
|
558 QString source = ":out";
|
Chris@52
|
559 QFile f(source + "/" + library);
|
Chris@75
|
560 QString destination = targetDir + "/" + library;
|
Chris@67
|
561
|
Chris@67
|
562 if (QFileInfo(destination).exists()) {
|
Chris@70
|
563 auto installed = getLibraryPluginVersions(destination);
|
Chris@68
|
564 } else {
|
Chris@68
|
565 SVCERR << "Note: library " << library
|
Chris@68
|
566 << " is not yet installed, not comparing versions" << endl;
|
Chris@67
|
567 }
|
Chris@52
|
568
|
Chris@51
|
569 SVCERR << "Copying " << library.toStdString() << " to "
|
Chris@51
|
570 << destination.toStdString() << "..." << endl;
|
Chris@42
|
571 if (!f.copy(destination)) {
|
Chris@51
|
572 SVCERR << "Failed to copy " << library.toStdString()
|
Chris@51
|
573 << " to target " << destination.toStdString() << endl;
|
Chris@42
|
574 return;
|
Chris@42
|
575 }
|
Chris@42
|
576 if (!QFile::setPermissions
|
Chris@42
|
577 (destination,
|
Chris@42
|
578 QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner |
|
Chris@42
|
579 QFile::ReadGroup | QFile::ExeGroup |
|
Chris@42
|
580 QFile::ReadOther | QFile::ExeOther)) {
|
Chris@51
|
581 SVCERR << "Failed to set permissions on "
|
Chris@51
|
582 << library.toStdString() << endl;
|
Chris@42
|
583 return;
|
Chris@42
|
584 }
|
Chris@52
|
585
|
Chris@52
|
586 QString base = QFileInfo(library).baseName();
|
Chris@52
|
587 QDir dir(source);
|
Chris@52
|
588 auto entries = dir.entryList({ base + "*" });
|
Chris@52
|
589 for (auto e: entries) {
|
Chris@52
|
590 if (e == library) continue;
|
Chris@75
|
591 QString destination = targetDir + "/" + e;
|
Chris@52
|
592 SVCERR << "Copying " << e.toStdString() << " to "
|
Chris@52
|
593 << destination.toStdString() << "..." << endl;
|
Chris@52
|
594 if (!QFile(source + "/" + e).copy(destination)) {
|
Chris@52
|
595 SVCERR << "Failed to copy " << e.toStdString()
|
Chris@52
|
596 << " to target " << destination.toStdString()
|
Chris@52
|
597 << " (ignoring)" << endl;
|
Chris@68
|
598 continue;
|
Chris@68
|
599 }
|
Chris@68
|
600 if (!QFile::setPermissions
|
Chris@68
|
601 (destination,
|
Chris@68
|
602 QFile::ReadOwner | QFile::WriteOwner |
|
Chris@68
|
603 QFile::ReadGroup |
|
Chris@68
|
604 QFile::ReadOther)) {
|
Chris@68
|
605 SVCERR << "Failed to set permissions on "
|
Chris@68
|
606 << destination.toStdString()
|
Chris@68
|
607 << " (ignoring)" << endl;
|
Chris@68
|
608 continue;
|
Chris@52
|
609 }
|
Chris@52
|
610 }
|
Chris@42
|
611 }
|
Chris@42
|
612
|
Chris@75
|
613 vector<LibraryInfo>
|
Chris@75
|
614 getUserApprovedPluginLibraries(vector<LibraryInfo> libraries,
|
Chris@75
|
615 QString targetDir)
|
Chris@42
|
616 {
|
Chris@42
|
617 QDialog dialog;
|
Chris@46
|
618
|
Chris@46
|
619 auto mainLayout = new QGridLayout;
|
Chris@47
|
620 mainLayout->setSpacing(0);
|
Chris@46
|
621 dialog.setLayout(mainLayout);
|
Chris@46
|
622
|
Chris@46
|
623 int mainRow = 0;
|
Chris@46
|
624
|
Chris@74
|
625 auto selectionFrame = new QWidget;
|
Chris@74
|
626 mainLayout->addWidget(selectionFrame, mainRow, 0);
|
Chris@47
|
627 ++mainRow;
|
Chris@46
|
628
|
Chris@46
|
629 auto selectionLayout = new QGridLayout;
|
Chris@46
|
630 selectionFrame->setLayout(selectionLayout);
|
Chris@46
|
631 int selectionRow = 0;
|
Chris@79
|
632
|
Chris@79
|
633 selectionLayout->addWidget
|
Chris@79
|
634 (new QLabel(QObject::tr("<b>Vamp Plugin Pack</b> v%1")
|
Chris@79
|
635 .arg(PACK_VERSION)),
|
Chris@79
|
636 selectionRow, 1);
|
Chris@79
|
637 ++selectionRow;
|
Chris@79
|
638
|
Chris@79
|
639 selectionLayout->addWidget
|
Chris@79
|
640 (new QLabel(QObject::tr("Select the plugin libraries to install:")),
|
Chris@79
|
641 selectionRow, 1, 1, 3);
|
Chris@79
|
642 ++selectionRow;
|
Chris@74
|
643
|
Chris@74
|
644 auto checkAll = new QCheckBox;
|
Chris@74
|
645 checkAll->setChecked(true);
|
Chris@74
|
646 selectionLayout->addWidget(checkAll, selectionRow, 0, Qt::AlignHCenter);
|
Chris@74
|
647 ++selectionRow;
|
Chris@74
|
648
|
Chris@74
|
649 auto checkArrow = new QLabel("▼");
|
Chris@74
|
650 checkArrow->setTextFormat(Qt::RichText);
|
Chris@74
|
651 selectionLayout->addWidget(checkArrow, selectionRow, 0, Qt::AlignHCenter);
|
Chris@74
|
652 ++selectionRow;
|
Chris@42
|
653
|
Chris@67
|
654 map<QString, QCheckBox *> checkBoxMap; // filename -> checkbox
|
Chris@67
|
655 map<QString, LibraryInfo> libFileInfo; // filename -> info
|
Chris@76
|
656 map<QString, RelativeStatus> statuses; // filename -> status
|
Chris@43
|
657
|
Chris@67
|
658 map<QString, LibraryInfo, function<bool (QString, QString)>>
|
Chris@49
|
659 orderedInfo
|
Chris@49
|
660 ([](QString k1, QString k2) {
|
Chris@49
|
661 return k1.localeAwareCompare(k2) < 0;
|
Chris@49
|
662 });
|
Chris@43
|
663 for (auto info: libraries) {
|
Chris@43
|
664 orderedInfo[info.title] = info;
|
Chris@43
|
665 }
|
Chris@53
|
666
|
Chris@74
|
667 int fontHeight = QFontMetrics(checkArrow->font()).height();
|
Chris@77
|
668 int dpratio = dialog.devicePixelRatio();
|
Chris@74
|
669
|
Chris@77
|
670 QPixmap infoMap(fontHeight * dpratio, fontHeight * dpratio);
|
Chris@77
|
671 QPixmap moreMap(fontHeight * dpratio * 2, fontHeight * dpratio * 2);
|
Chris@74
|
672 infoMap.fill(Qt::transparent);
|
Chris@74
|
673 moreMap.fill(Qt::transparent);
|
Chris@74
|
674 QSvgRenderer renderer(QString(":icons/scalable/info.svg"));
|
Chris@74
|
675 QPainter painter;
|
Chris@74
|
676 painter.begin(&infoMap);
|
Chris@74
|
677 renderer.render(&painter);
|
Chris@74
|
678 painter.end();
|
Chris@74
|
679 painter.begin(&moreMap);
|
Chris@74
|
680 renderer.render(&painter);
|
Chris@74
|
681 painter.end();
|
Chris@74
|
682
|
Chris@76
|
683 auto shouldCheck = [](RelativeStatus status) {
|
Chris@76
|
684 return (status == RelativeStatus::New ||
|
Chris@76
|
685 status == RelativeStatus::Upgrade ||
|
Chris@76
|
686 status == RelativeStatus::TargetNotLoadable);
|
Chris@76
|
687 };
|
Chris@76
|
688
|
Chris@43
|
689 for (auto ip: orderedInfo) {
|
Chris@46
|
690
|
Chris@46
|
691 auto cb = new QCheckBox;
|
Chris@74
|
692 selectionLayout->addWidget(cb, selectionRow, 0, Qt::AlignHCenter);
|
Chris@46
|
693
|
Chris@43
|
694 LibraryInfo info = ip.second;
|
Chris@72
|
695
|
Chris@76
|
696 auto shortLabel = new QLabel(info.title);
|
Chris@76
|
697 selectionLayout->addWidget(shortLabel, selectionRow, 1);
|
Chris@76
|
698
|
Chris@75
|
699 RelativeStatus relativeStatus = getRelativeStatus(info, targetDir);
|
Chris@76
|
700 auto statusLabel = new QLabel(relativeStatusLabel(relativeStatus));
|
Chris@76
|
701 selectionLayout->addWidget(statusLabel, selectionRow, 2);
|
Chris@76
|
702 cb->setChecked(shouldCheck(relativeStatus));
|
Chris@75
|
703
|
Chris@79
|
704 auto infoButton = new QToolButton;
|
Chris@79
|
705 infoButton->setAutoRaise(true);
|
Chris@79
|
706 infoButton->setIcon(infoMap);
|
Chris@79
|
707 infoButton->setIconSize(QSize(fontHeight, fontHeight));
|
Chris@77
|
708
|
Chris@77
|
709 #ifdef Q_OS_MAC
|
Chris@79
|
710 infoButton->setFixedSize(QSize(int(fontHeight * 1.2),
|
Chris@77
|
711 int(fontHeight * 1.2)));
|
Chris@79
|
712 infoButton->setStyleSheet("QToolButton { border: none; }");
|
Chris@77
|
713 #endif
|
Chris@77
|
714
|
Chris@79
|
715 selectionLayout->addWidget(infoButton, selectionRow, 3);
|
Chris@46
|
716
|
Chris@46
|
717 ++selectionRow;
|
Chris@46
|
718
|
Chris@77
|
719 QString moreTitleText = QObject::tr("<b>%1</b><br><i>%2</i>")
|
Chris@72
|
720 .arg(info.title)
|
Chris@77
|
721 .arg(info.maker);
|
Chris@77
|
722
|
Chris@79
|
723 QString moreInfoText = info.description;
|
Chris@79
|
724
|
Chris@79
|
725 if (info.page != "") {
|
Chris@79
|
726 moreInfoText += QObject::tr("<br><a href=\"%1\">%2</a>")
|
Chris@79
|
727 .arg(info.page)
|
Chris@79
|
728 .arg(info.page);
|
Chris@79
|
729 }
|
Chris@79
|
730
|
Chris@79
|
731 moreInfoText += QObject::tr("<br><br>Library contains:<ul>");
|
Chris@73
|
732
|
Chris@73
|
733 int n = 0;
|
Chris@73
|
734 bool closed = false;
|
Chris@73
|
735 for (auto title: info.pluginTitles) {
|
Chris@73
|
736 if (n == 10 && info.pluginTitles.size() > 15) {
|
Chris@77
|
737 moreInfoText += QObject::tr("</ul>");
|
Chris@77
|
738 moreInfoText += QObject::tr("... and %n other plugins.<br><br>",
|
Chris@77
|
739 "",
|
Chris@77
|
740 info.pluginTitles.size() - n);
|
Chris@73
|
741 closed = true;
|
Chris@73
|
742 break;
|
Chris@73
|
743 }
|
Chris@77
|
744 moreInfoText += QObject::tr("<li>%1</li>").arg(title);
|
Chris@73
|
745 ++n;
|
Chris@73
|
746 }
|
Chris@73
|
747
|
Chris@73
|
748 if (!closed) {
|
Chris@77
|
749 moreInfoText += QObject::tr("</ul>");
|
Chris@73
|
750 }
|
Chris@74
|
751
|
Chris@74
|
752 if (info.licence != "") {
|
Chris@77
|
753 moreInfoText += QObject::tr("Provided under the %1.<br>")
|
Chris@77
|
754 .arg(info.licence);
|
Chris@74
|
755 }
|
Chris@72
|
756
|
Chris@79
|
757 QObject::connect(infoButton, &QAbstractButton::clicked,
|
Chris@72
|
758 [=]() {
|
Chris@74
|
759 QMessageBox mbox;
|
Chris@74
|
760 mbox.setIconPixmap(moreMap);
|
Chris@74
|
761 mbox.setWindowTitle(QObject::tr("Library contents"));
|
Chris@77
|
762 mbox.setText(moreTitleText);
|
Chris@77
|
763 mbox.setInformativeText(moreInfoText);
|
Chris@74
|
764 mbox.exec();
|
Chris@72
|
765 });
|
Chris@72
|
766
|
Chris@43
|
767 checkBoxMap[info.fileName] = cb;
|
Chris@67
|
768 libFileInfo[info.fileName] = info;
|
Chris@76
|
769 statuses[info.fileName] = relativeStatus;
|
Chris@42
|
770 }
|
Chris@42
|
771
|
Chris@79
|
772 selectionLayout->addItem(new QSpacerItem(1, (fontHeight*2) / 3),
|
Chris@79
|
773 selectionRow, 0);
|
Chris@79
|
774 ++selectionRow;
|
Chris@79
|
775
|
Chris@79
|
776 selectionLayout->addWidget
|
Chris@79
|
777 (new QLabel(QObject::tr("Installation will be to: %1").arg(targetDir)),
|
Chris@79
|
778 selectionRow, 1, 1, 3);
|
Chris@79
|
779 ++selectionRow;
|
Chris@79
|
780
|
Chris@47
|
781 QObject::connect(checkAll, &QCheckBox::toggled,
|
Chris@72
|
782 [=](bool toCheck) {
|
Chris@47
|
783 for (auto p: checkBoxMap) {
|
Chris@47
|
784 p.second->setChecked(toCheck);
|
Chris@47
|
785 }
|
Chris@47
|
786 });
|
Chris@79
|
787
|
Chris@79
|
788 mainLayout->addItem(new QSpacerItem(1, fontHeight), mainRow, 0);
|
Chris@79
|
789 ++mainRow;
|
Chris@79
|
790
|
Chris@42
|
791 auto bb = new QDialogButtonBox(QDialogButtonBox::Ok |
|
Chris@76
|
792 QDialogButtonBox::Cancel |
|
Chris@76
|
793 QDialogButtonBox::Reset);
|
Chris@79
|
794 bb->button(QDialogButtonBox::Ok)->setText(QObject::tr("Install"));
|
Chris@74
|
795 mainLayout->addWidget(bb, mainRow, 0);
|
Chris@46
|
796 ++mainRow;
|
Chris@47
|
797
|
Chris@74
|
798 mainLayout->setRowStretch(0, 10);
|
Chris@74
|
799 mainLayout->setColumnStretch(0, 10);
|
Chris@74
|
800 selectionLayout->setColumnMinimumWidth(0, 50);
|
Chris@74
|
801 selectionLayout->setColumnStretch(1, 10);
|
Chris@47
|
802
|
Chris@76
|
803 QObject::connect
|
Chris@76
|
804 (bb, &QDialogButtonBox::clicked,
|
Chris@76
|
805 [&](QAbstractButton *button) {
|
Chris@76
|
806
|
Chris@76
|
807 auto role = bb->buttonRole(button);
|
Chris@76
|
808
|
Chris@76
|
809 switch (role) {
|
Chris@76
|
810
|
Chris@76
|
811 case QDialogButtonBox::AcceptRole: {
|
Chris@76
|
812 bool downgrade = false;
|
Chris@76
|
813 for (const auto &p: checkBoxMap) {
|
Chris@76
|
814 if (p.second->isChecked() &&
|
Chris@76
|
815 statuses.at(p.first) == RelativeStatus::Downgrade) {
|
Chris@76
|
816 downgrade = true;
|
Chris@76
|
817 break;
|
Chris@76
|
818 }
|
Chris@76
|
819 }
|
Chris@76
|
820 if (downgrade) {
|
Chris@76
|
821 if (QMessageBox::warning
|
Chris@76
|
822 (bb, QObject::tr("Downgrade?"),
|
Chris@76
|
823 QObject::tr("You have asked to downgrade one or more plugin libraries that are already installed.<br><br>Are you sure?"),
|
Chris@76
|
824 QMessageBox::Ok | QMessageBox::Cancel,
|
Chris@76
|
825 QMessageBox::Cancel) == QMessageBox::Ok) {
|
Chris@76
|
826 dialog.accept();
|
Chris@76
|
827 }
|
Chris@76
|
828 } else {
|
Chris@76
|
829 dialog.accept();
|
Chris@76
|
830 }
|
Chris@76
|
831 break;
|
Chris@76
|
832 }
|
Chris@76
|
833
|
Chris@76
|
834 case QDialogButtonBox::RejectRole:
|
Chris@76
|
835 dialog.reject();
|
Chris@76
|
836 break;
|
Chris@76
|
837
|
Chris@76
|
838 case QDialogButtonBox::ResetRole:
|
Chris@76
|
839 for (const auto &p: checkBoxMap) {
|
Chris@76
|
840 p.second->setChecked(shouldCheck(statuses.at(p.first)));
|
Chris@76
|
841 }
|
Chris@76
|
842 break;
|
Chris@76
|
843
|
Chris@76
|
844 default:
|
Chris@76
|
845 SVCERR << "WARNING: Unexpected role " << role << endl;
|
Chris@76
|
846 }
|
Chris@76
|
847 });
|
Chris@53
|
848
|
Chris@70
|
849 if (dialog.exec() != QDialog::Accepted) {
|
Chris@51
|
850 SVCERR << "rejected" << endl;
|
Chris@70
|
851 return {};
|
Chris@42
|
852 }
|
Chris@42
|
853
|
Chris@75
|
854 vector<LibraryInfo> approved;
|
Chris@42
|
855 for (const auto &p: checkBoxMap) {
|
Chris@42
|
856 if (p.second->isChecked()) {
|
Chris@75
|
857 approved.push_back(libFileInfo[p.first]);
|
Chris@33
|
858 }
|
Chris@42
|
859 }
|
Chris@42
|
860
|
Chris@42
|
861 return approved;
|
Chris@42
|
862 }
|
Chris@42
|
863
|
Chris@42
|
864 int main(int argc, char **argv)
|
Chris@42
|
865 {
|
Chris@42
|
866 QApplication app(argc, argv);
|
Chris@42
|
867
|
Chris@51
|
868 QApplication::setOrganizationName("sonic-visualiser");
|
Chris@51
|
869 QApplication::setOrganizationDomain("sonicvisualiser.org");
|
Chris@51
|
870 QApplication::setApplicationName(QApplication::tr("Vamp Plugin Pack Installer"));
|
Chris@51
|
871
|
Chris@77
|
872 QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
Chris@77
|
873
|
Chris@51
|
874 #ifdef Q_OS_WIN32
|
Chris@51
|
875 QFont font(QApplication::font());
|
Chris@51
|
876 QString preferredFamily = "Segoe UI";
|
Chris@51
|
877 font.setFamily(preferredFamily);
|
Chris@51
|
878 if (QFontInfo(font).family() == preferredFamily) {
|
Chris@51
|
879 font.setPointSize(10);
|
Chris@51
|
880 QApplication::setFont(font);
|
Chris@51
|
881 }
|
Chris@77
|
882 #else
|
Chris@77
|
883 #ifdef Q_OS_MAC
|
Chris@77
|
884 QFont font(QApplication::font());
|
Chris@77
|
885 QString preferredFamily = "Lucida Grande";
|
Chris@77
|
886 font.setFamily(preferredFamily);
|
Chris@77
|
887 if (QFontInfo(font).family() == preferredFamily) {
|
Chris@77
|
888 QApplication::setFont(font);
|
Chris@77
|
889 }
|
Chris@77
|
890 #endif
|
Chris@51
|
891 #endif
|
Chris@51
|
892
|
Chris@42
|
893 QString target = getDefaultInstallDirectory();
|
Chris@42
|
894 if (target == "") {
|
Chris@42
|
895 return 1;
|
Chris@42
|
896 }
|
Chris@42
|
897
|
Chris@42
|
898 QStringList libraries = getPluginLibraryList();
|
Chris@42
|
899
|
Chris@43
|
900 auto rdfStore = loadLibrariesRdf();
|
Chris@43
|
901
|
Chris@43
|
902 auto info = getLibraryInfo(*rdfStore, libraries);
|
Chris@43
|
903
|
Chris@75
|
904 vector<LibraryInfo> toInstall =
|
Chris@75
|
905 getUserApprovedPluginLibraries(info, target);
|
Chris@52
|
906
|
Chris@52
|
907 if (!toInstall.empty()) {
|
Chris@52
|
908 if (!QDir(target).exists()) {
|
Chris@52
|
909 QDir().mkpath(target);
|
Chris@52
|
910 }
|
Chris@52
|
911 }
|
Chris@42
|
912
|
Chris@42
|
913 for (auto lib: toInstall) {
|
Chris@75
|
914 installLibrary(lib, target);
|
Chris@33
|
915 }
|
Chris@33
|
916
|
Chris@32
|
917 return 0;
|
Chris@32
|
918 }
|