annotate installer.cpp @ 129:afd72eb2b0aa tip

Added tag v1.0-windows-32bit for changeset 7d5387c63447
author Chris Cannam
date Fri, 12 Jun 2020 17:20:52 +0100
parents 31435b4d9833
children
rev   line source
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@81 54 #include <QProgressDialog>
Chris@81 55 #include <QThread>
Chris@86 56 #include <QDateTime>
Chris@98 57 #include <QTimer>
Chris@42 58
Chris@41 59 #include <vamp-hostsdk/PluginHostAdapter.h>
Chris@41 60
Chris@43 61 #include <dataquay/BasicStore.h>
Chris@43 62 #include <dataquay/RDFException.h>
Chris@43 63
Chris@33 64 #include <iostream>
Chris@58 65 #include <memory>
Chris@43 66 #include <set>
Chris@43 67
Chris@51 68 #include "base/Debug.h"
Chris@51 69
Chris@79 70 #include "version.h"
Chris@79 71
Chris@33 72 using namespace std;
Chris@43 73 using namespace Dataquay;
Chris@32 74
Chris@42 75 QString
Chris@42 76 getDefaultInstallDirectory()
Chris@32 77 {
Chris@41 78 auto pathList = Vamp::PluginHostAdapter::getPluginPath();
Chris@41 79 if (pathList.empty()) {
Chris@51 80 SVCERR << "Failed to look up Vamp plugin path" << endl;
Chris@42 81 return QString();
Chris@41 82 }
Chris@41 83
Chris@42 84 auto firstPath = *pathList.begin();
Chris@42 85 QString target = QString::fromUtf8(firstPath.c_str(), firstPath.size());
Chris@42 86 return target;
Chris@42 87 }
Chris@42 88
Chris@42 89 QStringList
Chris@42 90 getPluginLibraryList()
Chris@42 91 {
Chris@33 92 QDir dir(":out/");
Chris@33 93 auto entries = dir.entryList({ "*.so", "*.dll", "*.dylib" });
Chris@33 94
Chris@33 95 for (auto e: entries) {
Chris@51 96 SVCERR << e.toStdString() << endl;
Chris@33 97 }
Chris@33 98
Chris@42 99 return entries;
Chris@42 100 }
Chris@33 101
Chris@50 102 void
Chris@50 103 loadLibraryRdf(BasicStore &store, QString filename)
Chris@50 104 {
Chris@50 105 QFile f(filename);
Chris@50 106 if (!f.open(QFile::ReadOnly | QFile::Text)) {
Chris@51 107 SVCERR << "Failed to open RDF resource file "
Chris@51 108 << filename.toStdString() << endl;
Chris@50 109 return;
Chris@50 110 }
Chris@50 111
Chris@50 112 QByteArray content = f.readAll();
Chris@50 113 f.close();
Chris@50 114
Chris@50 115 try {
Chris@50 116 store.importString(QString::fromUtf8(content),
Chris@50 117 Uri("file:" + filename),
Chris@50 118 BasicStore::ImportIgnoreDuplicates);
Chris@50 119 } catch (const RDFException &ex) {
Chris@51 120 SVCERR << "Failed to import RDF resource file "
Chris@51 121 << filename.toStdString() << ": " << ex.what() << endl;
Chris@50 122 }
Chris@50 123 }
Chris@50 124
Chris@43 125 unique_ptr<BasicStore>
Chris@43 126 loadLibrariesRdf()
Chris@43 127 {
Chris@43 128 unique_ptr<BasicStore> store(new BasicStore);
Chris@43 129
Chris@50 130 vector<QString> dirs { ":rdf/plugins", ":out" };
Chris@43 131
Chris@50 132 for (auto d: dirs) {
Chris@50 133 for (auto e: QDir(d).entryList({ "*.ttl", "*.n3" })) {
Chris@50 134 loadLibraryRdf(*store, d + "/" + e);
Chris@43 135 }
Chris@43 136 }
Chris@43 137
Chris@43 138 return store;
Chris@43 139 }
Chris@43 140
Chris@43 141 struct LibraryInfo {
Chris@43 142 QString id;
Chris@43 143 QString fileName;
Chris@43 144 QString title;
Chris@43 145 QString maker;
Chris@43 146 QString description;
Chris@78 147 QString page;
Chris@47 148 QStringList pluginTitles;
Chris@74 149 QString licence;
Chris@43 150 };
Chris@43 151
Chris@99 152 struct Licence
Chris@99 153 {
Chris@99 154 static QString gpl;
Chris@99 155 static QString gpl2;
Chris@99 156 static QString gpl3;
Chris@99 157 static QString agpl;
Chris@99 158 static QString apache;
Chris@99 159 static QString mit;
Chris@99 160 };
Chris@99 161
Chris@99 162 QString Licence::gpl = "GNU General Public License";
Chris@99 163 QString Licence::gpl2 = "GNU General Public License, version 2";
Chris@99 164 QString Licence::gpl3 = "GNU General Public License, version 3";
Chris@99 165 QString Licence::agpl = "GNU Affero General Public License";
Chris@99 166 QString Licence::apache = "Apache License";
Chris@99 167 QString Licence::mit = "MIT License";
Chris@99 168
Chris@74 169 QString
Chris@74 170 identifyLicence(QString libraryBasename)
Chris@74 171 {
Chris@74 172 QString licenceFile = QString(":out/%1_COPYING.txt").arg(libraryBasename);
Chris@74 173
Chris@74 174 QFile f(licenceFile);
Chris@74 175 if (!f.open(QFile::ReadOnly | QFile::Text)) {
Chris@74 176 SVCERR << "Failed to open licence file "
Chris@74 177 << licenceFile.toStdString() << endl;
Chris@74 178 return {};
Chris@74 179 }
Chris@74 180
Chris@74 181 QByteArray content = f.readAll();
Chris@74 182 f.close();
Chris@74 183
Chris@74 184 QString licenceText = QString::fromUtf8(content);
Chris@74 185
Chris@82 186 // NB these are not expected to identify an arbitrary licence! We
Chris@74 187 // know we have only a limited set here. But we do want to
Chris@74 188 // determine this from the actual licence text included with the
Chris@74 189 // plugin distribution, not just from e.g. RDF metadata
Chris@74 190
Chris@99 191 if (licenceText.contains(Licence::gpl.toUpper(), Qt::CaseSensitive)) {
Chris@74 192 if (licenceText.contains("Version 3, 29 June 2007")) {
Chris@99 193 return Licence::gpl3;
Chris@74 194 } else if (licenceText.contains("Version 2, June 1991")) {
Chris@99 195 return Licence::gpl2;
Chris@74 196 } else {
Chris@99 197 return Licence::gpl;
Chris@74 198 }
Chris@74 199 }
Chris@99 200 if (licenceText.contains(Licence::agpl.toUpper(), Qt::CaseSensitive)) {
Chris@99 201 return Licence::agpl;
Chris@74 202 }
Chris@99 203 if (licenceText.contains(Licence::apache)) {
Chris@99 204 return Licence::apache;
Chris@74 205 }
Chris@74 206 if (licenceText.contains("Permission is hereby granted, free of charge, to any person")) {
Chris@99 207 return Licence::mit;
Chris@74 208 }
Chris@74 209
Chris@74 210 SVCERR << "Didn't recognise licence for " << libraryBasename << endl;
Chris@74 211
Chris@74 212 return {};
Chris@74 213 }
Chris@74 214
Chris@99 215 QString
Chris@99 216 getLicenceURL(QString licence)
Chris@99 217 {
Chris@99 218 if (licence == Licence::gpl ||
Chris@99 219 licence == Licence::gpl3) {
Chris@99 220 return "https://www.gnu.org/licenses/gpl-3.0.en.html";
Chris@99 221 } else if (licence == Licence::gpl2) {
Chris@99 222 return "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html";
Chris@99 223 } else if (licence == Licence::agpl) {
Chris@99 224 return "https://www.gnu.org/licenses/agpl-3.0.html";
Chris@99 225 } else if (licence == Licence::apache) {
Chris@99 226 return "https://www.apache.org/licenses/LICENSE-2.0";
Chris@99 227 } else if (licence == Licence::mit) {
Chris@99 228 return "https://opensource.org/licenses/MIT";
Chris@99 229 }
Chris@99 230
Chris@99 231 return {};
Chris@99 232 }
Chris@99 233
Chris@43 234 vector<LibraryInfo>
Chris@43 235 getLibraryInfo(const Store &store, QStringList libraries)
Chris@43 236 {
Chris@43 237 /* e.g.
Chris@43 238
Chris@43 239 plugbase:library a vamp:PluginLibrary ;
Chris@43 240 vamp:identifier "qm-vamp-plugins" ;
Chris@43 241 dc:title "Queen Mary plugin set"
Chris@43 242 */
Chris@43 243
Chris@43 244 Triples tt = store.match(Triple(Node(),
Chris@43 245 Uri("a"),
Chris@43 246 store.expand("vamp:PluginLibrary")));
Chris@43 247
Chris@67 248 map<QString, QString> wanted; // basename -> full lib name
Chris@43 249 for (auto lib: libraries) {
Chris@43 250 wanted[QFileInfo(lib).baseName()] = lib;
Chris@43 251 }
Chris@43 252
Chris@43 253 vector<LibraryInfo> results;
Chris@43 254
Chris@43 255 for (auto t: tt) {
Chris@43 256
Chris@43 257 Node libId = store.complete(Triple(t.subject(),
Chris@43 258 store.expand("vamp:identifier"),
Chris@43 259 Node()));
Chris@43 260 if (libId.type != Node::Literal) {
Chris@43 261 continue;
Chris@43 262 }
Chris@43 263 auto wi = wanted.find(libId.value);
Chris@43 264 if (wi == wanted.end()) {
Chris@43 265 continue;
Chris@43 266 }
Chris@74 267
Chris@50 268 Node title = store.complete(Triple(t.subject(),
Chris@50 269 store.expand("dc:title"),
Chris@50 270 Node()));
Chris@50 271 if (title.type != Node::Literal) {
Chris@50 272 continue;
Chris@50 273 }
Chris@50 274
Chris@43 275 LibraryInfo info;
Chris@43 276 info.id = wi->first;
Chris@43 277 info.fileName = wi->second;
Chris@50 278 info.title = title.value;
Chris@43 279
Chris@43 280 Node maker = store.complete(Triple(t.subject(),
Chris@43 281 store.expand("foaf:maker"),
Chris@43 282 Node()));
Chris@43 283 if (maker.type == Node::Literal) {
Chris@43 284 info.maker = maker.value;
Chris@46 285 } else if (maker != Node()) {
Chris@46 286 maker = store.complete(Triple(maker,
Chris@46 287 store.expand("foaf:name"),
Chris@46 288 Node()));
Chris@46 289 if (maker.type == Node::Literal) {
Chris@46 290 info.maker = maker.value;
Chris@46 291 }
Chris@43 292 }
Chris@46 293
Chris@43 294 Node desc = store.complete(Triple(t.subject(),
Chris@43 295 store.expand("dc:description"),
Chris@43 296 Node()));
Chris@43 297 if (desc.type == Node::Literal) {
Chris@43 298 info.description = desc.value;
Chris@43 299 }
Chris@78 300
Chris@78 301 Node page = store.complete(Triple(t.subject(),
Chris@78 302 store.expand("foaf:page"),
Chris@78 303 Node()));
Chris@79 304 if (page.type == Node::URI) {
Chris@79 305 info.page = page.value;
Chris@78 306 }
Chris@43 307
Chris@47 308 Triples pp = store.match(Triple(t.subject(),
Chris@47 309 store.expand("vamp:available_plugin"),
Chris@47 310 Node()));
Chris@47 311 for (auto p: pp) {
Chris@47 312 Node ptitle = store.complete(Triple(p.object(),
Chris@47 313 store.expand("dc:title"),
Chris@47 314 Node()));
Chris@47 315 if (ptitle.type == Node::Literal) {
Chris@47 316 info.pluginTitles.push_back(ptitle.value);
Chris@47 317 }
Chris@47 318 }
Chris@74 319
Chris@74 320 info.licence = identifyLicence(libId.value);
Chris@74 321 SVCERR << "licence = " << info.licence << endl;
Chris@47 322
Chris@43 323 results.push_back(info);
Chris@50 324 wanted.erase(libId.value);
Chris@43 325 }
Chris@43 326
Chris@50 327 for (auto wp: wanted) {
Chris@51 328 SVCERR << "Failed to find any RDF information about library "
Chris@51 329 << wp.second << endl;
Chris@50 330 }
Chris@50 331
Chris@43 332 return results;
Chris@43 333 }
Chris@43 334
Chris@67 335 struct TempFileDeleter {
Chris@67 336 ~TempFileDeleter() {
Chris@67 337 if (tempFile != "") {
Chris@67 338 QFile(tempFile).remove();
Chris@67 339 }
Chris@67 340 }
Chris@67 341 QString tempFile;
Chris@67 342 };
Chris@67 343
Chris@97 344 bool
Chris@97 345 unbundleFile(QString filePath, QString targetPath, bool isExecutable)
Chris@97 346 {
Chris@97 347 SVCERR << "Copying " << filePath.toStdString() << " to "
Chris@97 348 << targetPath.toStdString() << "..." << endl;
Chris@97 349
Chris@97 350 // This has to be able to work even if the destination exists, and
Chris@97 351 // to do so without deleting it first - e.g. when copying to a
Chris@97 352 // temporary file. So we open the file and copy to it ourselves
Chris@97 353 // rather than use QFile::copy
Chris@97 354
Chris@97 355 QFile source(filePath);
Chris@97 356 if (!source.open(QFile::ReadOnly)) {
Chris@97 357 SVCERR << "ERROR: Failed to read bundled file " << filePath << endl;
Chris@97 358 return {};
Chris@97 359 }
Chris@97 360 QByteArray content = source.readAll();
Chris@97 361 source.close();
Chris@97 362
Chris@97 363 QFile target(targetPath);
Chris@97 364 if (!target.open(QFile::WriteOnly)) {
Chris@97 365 SVCERR << "ERROR: Failed to read target file " << targetPath << endl;
Chris@97 366 return {};
Chris@97 367 }
Chris@97 368 if (target.write(content) != content.size()) {
Chris@97 369 SVCERR << "ERROR: Incomplete write to target file" << endl;
Chris@97 370 return {};
Chris@97 371 }
Chris@97 372 target.close();
Chris@97 373
Chris@97 374 auto permissions =
Chris@97 375 QFile::ReadOwner | QFile::WriteOwner |
Chris@97 376 QFile::ReadGroup |
Chris@97 377 QFile::ReadOther;
Chris@97 378
Chris@97 379 if (isExecutable) {
Chris@97 380 permissions |=
Chris@97 381 QFile::ExeOwner |
Chris@97 382 QFile::ExeGroup |
Chris@97 383 QFile::ExeOther;
Chris@97 384 };
Chris@97 385
Chris@97 386 if (!QFile::setPermissions(targetPath, permissions)) {
Chris@97 387 SVCERR << "Failed to set permissions on "
Chris@97 388 << targetPath.toStdString() << endl;
Chris@97 389 return false;
Chris@97 390 }
Chris@97 391
Chris@97 392 return true;
Chris@97 393 }
Chris@97 394
Chris@67 395 map<QString, int>
Chris@70 396 getLibraryPluginVersions(QString libraryFilePath)
Chris@67 397 {
Chris@67 398 static QMutex mutex;
Chris@67 399 static QString tempFileName;
Chris@67 400 static TempFileDeleter deleter;
Chris@67 401 static bool initHappened = false, initSucceeded = false;
Chris@67 402
Chris@67 403 QMutexLocker locker (&mutex);
Chris@67 404
Chris@67 405 if (!initHappened) {
Chris@67 406 initHappened = true;
Chris@67 407
Chris@67 408 QTemporaryFile tempFile;
Chris@67 409 tempFile.setAutoRemove(false);
Chris@67 410 if (!tempFile.open()) {
Chris@67 411 SVCERR << "ERROR: Failed to open a temporary file" << endl;
Chris@67 412 return {};
Chris@67 413 }
Chris@67 414
Chris@67 415 // We can't make the QTemporaryFile static, as it will hold
Chris@67 416 // the file open and that prevents us from executing it. Hence
Chris@67 417 // the separate deleter.
Chris@67 418
Chris@67 419 tempFileName = tempFile.fileName();
Chris@67 420 deleter.tempFile = tempFileName;
Chris@67 421
Chris@67 422 #ifdef Q_OS_WIN32
Chris@67 423 QString helperPath = ":out/get-version.exe";
Chris@67 424 #else
Chris@67 425 QString helperPath = ":out/get-version";
Chris@97 426 #endif
Chris@97 427
Chris@97 428 tempFile.close();
Chris@97 429 if (!unbundleFile(helperPath, tempFileName, true)) {
Chris@97 430 SVCERR << "ERROR: Failed to unbundle helper code" << endl;
Chris@67 431 return {};
Chris@67 432 }
Chris@67 433
Chris@67 434 initSucceeded = true;
Chris@67 435 }
Chris@67 436
Chris@67 437 if (!initSucceeded) {
Chris@67 438 return {};
Chris@67 439 }
Chris@67 440
Chris@67 441 QProcess process;
Chris@67 442 process.start(tempFileName, { libraryFilePath });
Chris@67 443
Chris@67 444 if (!process.waitForStarted()) {
Chris@67 445 QProcess::ProcessError err = process.error();
Chris@67 446 if (err == QProcess::FailedToStart) {
Chris@67 447 SVCERR << "Unable to start helper process " << tempFileName << endl;
Chris@67 448 } else if (err == QProcess::Crashed) {
Chris@67 449 SVCERR << "Helper process " << tempFileName
Chris@67 450 << " crashed on startup" << endl;
Chris@67 451 } else {
Chris@67 452 SVCERR << "Helper process " << tempFileName
Chris@67 453 << " failed on startup with error code " << err << endl;
Chris@67 454 }
Chris@67 455 return {};
Chris@67 456 }
Chris@67 457 process.waitForFinished();
Chris@67 458
Chris@67 459 QByteArray stdOut = process.readAllStandardOutput();
Chris@67 460 QByteArray stdErr = process.readAllStandardError();
Chris@67 461
Chris@67 462 QString errStr = QString::fromUtf8(stdErr);
Chris@67 463 if (!errStr.isEmpty()) {
Chris@67 464 SVCERR << "Note: Helper process stderr follows:" << endl;
Chris@67 465 SVCERR << errStr << endl;
Chris@67 466 SVCERR << "Note: Helper process stderr ends" << endl;
Chris@67 467 }
Chris@67 468
Chris@67 469 QStringList lines = QString::fromUtf8(stdOut).split
Chris@67 470 (QRegExp("[\\r\\n]+"), QString::SkipEmptyParts);
Chris@67 471 map<QString, int> versions;
Chris@67 472 for (QString line: lines) {
Chris@67 473 QStringList parts = line.split(":");
Chris@67 474 if (parts.size() != 2) {
Chris@67 475 SVCERR << "Unparseable output line: " << line << endl;
Chris@67 476 continue;
Chris@67 477 }
Chris@67 478 bool ok = false;
Chris@67 479 int version = parts[1].toInt(&ok);
Chris@67 480 if (!ok) {
Chris@67 481 SVCERR << "Unparseable version number in line: " << line << endl;
Chris@67 482 continue;
Chris@67 483 }
Chris@67 484 versions[parts[0]] = version;
Chris@67 485 }
Chris@67 486
Chris@67 487 return versions;
Chris@67 488 }
Chris@67 489
Chris@97 490 map<QString, int>
Chris@97 491 getBundledLibraryPluginVersions(QString libraryFileName)
Chris@97 492 {
Chris@97 493 QString tempFileName;
Chris@97 494 TempFileDeleter deleter;
Chris@97 495
Chris@97 496 {
Chris@97 497 QTemporaryFile tempFile;
Chris@97 498 tempFile.setAutoRemove(false);
Chris@97 499 if (!tempFile.open()) {
Chris@97 500 SVCERR << "ERROR: Failed to open a temporary file" << endl;
Chris@97 501 return {};
Chris@97 502 }
Chris@97 503
Chris@97 504 // We can't use QTemporaryFile's auto-remove, as it will hold
Chris@97 505 // the file open and that prevents us from executing it. Hence
Chris@97 506 // the separate deleter.
Chris@97 507
Chris@97 508 tempFileName = tempFile.fileName();
Chris@97 509 deleter.tempFile = tempFileName;
Chris@97 510 tempFile.close();
Chris@97 511 }
Chris@97 512
Chris@97 513 if (!unbundleFile(":out/" + libraryFileName, tempFileName, true)) {
Chris@97 514 return {};
Chris@97 515 }
Chris@97 516
Chris@97 517 return getLibraryPluginVersions(tempFileName);
Chris@97 518 }
Chris@97 519
Chris@67 520 bool isLibraryNewer(map<QString, int> a, map<QString, int> b)
Chris@67 521 {
Chris@67 522 // a and b are maps from plugin id to plugin version for libraries
Chris@67 523 // A and B. (There is no overarching library version number.) We
Chris@67 524 // deem library A to be newer than library B if:
Chris@67 525 //
Chris@67 526 // 1. A contains a plugin id that is also in B, whose version in
Chris@67 527 // A is newer than that in B, or
Chris@67 528 //
Chris@67 529 // 2. B is not newer than A according to rule 1, and neither A or
Chris@67 530 // B is empty, and A contains a plugin id that is not in B, and B
Chris@67 531 // does not contain any plugin id that is not in A
Chris@67 532 //
Chris@67 533 // (The not-empty part of rule 2 is just to avoid false positives
Chris@67 534 // when a library or its metadata could not be read at all.)
Chris@67 535
Chris@67 536 auto containsANewerPlugin = [](const map<QString, int> &m1,
Chris@67 537 const map<QString, int> &m2) {
Chris@67 538 for (auto p: m1) {
Chris@67 539 if (m2.find(p.first) != m2.end() &&
Chris@67 540 p.second > m2.at(p.first)) {
Chris@67 541 return true;
Chris@67 542 }
Chris@67 543 }
Chris@67 544 return false;
Chris@67 545 };
Chris@67 546
Chris@67 547 auto containsANovelPlugin = [](const map<QString, int> &m1,
Chris@67 548 const map<QString, int> &m2) {
Chris@67 549 for (auto p: m1) {
Chris@67 550 if (m2.find(p.first) == m2.end()) {
Chris@67 551 return true;
Chris@67 552 }
Chris@67 553 }
Chris@67 554 return false;
Chris@67 555 };
Chris@67 556
Chris@67 557 if (containsANewerPlugin(a, b)) {
Chris@67 558 return true;
Chris@67 559 }
Chris@67 560
Chris@67 561 if (!containsANewerPlugin(b, a) &&
Chris@67 562 !a.empty() &&
Chris@67 563 !b.empty() &&
Chris@67 564 containsANovelPlugin(a, b) &&
Chris@67 565 !containsANovelPlugin(b, a)) {
Chris@67 566 return true;
Chris@67 567 }
Chris@67 568
Chris@67 569 return false;
Chris@67 570 }
Chris@67 571
Chris@67 572 QString
Chris@67 573 versionsString(const map<QString, int> &vv)
Chris@67 574 {
Chris@67 575 QStringList pv;
Chris@67 576 for (auto v: vv) {
Chris@67 577 pv.push_back(QString("%1:%2").arg(v.first).arg(v.second));
Chris@67 578 }
Chris@67 579 return "{ " + pv.join(", ") + " }";
Chris@67 580 }
Chris@67 581
Chris@75 582 enum class RelativeStatus {
Chris@75 583 New,
Chris@75 584 Same,
Chris@75 585 Upgrade,
Chris@75 586 Downgrade,
Chris@75 587 TargetNotLoadable
Chris@75 588 };
Chris@75 589
Chris@75 590 QString
Chris@75 591 relativeStatusLabel(RelativeStatus status) {
Chris@75 592 switch (status) {
Chris@76 593 case RelativeStatus::New: return QObject::tr("Not yet installed");
Chris@76 594 case RelativeStatus::Same: return QObject::tr("Already installed");
Chris@76 595 case RelativeStatus::Upgrade: return QObject::tr("Update");
Chris@76 596 case RelativeStatus::Downgrade: return QObject::tr("Newer version installed");
Chris@84 597 case RelativeStatus::TargetNotLoadable: return QObject::tr("Installed version not working");
Chris@79 598 default: return {};
Chris@75 599 }
Chris@75 600 }
Chris@75 601
Chris@75 602 RelativeStatus
Chris@75 603 getRelativeStatus(LibraryInfo info, QString targetDir)
Chris@75 604 {
Chris@75 605 QString destination = targetDir + "/" + info.fileName;
Chris@75 606
Chris@75 607 SVCERR << "\ngetRelativeStatus: " << info.fileName << ":\n";
Chris@75 608
Chris@97 609 if (!QFileInfo(destination).exists()) {
Chris@97 610 SVCERR << " - relative status: " << relativeStatusLabel(RelativeStatus::New) << endl;
Chris@97 611 return RelativeStatus::New;
Chris@97 612 }
Chris@75 613
Chris@97 614 RelativeStatus status = RelativeStatus::Same;
Chris@75 615
Chris@97 616 auto packaged = getBundledLibraryPluginVersions(info.fileName);
Chris@97 617 auto installed = getLibraryPluginVersions(destination);
Chris@75 618
Chris@97 619 SVCERR << " * installed: " << versionsString(installed)
Chris@97 620 << "\n * packaged: " << versionsString(packaged)
Chris@97 621 << endl;
Chris@75 622
Chris@97 623 if (installed.empty()) {
Chris@97 624 status = RelativeStatus::TargetNotLoadable;
Chris@97 625 }
Chris@75 626
Chris@97 627 if (isLibraryNewer(installed, packaged)) {
Chris@97 628 status = RelativeStatus::Downgrade;
Chris@97 629 }
Chris@75 630
Chris@97 631 if (isLibraryNewer(packaged, installed)) {
Chris@97 632 status = RelativeStatus::Upgrade;
Chris@75 633 }
Chris@75 634
Chris@75 635 SVCERR << " - relative status: " << relativeStatusLabel(status) << endl;
Chris@75 636
Chris@75 637 return status;
Chris@75 638 }
Chris@75 639
Chris@86 640 bool
Chris@86 641 backup(QString filePath, QString backupDir)
Chris@86 642 {
Chris@86 643 QFileInfo file(filePath);
Chris@86 644
Chris@86 645 if (!file.exists()) {
Chris@86 646 return true;
Chris@86 647 }
Chris@86 648
Chris@86 649 if (!QDir(backupDir).exists()) {
Chris@86 650 QDir().mkpath(backupDir);
Chris@86 651 }
Chris@86 652
Chris@86 653 QString backup = backupDir + "/" + file.fileName() + ".bak";
Chris@86 654 SVCERR << "Note: existing file " << filePath
Chris@86 655 << " found, backing up to " << backup << endl;
Chris@86 656 if (!QFile(filePath).rename(backup)) {
Chris@86 657 SVCERR << "Failed to move " << filePath.toStdString()
Chris@86 658 << " to backup " << backup.toStdString() << endl;
Chris@86 659 return false;
Chris@86 660 }
Chris@86 661
Chris@86 662 return true;
Chris@86 663 }
Chris@86 664
Chris@81 665 QString
Chris@75 666 installLibrary(LibraryInfo info, QString targetDir)
Chris@42 667 {
Chris@75 668 QString library = info.fileName;
Chris@52 669 QString source = ":out";
Chris@75 670 QString destination = targetDir + "/" + library;
Chris@94 671
Chris@94 672 static QString backupDirName;
Chris@94 673 if (backupDirName == "") {
Chris@94 674 // Static so as to be created once - don't go creating a
Chris@94 675 // second directory if the clock ticks over by one second
Chris@94 676 // between library installs
Chris@94 677 backupDirName =
Chris@94 678 QString("saved-%1").arg(QDateTime::currentDateTime().toString
Chris@94 679 ("yyyyMMdd-hhmmss"));
Chris@94 680 }
Chris@94 681 QString backupDir = targetDir + "/" + backupDirName;
Chris@67 682
Chris@86 683 if (!QDir(targetDir).exists()) {
Chris@86 684 QDir().mkpath(targetDir);
Chris@67 685 }
Chris@82 686
Chris@86 687 if (!backup(destination, backupDir)) {
Chris@86 688 return QObject::tr("Failed to move aside existing library");
Chris@86 689 }
Chris@97 690
Chris@97 691 if (!unbundleFile(source + "/" + library, destination, true)) {
Chris@97 692 return QObject::tr("Failed to copy library file to target directory");
Chris@97 693 }
Chris@52 694
Chris@52 695 QString base = QFileInfo(library).baseName();
Chris@52 696 QDir dir(source);
Chris@52 697 auto entries = dir.entryList({ base + "*" });
Chris@52 698 for (auto e: entries) {
Chris@52 699 if (e == library) continue;
Chris@75 700 QString destination = targetDir + "/" + e;
Chris@86 701 if (!backup(destination, backupDir)) {
Chris@86 702 continue;
Chris@86 703 }
Chris@97 704 if (!unbundleFile(source + "/" + e, destination, false)) {
Chris@68 705 continue;
Chris@52 706 }
Chris@52 707 }
Chris@81 708
Chris@81 709 return {};
Chris@42 710 }
Chris@42 711
Chris@99 712 QString
Chris@99 713 getHelpText(vector<LibraryInfo> libraries)
Chris@99 714 {
Chris@99 715 set<QString, function<bool (QString, QString)>>
Chris@99 716 makers
Chris@99 717 ([](QString k1, QString k2) {
Chris@99 718 return k1.localeAwareCompare(k2) < 0;
Chris@99 719 });
Chris@99 720
Chris@99 721 for (auto info: libraries) {
Chris@99 722 makers.insert(info.maker);
Chris@99 723 }
Chris@99 724
Chris@99 725 QString makerList;
Chris@99 726 for (QString maker: makers) {
Chris@99 727 makerList += QObject::tr("<li>%1</li>").arg(maker);
Chris@99 728 }
Chris@99 729
Chris@99 730 return QObject::tr
Chris@99 731 ("<p>Vamp Plugin Pack collects together a number of <a href=\"https://vamp-plugins.org\">Vamp audio analysis plugins</a> into a single installer.</p>"
Chris@99 732 "<p>The libraries you select will be installed into the standard Vamp plugin directory, where hosts such as <a href=\"https://sonicvisualiser.org/\">Sonic Visualiser</a> can find them.</p>"
Chris@99 733 "<p>The plugin libraries included here were developed and published by various different authors and institutions:</p><ul>%1</ul>"
Chris@99 734 "<p>All of the libraries are open source and are redistributable under open-source licences. Click the information icon to the right of each library in the main window for more details.</p>"
Chris@99 735 "<p>The entire pack may be redistributed under the <a href=\"%2\">GNU Affero General Public License v3</a>.</p>"
Chris@99 736 "<p>The plugins were collected together, and the installer was written and published, at the <a href=\"https://c4dm.eecs.qmul.ac.uk\">Centre for Digital Music</a>, Queen Mary University of London.</p>")
Chris@99 737 .arg(makerList)
Chris@99 738 .arg(getLicenceURL(Licence::agpl));
Chris@99 739 }
Chris@99 740
Chris@75 741 vector<LibraryInfo>
Chris@75 742 getUserApprovedPluginLibraries(vector<LibraryInfo> libraries,
Chris@75 743 QString targetDir)
Chris@42 744 {
Chris@42 745 QDialog dialog;
Chris@46 746
Chris@84 747 int fontHeight = QFontMetrics(dialog.font()).height();
Chris@84 748 int dpratio = dialog.devicePixelRatio();
Chris@84 749
Chris@46 750 auto mainLayout = new QGridLayout;
Chris@47 751 mainLayout->setSpacing(0);
Chris@46 752 dialog.setLayout(mainLayout);
Chris@46 753
Chris@46 754 int mainRow = 0;
Chris@46 755
Chris@74 756 auto selectionFrame = new QWidget;
Chris@74 757 mainLayout->addWidget(selectionFrame, mainRow, 0);
Chris@47 758 ++mainRow;
Chris@46 759
Chris@46 760 auto selectionLayout = new QGridLayout;
Chris@84 761 selectionLayout->setContentsMargins(0, 0, 0, 0);
Chris@84 762 selectionLayout->setSpacing(fontHeight / 6);
Chris@46 763 selectionFrame->setLayout(selectionLayout);
Chris@84 764
Chris@46 765 int selectionRow = 0;
Chris@84 766 int checkColumn = 0;
Chris@84 767 int titleColumn = 1;
Chris@84 768 int statusColumn = 2;
Chris@85 769 int infoColumn = 4; // column 3 is a small sliver of spacing
Chris@79 770
Chris@126 771 QString additionalNote = "";
Chris@126 772 if (sizeof(char *) == 4) {
Chris@126 773 additionalNote = QObject::tr("(32-bit)");
Chris@126 774 }
Chris@126 775
Chris@79 776 selectionLayout->addWidget
Chris@126 777 (new QLabel(QObject::tr("<b>Vamp Plugin Pack</b> v%1 %2")
Chris@126 778 .arg(PACK_VERSION)
Chris@126 779 .arg(additionalNote)),
Chris@84 780 selectionRow, titleColumn, 1, 3);
Chris@79 781 ++selectionRow;
Chris@79 782
Chris@79 783 selectionLayout->addWidget
Chris@79 784 (new QLabel(QObject::tr("Select the plugin libraries to install:")),
Chris@84 785 selectionRow, titleColumn, 1, 3);
Chris@79 786 ++selectionRow;
Chris@74 787
Chris@74 788 auto checkAll = new QCheckBox;
Chris@74 789 checkAll->setChecked(true);
Chris@84 790 selectionLayout->addWidget
Chris@84 791 (checkAll, selectionRow, checkColumn, Qt::AlignHCenter);
Chris@74 792 ++selectionRow;
Chris@74 793
Chris@85 794 auto checkArrow = new QLabel(
Chris@85 795 #ifdef Q_OS_MAC
Chris@85 796 "&nbsp;&nbsp;&#9660;"
Chris@85 797 #else
Chris@85 798 "&#9660;"
Chris@85 799 #endif
Chris@85 800 );
Chris@74 801 checkArrow->setTextFormat(Qt::RichText);
Chris@84 802 selectionLayout->addWidget
Chris@84 803 (checkArrow, selectionRow, checkColumn, Qt::AlignHCenter);
Chris@74 804 ++selectionRow;
Chris@42 805
Chris@67 806 map<QString, QCheckBox *> checkBoxMap; // filename -> checkbox
Chris@67 807 map<QString, LibraryInfo> libFileInfo; // filename -> info
Chris@76 808 map<QString, RelativeStatus> statuses; // filename -> status
Chris@43 809
Chris@67 810 map<QString, LibraryInfo, function<bool (QString, QString)>>
Chris@49 811 orderedInfo
Chris@49 812 ([](QString k1, QString k2) {
Chris@49 813 return k1.localeAwareCompare(k2) < 0;
Chris@49 814 });
Chris@43 815 for (auto info: libraries) {
Chris@43 816 orderedInfo[info.title] = info;
Chris@43 817 }
Chris@53 818
Chris@77 819 QPixmap infoMap(fontHeight * dpratio, fontHeight * dpratio);
Chris@77 820 QPixmap moreMap(fontHeight * dpratio * 2, fontHeight * dpratio * 2);
Chris@74 821 infoMap.fill(Qt::transparent);
Chris@74 822 moreMap.fill(Qt::transparent);
Chris@74 823 QSvgRenderer renderer(QString(":icons/scalable/info.svg"));
Chris@74 824 QPainter painter;
Chris@74 825 painter.begin(&infoMap);
Chris@74 826 renderer.render(&painter);
Chris@74 827 painter.end();
Chris@74 828 painter.begin(&moreMap);
Chris@74 829 renderer.render(&painter);
Chris@74 830 painter.end();
Chris@74 831
Chris@76 832 auto shouldCheck = [](RelativeStatus status) {
Chris@76 833 return (status == RelativeStatus::New ||
Chris@76 834 status == RelativeStatus::Upgrade ||
Chris@76 835 status == RelativeStatus::TargetNotLoadable);
Chris@76 836 };
Chris@76 837
Chris@43 838 for (auto ip: orderedInfo) {
Chris@46 839
Chris@46 840 auto cb = new QCheckBox;
Chris@84 841 selectionLayout->addWidget
Chris@84 842 (cb, selectionRow, checkColumn, Qt::AlignHCenter);
Chris@46 843
Chris@43 844 LibraryInfo info = ip.second;
Chris@72 845
Chris@76 846 auto shortLabel = new QLabel(info.title);
Chris@84 847 selectionLayout->addWidget(shortLabel, selectionRow, titleColumn);
Chris@76 848
Chris@75 849 RelativeStatus relativeStatus = getRelativeStatus(info, targetDir);
Chris@76 850 auto statusLabel = new QLabel(relativeStatusLabel(relativeStatus));
Chris@84 851 selectionLayout->addWidget(statusLabel, selectionRow, statusColumn);
Chris@76 852 cb->setChecked(shouldCheck(relativeStatus));
Chris@75 853
Chris@79 854 auto infoButton = new QToolButton;
Chris@79 855 infoButton->setAutoRaise(true);
Chris@79 856 infoButton->setIcon(infoMap);
Chris@79 857 infoButton->setIconSize(QSize(fontHeight, fontHeight));
Chris@77 858
Chris@77 859 #ifdef Q_OS_MAC
Chris@79 860 infoButton->setFixedSize(QSize(int(fontHeight * 1.2),
Chris@77 861 int(fontHeight * 1.2)));
Chris@79 862 infoButton->setStyleSheet("QToolButton { border: none; }");
Chris@77 863 #endif
Chris@77 864
Chris@84 865 selectionLayout->addWidget(infoButton, selectionRow, infoColumn);
Chris@46 866
Chris@46 867 ++selectionRow;
Chris@46 868
Chris@77 869 QString moreTitleText = QObject::tr("<b>%1</b><br><i>%2</i>")
Chris@72 870 .arg(info.title)
Chris@77 871 .arg(info.maker);
Chris@77 872
Chris@79 873 QString moreInfoText = info.description;
Chris@79 874
Chris@79 875 if (info.page != "") {
Chris@79 876 moreInfoText += QObject::tr("<br><a href=\"%1\">%2</a>")
Chris@79 877 .arg(info.page)
Chris@79 878 .arg(info.page);
Chris@79 879 }
Chris@79 880
Chris@79 881 moreInfoText += QObject::tr("<br><br>Library contains:<ul>");
Chris@73 882
Chris@73 883 int n = 0;
Chris@73 884 bool closed = false;
Chris@73 885 for (auto title: info.pluginTitles) {
Chris@73 886 if (n == 10 && info.pluginTitles.size() > 15) {
Chris@77 887 moreInfoText += QObject::tr("</ul>");
Chris@77 888 moreInfoText += QObject::tr("... and %n other plugins.<br><br>",
Chris@77 889 "",
Chris@77 890 info.pluginTitles.size() - n);
Chris@73 891 closed = true;
Chris@73 892 break;
Chris@73 893 }
Chris@77 894 moreInfoText += QObject::tr("<li>%1</li>").arg(title);
Chris@73 895 ++n;
Chris@73 896 }
Chris@73 897
Chris@73 898 if (!closed) {
Chris@77 899 moreInfoText += QObject::tr("</ul>");
Chris@73 900 }
Chris@74 901
Chris@74 902 if (info.licence != "") {
Chris@99 903 moreInfoText += QObject::tr("Provided under the <a href=\"%1\">%2</a>.<br>")
Chris@99 904 .arg(getLicenceURL(info.licence))
Chris@77 905 .arg(info.licence);
Chris@74 906 }
Chris@72 907
Chris@79 908 QObject::connect(infoButton, &QAbstractButton::clicked,
Chris@72 909 [=]() {
Chris@74 910 QMessageBox mbox;
Chris@74 911 mbox.setIconPixmap(moreMap);
Chris@74 912 mbox.setWindowTitle(QObject::tr("Library contents"));
Chris@77 913 mbox.setText(moreTitleText);
Chris@77 914 mbox.setInformativeText(moreInfoText);
Chris@74 915 mbox.exec();
Chris@72 916 });
Chris@72 917
Chris@43 918 checkBoxMap[info.fileName] = cb;
Chris@67 919 libFileInfo[info.fileName] = info;
Chris@76 920 statuses[info.fileName] = relativeStatus;
Chris@42 921 }
Chris@42 922
Chris@79 923 selectionLayout->addItem(new QSpacerItem(1, (fontHeight*2) / 3),
Chris@79 924 selectionRow, 0);
Chris@79 925 ++selectionRow;
Chris@79 926
Chris@79 927 selectionLayout->addWidget
Chris@79 928 (new QLabel(QObject::tr("Installation will be to: %1").arg(targetDir)),
Chris@84 929 selectionRow, titleColumn, 1, 3);
Chris@79 930 ++selectionRow;
Chris@79 931
Chris@47 932 QObject::connect(checkAll, &QCheckBox::toggled,
Chris@72 933 [=](bool toCheck) {
Chris@47 934 for (auto p: checkBoxMap) {
Chris@47 935 p.second->setChecked(toCheck);
Chris@47 936 }
Chris@47 937 });
Chris@79 938
Chris@79 939 mainLayout->addItem(new QSpacerItem(1, fontHeight), mainRow, 0);
Chris@79 940 ++mainRow;
Chris@79 941
Chris@42 942 auto bb = new QDialogButtonBox(QDialogButtonBox::Ok |
Chris@76 943 QDialogButtonBox::Cancel |
Chris@99 944 QDialogButtonBox::Reset |
Chris@99 945 QDialogButtonBox::Help);
Chris@79 946 bb->button(QDialogButtonBox::Ok)->setText(QObject::tr("Install"));
Chris@74 947 mainLayout->addWidget(bb, mainRow, 0);
Chris@46 948 ++mainRow;
Chris@47 949
Chris@74 950 mainLayout->setRowStretch(0, 10);
Chris@74 951 mainLayout->setColumnStretch(0, 10);
Chris@74 952 selectionLayout->setColumnMinimumWidth(0, 50);
Chris@85 953 #ifdef Q_OS_MAC
Chris@85 954 selectionLayout->setColumnMinimumWidth(3, 10);
Chris@85 955 selectionLayout->setColumnMinimumWidth(5, 12);
Chris@85 956 #endif
Chris@74 957 selectionLayout->setColumnStretch(1, 10);
Chris@47 958
Chris@76 959 QObject::connect
Chris@76 960 (bb, &QDialogButtonBox::clicked,
Chris@76 961 [&](QAbstractButton *button) {
Chris@76 962
Chris@76 963 auto role = bb->buttonRole(button);
Chris@76 964
Chris@76 965 switch (role) {
Chris@76 966
Chris@76 967 case QDialogButtonBox::AcceptRole: {
Chris@76 968 bool downgrade = false;
Chris@76 969 for (const auto &p: checkBoxMap) {
Chris@76 970 if (p.second->isChecked() &&
Chris@76 971 statuses.at(p.first) == RelativeStatus::Downgrade) {
Chris@76 972 downgrade = true;
Chris@76 973 break;
Chris@76 974 }
Chris@76 975 }
Chris@76 976 if (downgrade) {
Chris@76 977 if (QMessageBox::warning
Chris@76 978 (bb, QObject::tr("Downgrade?"),
Chris@76 979 QObject::tr("You have asked to downgrade one or more plugin libraries that are already installed.<br><br>Are you sure?"),
Chris@76 980 QMessageBox::Ok | QMessageBox::Cancel,
Chris@76 981 QMessageBox::Cancel) == QMessageBox::Ok) {
Chris@76 982 dialog.accept();
Chris@76 983 }
Chris@76 984 } else {
Chris@76 985 dialog.accept();
Chris@76 986 }
Chris@76 987 break;
Chris@76 988 }
Chris@76 989
Chris@76 990 case QDialogButtonBox::RejectRole:
Chris@76 991 dialog.reject();
Chris@76 992 break;
Chris@76 993
Chris@76 994 case QDialogButtonBox::ResetRole:
Chris@76 995 for (const auto &p: checkBoxMap) {
Chris@76 996 p.second->setChecked(shouldCheck(statuses.at(p.first)));
Chris@76 997 }
Chris@76 998 break;
Chris@76 999
Chris@99 1000 case QDialogButtonBox::HelpRole: {
Chris@99 1001 QMessageBox mbox;
Chris@99 1002 mbox.setWindowTitle(QApplication::applicationName());
Chris@99 1003 mbox.setText(QObject::tr("<b>Vamp Plugin Pack</b>"));
Chris@99 1004 mbox.setInformativeText(getHelpText(libraries));
Chris@99 1005 mbox.exec();
Chris@99 1006 break;
Chris@99 1007 }
Chris@99 1008
Chris@76 1009 default:
Chris@76 1010 SVCERR << "WARNING: Unexpected role " << role << endl;
Chris@99 1011 break;
Chris@76 1012 }
Chris@76 1013 });
Chris@98 1014
Chris@98 1015 if (QString(PACK_VERSION).contains("-pre") ||
Chris@98 1016 QString(PACK_VERSION).contains("-alpha") ||
Chris@98 1017 QString(PACK_VERSION).contains("-beta")) {
Chris@98 1018 QTimer::singleShot
Chris@98 1019 (500, [&]() {
Chris@98 1020 QString url = "https://code.soundsoftware.ac.uk/projects/vamp-plugin-pack";
Chris@98 1021 QMessageBox::information
Chris@98 1022 (&dialog, QObject::tr("Test release"),
Chris@98 1023 QObject::tr("<b>This is a test release of %1</b><p>Please send any feedback to the developers. See <a href=\"%2\">%3</a> for more information.</p>").arg(QApplication::applicationName()).arg(url).arg(url));
Chris@98 1024 });
Chris@98 1025 }
Chris@98 1026
Chris@70 1027 if (dialog.exec() != QDialog::Accepted) {
Chris@51 1028 SVCERR << "rejected" << endl;
Chris@70 1029 return {};
Chris@42 1030 }
Chris@42 1031
Chris@75 1032 vector<LibraryInfo> approved;
Chris@42 1033 for (const auto &p: checkBoxMap) {
Chris@42 1034 if (p.second->isChecked()) {
Chris@75 1035 approved.push_back(libFileInfo[p.first]);
Chris@33 1036 }
Chris@42 1037 }
Chris@42 1038
Chris@42 1039 return approved;
Chris@42 1040 }
Chris@42 1041
Chris@42 1042 int main(int argc, char **argv)
Chris@42 1043 {
Chris@93 1044 if (argc == 2 && (QString(argv[1]) == "--version" ||
Chris@93 1045 QString(argv[1]) == "-v")) {
Chris@95 1046 cerr << PACK_VERSION << std::endl; // std:: needed here for MSVC for some reason
Chris@93 1047 exit(0);
Chris@93 1048 }
Chris@93 1049
Chris@42 1050 QApplication app(argc, argv);
Chris@42 1051
Chris@51 1052 QApplication::setOrganizationName("sonic-visualiser");
Chris@51 1053 QApplication::setOrganizationDomain("sonicvisualiser.org");
Chris@51 1054 QApplication::setApplicationName(QApplication::tr("Vamp Plugin Pack Installer"));
Chris@51 1055
Chris@77 1056 QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
Chris@77 1057
Chris@51 1058 #ifdef Q_OS_WIN32
Chris@51 1059 QFont font(QApplication::font());
Chris@51 1060 QString preferredFamily = "Segoe UI";
Chris@51 1061 font.setFamily(preferredFamily);
Chris@51 1062 if (QFontInfo(font).family() == preferredFamily) {
Chris@51 1063 font.setPointSize(10);
Chris@51 1064 QApplication::setFont(font);
Chris@51 1065 }
Chris@77 1066 #else
Chris@77 1067 #ifdef Q_OS_MAC
Chris@77 1068 QFont font(QApplication::font());
Chris@77 1069 QString preferredFamily = "Lucida Grande";
Chris@77 1070 font.setFamily(preferredFamily);
Chris@77 1071 if (QFontInfo(font).family() == preferredFamily) {
Chris@85 1072 font.setPointSize(12);
Chris@77 1073 QApplication::setFont(font);
Chris@77 1074 }
Chris@77 1075 #endif
Chris@51 1076 #endif
Chris@51 1077
Chris@42 1078 QString target = getDefaultInstallDirectory();
Chris@42 1079 if (target == "") {
Chris@42 1080 return 1;
Chris@42 1081 }
Chris@42 1082
Chris@42 1083 QStringList libraries = getPluginLibraryList();
Chris@42 1084
Chris@43 1085 auto rdfStore = loadLibrariesRdf();
Chris@43 1086
Chris@43 1087 auto info = getLibraryInfo(*rdfStore, libraries);
Chris@43 1088
Chris@75 1089 vector<LibraryInfo> toInstall =
Chris@75 1090 getUserApprovedPluginLibraries(info, target);
Chris@83 1091
Chris@100 1092 if (toInstall.empty()) { // Cancelled, or nothing selected
Chris@100 1093 SVCERR << "No libraries selected for installation, nothing to do"
Chris@100 1094 << endl;
Chris@100 1095 return 0;
Chris@100 1096 }
Chris@100 1097
Chris@81 1098 QProgressDialog progress(QObject::tr("Installing..."),
Chris@95 1099 QObject::tr("Stop"), 0,
Chris@95 1100 int(toInstall.size()) + 1);
Chris@81 1101 progress.setMinimumDuration(0);
Chris@99 1102
Chris@81 1103 int pval = 0;
Chris@81 1104 bool complete = true;
Chris@42 1105
Chris@42 1106 for (auto lib: toInstall) {
Chris@81 1107 progress.setValue(++pval);
Chris@81 1108 QThread::currentThread()->msleep(40);
Chris@81 1109 app.processEvents();
Chris@81 1110 if (progress.wasCanceled()) {
Chris@81 1111 complete = false;
Chris@81 1112 break;
Chris@81 1113 }
Chris@81 1114 QString error = installLibrary(lib, target);
Chris@81 1115 if (error != "") {
Chris@81 1116 complete = false;
Chris@81 1117 if (QMessageBox::critical
Chris@81 1118 (&progress,
Chris@81 1119 QObject::tr("Install failed"),
Chris@81 1120 QObject::tr("Failed to install library \"%1\": %2")
Chris@81 1121 .arg(lib.title)
Chris@81 1122 .arg(error),
Chris@81 1123 QMessageBox::Abort | QMessageBox::Ignore,
Chris@81 1124 QMessageBox::Ignore) ==
Chris@81 1125 QMessageBox::Abort) {
Chris@81 1126 break;
Chris@81 1127 }
Chris@81 1128 }
Chris@81 1129 }
Chris@81 1130
Chris@81 1131 progress.hide();
Chris@81 1132
Chris@81 1133 if (complete) {
Chris@81 1134 QMessageBox::information
Chris@81 1135 (&progress,
Chris@81 1136 QObject::tr("Complete"),
Chris@81 1137 QObject::tr("Installation completed successfully"),
Chris@81 1138 QMessageBox::Ok,
Chris@81 1139 QMessageBox::Ok);
Chris@81 1140 } else {
Chris@81 1141 QMessageBox::information
Chris@81 1142 (&progress,
Chris@81 1143 QObject::tr("Incomplete"),
Chris@81 1144 QObject::tr("Installation was not complete. Exiting"),
Chris@81 1145 QMessageBox::Ok,
Chris@81 1146 QMessageBox::Ok);
Chris@33 1147 }
Chris@33 1148
Chris@83 1149 return (complete ? 0 : 2);
Chris@32 1150 }