annotate installer.cpp @ 67:38cd115c91d4

Integrate version checker into installer program & build, + win32 signing bits
author Chris Cannam
date Thu, 13 Feb 2020 10:56:15 +0000
parents 85768d48e6ce
children d70c50c4a07c
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@46 39 #include <QScrollArea>
Chris@42 40 #include <QDialogButtonBox>
Chris@46 41 #include <QLabel>
Chris@51 42 #include <QFont>
Chris@51 43 #include <QFontInfo>
Chris@67 44 #include <QTemporaryFile>
Chris@67 45 #include <QMutex>
Chris@67 46 #include <QMutexLocker>
Chris@67 47 #include <QProcess>
Chris@42 48
Chris@41 49 #include <vamp-hostsdk/PluginHostAdapter.h>
Chris@41 50
Chris@43 51 #include <dataquay/BasicStore.h>
Chris@43 52 #include <dataquay/RDFException.h>
Chris@43 53
Chris@33 54 #include <iostream>
Chris@58 55 #include <memory>
Chris@43 56 #include <set>
Chris@43 57
Chris@67 58 #include <unistd.h>
Chris@67 59
Chris@51 60 #include "base/Debug.h"
Chris@51 61
Chris@33 62 using namespace std;
Chris@43 63 using namespace Dataquay;
Chris@32 64
Chris@42 65 QString
Chris@42 66 getDefaultInstallDirectory()
Chris@32 67 {
Chris@41 68 auto pathList = Vamp::PluginHostAdapter::getPluginPath();
Chris@41 69 if (pathList.empty()) {
Chris@51 70 SVCERR << "Failed to look up Vamp plugin path" << endl;
Chris@42 71 return QString();
Chris@41 72 }
Chris@41 73
Chris@42 74 auto firstPath = *pathList.begin();
Chris@42 75 QString target = QString::fromUtf8(firstPath.c_str(), firstPath.size());
Chris@42 76 return target;
Chris@42 77 }
Chris@42 78
Chris@42 79 QStringList
Chris@42 80 getPluginLibraryList()
Chris@42 81 {
Chris@33 82 QDir dir(":out/");
Chris@33 83 auto entries = dir.entryList({ "*.so", "*.dll", "*.dylib" });
Chris@33 84
Chris@33 85 for (auto e: entries) {
Chris@51 86 SVCERR << e.toStdString() << endl;
Chris@33 87 }
Chris@33 88
Chris@42 89 return entries;
Chris@42 90 }
Chris@33 91
Chris@50 92 void
Chris@50 93 loadLibraryRdf(BasicStore &store, QString filename)
Chris@50 94 {
Chris@50 95 QFile f(filename);
Chris@50 96 if (!f.open(QFile::ReadOnly | QFile::Text)) {
Chris@51 97 SVCERR << "Failed to open RDF resource file "
Chris@51 98 << filename.toStdString() << endl;
Chris@50 99 return;
Chris@50 100 }
Chris@50 101
Chris@50 102 QByteArray content = f.readAll();
Chris@50 103 f.close();
Chris@50 104
Chris@50 105 try {
Chris@50 106 store.importString(QString::fromUtf8(content),
Chris@50 107 Uri("file:" + filename),
Chris@50 108 BasicStore::ImportIgnoreDuplicates);
Chris@50 109 } catch (const RDFException &ex) {
Chris@51 110 SVCERR << "Failed to import RDF resource file "
Chris@51 111 << filename.toStdString() << ": " << ex.what() << endl;
Chris@50 112 }
Chris@50 113 }
Chris@50 114
Chris@43 115 unique_ptr<BasicStore>
Chris@43 116 loadLibrariesRdf()
Chris@43 117 {
Chris@43 118 unique_ptr<BasicStore> store(new BasicStore);
Chris@43 119
Chris@50 120 vector<QString> dirs { ":rdf/plugins", ":out" };
Chris@43 121
Chris@50 122 for (auto d: dirs) {
Chris@50 123 for (auto e: QDir(d).entryList({ "*.ttl", "*.n3" })) {
Chris@50 124 loadLibraryRdf(*store, d + "/" + e);
Chris@43 125 }
Chris@43 126 }
Chris@43 127
Chris@43 128 return store;
Chris@43 129 }
Chris@43 130
Chris@43 131 struct LibraryInfo {
Chris@43 132 QString id;
Chris@43 133 QString fileName;
Chris@43 134 QString title;
Chris@43 135 QString maker;
Chris@43 136 QString description;
Chris@47 137 QStringList pluginTitles;
Chris@67 138 map<QString, int> pluginVersions; // id -> version
Chris@43 139 };
Chris@43 140
Chris@43 141 vector<LibraryInfo>
Chris@43 142 getLibraryInfo(const Store &store, QStringList libraries)
Chris@43 143 {
Chris@43 144 /* e.g.
Chris@43 145
Chris@43 146 plugbase:library a vamp:PluginLibrary ;
Chris@43 147 vamp:identifier "qm-vamp-plugins" ;
Chris@43 148 dc:title "Queen Mary plugin set"
Chris@43 149 */
Chris@43 150
Chris@43 151 Triples tt = store.match(Triple(Node(),
Chris@43 152 Uri("a"),
Chris@43 153 store.expand("vamp:PluginLibrary")));
Chris@43 154
Chris@67 155 map<QString, QString> wanted; // basename -> full lib name
Chris@43 156 for (auto lib: libraries) {
Chris@43 157 wanted[QFileInfo(lib).baseName()] = lib;
Chris@43 158 }
Chris@43 159
Chris@43 160 vector<LibraryInfo> results;
Chris@43 161
Chris@43 162 for (auto t: tt) {
Chris@43 163
Chris@43 164 Node libId = store.complete(Triple(t.subject(),
Chris@43 165 store.expand("vamp:identifier"),
Chris@43 166 Node()));
Chris@43 167 if (libId.type != Node::Literal) {
Chris@43 168 continue;
Chris@43 169 }
Chris@43 170 auto wi = wanted.find(libId.value);
Chris@43 171 if (wi == wanted.end()) {
Chris@43 172 continue;
Chris@43 173 }
Chris@43 174
Chris@50 175 Node title = store.complete(Triple(t.subject(),
Chris@50 176 store.expand("dc:title"),
Chris@50 177 Node()));
Chris@50 178 if (title.type != Node::Literal) {
Chris@50 179 continue;
Chris@50 180 }
Chris@50 181
Chris@43 182 LibraryInfo info;
Chris@43 183 info.id = wi->first;
Chris@43 184 info.fileName = wi->second;
Chris@50 185 info.title = title.value;
Chris@43 186
Chris@43 187 Node maker = store.complete(Triple(t.subject(),
Chris@43 188 store.expand("foaf:maker"),
Chris@43 189 Node()));
Chris@43 190 if (maker.type == Node::Literal) {
Chris@43 191 info.maker = maker.value;
Chris@46 192 } else if (maker != Node()) {
Chris@46 193 maker = store.complete(Triple(maker,
Chris@46 194 store.expand("foaf:name"),
Chris@46 195 Node()));
Chris@46 196 if (maker.type == Node::Literal) {
Chris@46 197 info.maker = maker.value;
Chris@46 198 }
Chris@43 199 }
Chris@46 200
Chris@43 201 Node desc = store.complete(Triple(t.subject(),
Chris@43 202 store.expand("dc:description"),
Chris@43 203 Node()));
Chris@43 204 if (desc.type == Node::Literal) {
Chris@43 205 info.description = desc.value;
Chris@43 206 }
Chris@43 207
Chris@47 208 Triples pp = store.match(Triple(t.subject(),
Chris@47 209 store.expand("vamp:available_plugin"),
Chris@47 210 Node()));
Chris@47 211 for (auto p: pp) {
Chris@47 212 Node ptitle = store.complete(Triple(p.object(),
Chris@47 213 store.expand("dc:title"),
Chris@47 214 Node()));
Chris@47 215 if (ptitle.type == Node::Literal) {
Chris@47 216 info.pluginTitles.push_back(ptitle.value);
Chris@47 217 }
Chris@67 218
Chris@67 219 Node pident = store.complete(Triple(p.object(),
Chris@67 220 store.expand("vamp:identifier"),
Chris@67 221 Node()));
Chris@67 222 Node pversion = store.complete(Triple(p.object(),
Chris@67 223 store.expand("owl:versionInfo"),
Chris@67 224 Node()));
Chris@67 225 if (pident.type == Node::Literal &&
Chris@67 226 pversion.type == Node::Literal) {
Chris@67 227 bool ok = false;
Chris@67 228 int version = pversion.value.toInt(&ok);
Chris@67 229 if (ok) {
Chris@67 230 info.pluginVersions[pident.value] = version;
Chris@67 231 }
Chris@67 232 }
Chris@47 233 }
Chris@47 234
Chris@43 235 results.push_back(info);
Chris@50 236 wanted.erase(libId.value);
Chris@43 237 }
Chris@43 238
Chris@50 239 for (auto wp: wanted) {
Chris@51 240 SVCERR << "Failed to find any RDF information about library "
Chris@51 241 << wp.second << endl;
Chris@50 242 }
Chris@50 243
Chris@43 244 return results;
Chris@43 245 }
Chris@43 246
Chris@67 247 struct TempFileDeleter {
Chris@67 248 ~TempFileDeleter() {
Chris@67 249 if (tempFile != "") {
Chris@67 250 QFile(tempFile).remove();
Chris@67 251 }
Chris@67 252 }
Chris@67 253 QString tempFile;
Chris@67 254 };
Chris@67 255
Chris@67 256 map<QString, int>
Chris@67 257 getInstalledLibraryPluginVersions(QString libraryFilePath)
Chris@67 258 {
Chris@67 259 static QMutex mutex;
Chris@67 260 static QString tempFileName;
Chris@67 261 static TempFileDeleter deleter;
Chris@67 262 static bool initHappened = false, initSucceeded = false;
Chris@67 263
Chris@67 264 QMutexLocker locker (&mutex);
Chris@67 265
Chris@67 266 if (!initHappened) {
Chris@67 267 initHappened = true;
Chris@67 268
Chris@67 269 QTemporaryFile tempFile;
Chris@67 270 tempFile.setAutoRemove(false);
Chris@67 271 if (!tempFile.open()) {
Chris@67 272 SVCERR << "ERROR: Failed to open a temporary file" << endl;
Chris@67 273 return {};
Chris@67 274 }
Chris@67 275
Chris@67 276 // We can't make the QTemporaryFile static, as it will hold
Chris@67 277 // the file open and that prevents us from executing it. Hence
Chris@67 278 // the separate deleter.
Chris@67 279
Chris@67 280 tempFileName = tempFile.fileName();
Chris@67 281 deleter.tempFile = tempFileName;
Chris@67 282
Chris@67 283 #ifdef Q_OS_WIN32
Chris@67 284 QString helperPath = ":out/get-version.exe";
Chris@67 285 #else
Chris@67 286 QString helperPath = ":out/get-version";
Chris@67 287 #endif
Chris@67 288 QFile helper(helperPath);
Chris@67 289 if (!helper.open(QFile::ReadOnly)) {
Chris@67 290 SVCERR << "ERROR: Failed to read helper code" << endl;
Chris@67 291 return {};
Chris@67 292 }
Chris@67 293 QByteArray content = helper.readAll();
Chris@67 294 helper.close();
Chris@67 295
Chris@67 296 if (tempFile.write(content) != content.size()) {
Chris@67 297 SVCERR << "ERROR: Incomplete write to temporary file" << endl;
Chris@67 298 return {};
Chris@67 299 }
Chris@67 300 tempFile.close();
Chris@67 301
Chris@67 302 if (!QFile::setPermissions
Chris@67 303 (tempFileName,
Chris@67 304 QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)) {
Chris@67 305 SVCERR << "ERROR: Failed to set execute permission on helper "
Chris@67 306 << tempFileName << endl;
Chris@67 307 return {};
Chris@67 308 }
Chris@67 309
Chris@67 310 initSucceeded = true;
Chris@67 311 }
Chris@67 312
Chris@67 313 if (!initSucceeded) {
Chris@67 314 return {};
Chris@67 315 }
Chris@67 316
Chris@67 317 QProcess process;
Chris@67 318 process.start(tempFileName, { libraryFilePath });
Chris@67 319
Chris@67 320 if (!process.waitForStarted()) {
Chris@67 321 QProcess::ProcessError err = process.error();
Chris@67 322 if (err == QProcess::FailedToStart) {
Chris@67 323 SVCERR << "Unable to start helper process " << tempFileName << endl;
Chris@67 324 } else if (err == QProcess::Crashed) {
Chris@67 325 SVCERR << "Helper process " << tempFileName
Chris@67 326 << " crashed on startup" << endl;
Chris@67 327 } else {
Chris@67 328 SVCERR << "Helper process " << tempFileName
Chris@67 329 << " failed on startup with error code " << err << endl;
Chris@67 330 }
Chris@67 331 return {};
Chris@67 332 }
Chris@67 333 process.waitForFinished();
Chris@67 334
Chris@67 335 QByteArray stdOut = process.readAllStandardOutput();
Chris@67 336 QByteArray stdErr = process.readAllStandardError();
Chris@67 337
Chris@67 338 QString errStr = QString::fromUtf8(stdErr);
Chris@67 339 if (!errStr.isEmpty()) {
Chris@67 340 SVCERR << "Note: Helper process stderr follows:" << endl;
Chris@67 341 SVCERR << errStr << endl;
Chris@67 342 SVCERR << "Note: Helper process stderr ends" << endl;
Chris@67 343 }
Chris@67 344
Chris@67 345 QStringList lines = QString::fromUtf8(stdOut).split
Chris@67 346 (QRegExp("[\\r\\n]+"), QString::SkipEmptyParts);
Chris@67 347 map<QString, int> versions;
Chris@67 348 for (QString line: lines) {
Chris@67 349 QStringList parts = line.split(":");
Chris@67 350 if (parts.size() != 2) {
Chris@67 351 SVCERR << "Unparseable output line: " << line << endl;
Chris@67 352 continue;
Chris@67 353 }
Chris@67 354 bool ok = false;
Chris@67 355 int version = parts[1].toInt(&ok);
Chris@67 356 if (!ok) {
Chris@67 357 SVCERR << "Unparseable version number in line: " << line << endl;
Chris@67 358 continue;
Chris@67 359 }
Chris@67 360 versions[parts[0]] = version;
Chris@67 361 }
Chris@67 362
Chris@67 363 return versions;
Chris@67 364 }
Chris@67 365
Chris@67 366 bool isLibraryNewer(map<QString, int> a, map<QString, int> b)
Chris@67 367 {
Chris@67 368 // a and b are maps from plugin id to plugin version for libraries
Chris@67 369 // A and B. (There is no overarching library version number.) We
Chris@67 370 // deem library A to be newer than library B if:
Chris@67 371 //
Chris@67 372 // 1. A contains a plugin id that is also in B, whose version in
Chris@67 373 // A is newer than that in B, or
Chris@67 374 //
Chris@67 375 // 2. B is not newer than A according to rule 1, and neither A or
Chris@67 376 // B is empty, and A contains a plugin id that is not in B, and B
Chris@67 377 // does not contain any plugin id that is not in A
Chris@67 378 //
Chris@67 379 // (The not-empty part of rule 2 is just to avoid false positives
Chris@67 380 // when a library or its metadata could not be read at all.)
Chris@67 381
Chris@67 382 auto containsANewerPlugin = [](const map<QString, int> &m1,
Chris@67 383 const map<QString, int> &m2) {
Chris@67 384 for (auto p: m1) {
Chris@67 385 if (m2.find(p.first) != m2.end() &&
Chris@67 386 p.second > m2.at(p.first)) {
Chris@67 387 return true;
Chris@67 388 }
Chris@67 389 }
Chris@67 390 return false;
Chris@67 391 };
Chris@67 392
Chris@67 393 auto containsANovelPlugin = [](const map<QString, int> &m1,
Chris@67 394 const map<QString, int> &m2) {
Chris@67 395 for (auto p: m1) {
Chris@67 396 if (m2.find(p.first) == m2.end()) {
Chris@67 397 return true;
Chris@67 398 }
Chris@67 399 }
Chris@67 400 return false;
Chris@67 401 };
Chris@67 402
Chris@67 403 if (containsANewerPlugin(a, b)) {
Chris@67 404 return true;
Chris@67 405 }
Chris@67 406
Chris@67 407 if (!containsANewerPlugin(b, a) &&
Chris@67 408 !a.empty() &&
Chris@67 409 !b.empty() &&
Chris@67 410 containsANovelPlugin(a, b) &&
Chris@67 411 !containsANovelPlugin(b, a)) {
Chris@67 412 return true;
Chris@67 413 }
Chris@67 414
Chris@67 415 return false;
Chris@67 416 }
Chris@67 417
Chris@67 418 QString
Chris@67 419 versionsString(const map<QString, int> &vv)
Chris@67 420 {
Chris@67 421 QStringList pv;
Chris@67 422 for (auto v: vv) {
Chris@67 423 pv.push_back(QString("%1:%2").arg(v.first).arg(v.second));
Chris@67 424 }
Chris@67 425 return "{ " + pv.join(", ") + " }";
Chris@67 426 }
Chris@67 427
Chris@42 428 void
Chris@67 429 installLibrary(QString library, LibraryInfo info, QString target)
Chris@42 430 {
Chris@52 431 QString source = ":out";
Chris@52 432 QFile f(source + "/" + library);
Chris@42 433 QString destination = target + "/" + library;
Chris@67 434
Chris@67 435 if (QFileInfo(destination).exists()) {
Chris@67 436 auto installed = getInstalledLibraryPluginVersions(destination);
Chris@67 437 SVCERR << "Note: comparing installed plugin versions "
Chris@67 438 << versionsString(installed)
Chris@67 439 << " to packaged versions "
Chris@67 440 << versionsString(info.pluginVersions)
Chris@67 441 << ": isLibraryNewer(installed, packaged) returns "
Chris@67 442 << isLibraryNewer(installed, info.pluginVersions)
Chris@67 443 << endl;
Chris@67 444 }
Chris@52 445
Chris@51 446 SVCERR << "Copying " << library.toStdString() << " to "
Chris@51 447 << destination.toStdString() << "..." << endl;
Chris@42 448 if (!f.copy(destination)) {
Chris@51 449 SVCERR << "Failed to copy " << library.toStdString()
Chris@51 450 << " to target " << destination.toStdString() << endl;
Chris@42 451 return;
Chris@42 452 }
Chris@42 453 if (!QFile::setPermissions
Chris@42 454 (destination,
Chris@42 455 QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner |
Chris@42 456 QFile::ReadGroup | QFile::ExeGroup |
Chris@42 457 QFile::ReadOther | QFile::ExeOther)) {
Chris@51 458 SVCERR << "Failed to set permissions on "
Chris@51 459 << library.toStdString() << endl;
Chris@42 460 return;
Chris@42 461 }
Chris@52 462
Chris@52 463 QString base = QFileInfo(library).baseName();
Chris@52 464 QDir dir(source);
Chris@52 465 auto entries = dir.entryList({ base + "*" });
Chris@52 466 for (auto e: entries) {
Chris@52 467 if (e == library) continue;
Chris@52 468 QString destination = target + "/" + e;
Chris@52 469 SVCERR << "Copying " << e.toStdString() << " to "
Chris@52 470 << destination.toStdString() << "..." << endl;
Chris@52 471 if (!QFile(source + "/" + e).copy(destination)) {
Chris@52 472 SVCERR << "Failed to copy " << e.toStdString()
Chris@52 473 << " to target " << destination.toStdString()
Chris@52 474 << " (ignoring)" << endl;
Chris@52 475 }
Chris@52 476 }
Chris@42 477 }
Chris@42 478
Chris@67 479 map<QString, LibraryInfo>
Chris@43 480 getUserApprovedPluginLibraries(vector<LibraryInfo> libraries)
Chris@42 481 {
Chris@42 482 QDialog dialog;
Chris@46 483
Chris@46 484 auto mainLayout = new QGridLayout;
Chris@47 485 mainLayout->setSpacing(0);
Chris@46 486 dialog.setLayout(mainLayout);
Chris@46 487
Chris@46 488 int mainRow = 0;
Chris@46 489
Chris@47 490 auto checkAll = new QCheckBox;
Chris@53 491 checkAll->setChecked(true);
Chris@47 492 mainLayout->addWidget(checkAll, mainRow, 0, Qt::AlignHCenter);
Chris@47 493 ++mainRow;
Chris@47 494
Chris@47 495 auto checkArrow = new QLabel("&#9660;");
Chris@47 496 checkArrow->setTextFormat(Qt::RichText);
Chris@47 497 mainLayout->addWidget(checkArrow, mainRow, 0, Qt::AlignHCenter);
Chris@47 498 ++mainRow;
Chris@47 499
Chris@46 500 auto scroll = new QScrollArea;
Chris@47 501 scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
Chris@46 502 mainLayout->addWidget(scroll, mainRow, 0, 1, 2);
Chris@46 503 mainLayout->setRowStretch(mainRow, 10);
Chris@46 504 ++mainRow;
Chris@46 505
Chris@46 506 auto selectionFrame = new QWidget;
Chris@46 507
Chris@46 508 auto selectionLayout = new QGridLayout;
Chris@46 509 selectionFrame->setLayout(selectionLayout);
Chris@46 510 int selectionRow = 0;
Chris@42 511
Chris@67 512 map<QString, QCheckBox *> checkBoxMap; // filename -> checkbox
Chris@67 513 map<QString, LibraryInfo> libFileInfo; // filename -> info
Chris@43 514
Chris@67 515 map<QString, LibraryInfo, function<bool (QString, QString)>>
Chris@49 516 orderedInfo
Chris@49 517 ([](QString k1, QString k2) {
Chris@49 518 return k1.localeAwareCompare(k2) < 0;
Chris@49 519 });
Chris@43 520 for (auto info: libraries) {
Chris@43 521 orderedInfo[info.title] = info;
Chris@43 522 }
Chris@53 523
Chris@43 524 for (auto ip: orderedInfo) {
Chris@46 525
Chris@46 526 auto cb = new QCheckBox;
Chris@53 527 cb->setChecked(true);
Chris@53 528
Chris@47 529 selectionLayout->addWidget(cb, selectionRow, 0,
Chris@47 530 Qt::AlignTop | Qt::AlignHCenter);
Chris@46 531
Chris@43 532 LibraryInfo info = ip.second;
Chris@47 533 /*
Chris@47 534 int n = info.pluginTitles.size();
Chris@47 535 QString contents;
Chris@47 536
Chris@47 537 if (n > 0) {
Chris@47 538 int max = 4;
Chris@47 539 QStringList titles;
Chris@47 540 for (int i = 0; i < max && i < int(info.pluginTitles.size()); ++i) {
Chris@47 541 titles.push_back(info.pluginTitles[i]);
Chris@47 542 }
Chris@47 543 QString titleText = titles.join(", ");
Chris@47 544 if (max < int(info.pluginTitles.size())) {
Chris@47 545 titleText = QObject::tr("%1 ...").arg(titleText);
Chris@47 546 }
Chris@47 547 contents = QObject::tr("Plugins: %1").arg(titleText);
Chris@47 548 }
Chris@47 549 */
Chris@51 550 QString text = QObject::tr("<b>%1</b><br><i>%2</i><br>%3")
Chris@47 551 .arg(info.title)
Chris@47 552 .arg(info.maker)
Chris@47 553 .arg(info.description);
Chris@46 554
Chris@46 555 auto label = new QLabel(text);
Chris@47 556 label->setWordWrap(true);
Chris@47 557 label->setMinimumWidth(800);
Chris@46 558
Chris@46 559 selectionLayout->addWidget(label, selectionRow, 1, Qt::AlignTop);
Chris@46 560
Chris@46 561 ++selectionRow;
Chris@46 562
Chris@43 563 checkBoxMap[info.fileName] = cb;
Chris@67 564 libFileInfo[info.fileName] = info;
Chris@42 565 }
Chris@42 566
Chris@46 567 scroll->setWidget(selectionFrame);
Chris@46 568
Chris@47 569 QObject::connect(checkAll, &QCheckBox::toggled,
Chris@47 570 [=]() {
Chris@47 571 bool toCheck = checkAll->isChecked();
Chris@47 572 for (auto p: checkBoxMap) {
Chris@47 573 p.second->setChecked(toCheck);
Chris@47 574 }
Chris@47 575 });
Chris@47 576
Chris@42 577 auto bb = new QDialogButtonBox(QDialogButtonBox::Ok |
Chris@42 578 QDialogButtonBox::Cancel);
Chris@46 579 mainLayout->addWidget(bb, mainRow, 0, 1, 2);
Chris@46 580 ++mainRow;
Chris@47 581
Chris@47 582 int cw = 50;
Chris@47 583 mainLayout->setColumnMinimumWidth(0, cw + 20); //!!!
Chris@47 584 mainLayout->setColumnStretch(1, 10);
Chris@47 585 selectionLayout->setColumnMinimumWidth(0, cw); //!!!
Chris@53 586 selectionLayout->setColumnMinimumWidth(1, 820); //!!!
Chris@53 587 selectionLayout->setColumnStretch(1, 10);
Chris@47 588
Chris@42 589 QObject::connect(bb, SIGNAL(accepted()), &dialog, SLOT(accept()));
Chris@42 590 QObject::connect(bb, SIGNAL(rejected()), &dialog, SLOT(reject()));
Chris@53 591
Chris@42 592 if (dialog.exec() == QDialog::Accepted) {
Chris@51 593 SVCERR << "accepted" << endl;
Chris@42 594 } else {
Chris@51 595 SVCERR << "rejected" << endl;
Chris@42 596 }
Chris@42 597
Chris@67 598 map<QString, LibraryInfo> approved;
Chris@42 599 for (const auto &p: checkBoxMap) {
Chris@42 600 if (p.second->isChecked()) {
Chris@67 601 approved[p.first] = libFileInfo[p.first];
Chris@33 602 }
Chris@42 603 }
Chris@42 604
Chris@42 605 return approved;
Chris@42 606 }
Chris@42 607
Chris@42 608 int main(int argc, char **argv)
Chris@42 609 {
Chris@42 610 QApplication app(argc, argv);
Chris@42 611
Chris@51 612 QApplication::setOrganizationName("sonic-visualiser");
Chris@51 613 QApplication::setOrganizationDomain("sonicvisualiser.org");
Chris@51 614 QApplication::setApplicationName(QApplication::tr("Vamp Plugin Pack Installer"));
Chris@51 615
Chris@51 616 #ifdef Q_OS_WIN32
Chris@51 617 QFont font(QApplication::font());
Chris@51 618 QString preferredFamily = "Segoe UI";
Chris@51 619 font.setFamily(preferredFamily);
Chris@51 620 if (QFontInfo(font).family() == preferredFamily) {
Chris@51 621 font.setPointSize(10);
Chris@51 622 QApplication::setFont(font);
Chris@51 623 }
Chris@51 624 #endif
Chris@51 625
Chris@42 626 QString target = getDefaultInstallDirectory();
Chris@42 627 if (target == "") {
Chris@42 628 return 1;
Chris@42 629 }
Chris@42 630
Chris@42 631 QStringList libraries = getPluginLibraryList();
Chris@42 632
Chris@43 633 auto rdfStore = loadLibrariesRdf();
Chris@43 634
Chris@43 635 auto info = getLibraryInfo(*rdfStore, libraries);
Chris@43 636
Chris@67 637 map<QString, LibraryInfo> toInstall = getUserApprovedPluginLibraries(info);
Chris@52 638
Chris@52 639 if (!toInstall.empty()) {
Chris@52 640 if (!QDir(target).exists()) {
Chris@52 641 QDir().mkpath(target);
Chris@52 642 }
Chris@52 643 }
Chris@42 644
Chris@42 645 for (auto lib: toInstall) {
Chris@67 646 installLibrary(lib.first, lib.second, target);
Chris@33 647 }
Chris@33 648
Chris@32 649 return 0;
Chris@32 650 }