annotate installer.cpp @ 69:6b2b4bcbe4e5

Console application, not app bundle
author Chris Cannam
date Thu, 13 Feb 2020 11:34:55 +0000
parents d70c50c4a07c
children 103ee4b82f30
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@68 444 } else {
Chris@68 445 SVCERR << "Note: library " << library
Chris@68 446 << " is not yet installed, not comparing versions" << endl;
Chris@67 447 }
Chris@52 448
Chris@51 449 SVCERR << "Copying " << library.toStdString() << " to "
Chris@51 450 << destination.toStdString() << "..." << endl;
Chris@42 451 if (!f.copy(destination)) {
Chris@51 452 SVCERR << "Failed to copy " << library.toStdString()
Chris@51 453 << " to target " << destination.toStdString() << endl;
Chris@42 454 return;
Chris@42 455 }
Chris@42 456 if (!QFile::setPermissions
Chris@42 457 (destination,
Chris@42 458 QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner |
Chris@42 459 QFile::ReadGroup | QFile::ExeGroup |
Chris@42 460 QFile::ReadOther | QFile::ExeOther)) {
Chris@51 461 SVCERR << "Failed to set permissions on "
Chris@51 462 << library.toStdString() << endl;
Chris@42 463 return;
Chris@42 464 }
Chris@52 465
Chris@52 466 QString base = QFileInfo(library).baseName();
Chris@52 467 QDir dir(source);
Chris@52 468 auto entries = dir.entryList({ base + "*" });
Chris@52 469 for (auto e: entries) {
Chris@52 470 if (e == library) continue;
Chris@52 471 QString destination = target + "/" + e;
Chris@52 472 SVCERR << "Copying " << e.toStdString() << " to "
Chris@52 473 << destination.toStdString() << "..." << endl;
Chris@52 474 if (!QFile(source + "/" + e).copy(destination)) {
Chris@52 475 SVCERR << "Failed to copy " << e.toStdString()
Chris@52 476 << " to target " << destination.toStdString()
Chris@52 477 << " (ignoring)" << endl;
Chris@68 478 continue;
Chris@68 479 }
Chris@68 480 if (!QFile::setPermissions
Chris@68 481 (destination,
Chris@68 482 QFile::ReadOwner | QFile::WriteOwner |
Chris@68 483 QFile::ReadGroup |
Chris@68 484 QFile::ReadOther)) {
Chris@68 485 SVCERR << "Failed to set permissions on "
Chris@68 486 << destination.toStdString()
Chris@68 487 << " (ignoring)" << endl;
Chris@68 488 continue;
Chris@52 489 }
Chris@52 490 }
Chris@42 491 }
Chris@42 492
Chris@67 493 map<QString, LibraryInfo>
Chris@43 494 getUserApprovedPluginLibraries(vector<LibraryInfo> libraries)
Chris@42 495 {
Chris@42 496 QDialog dialog;
Chris@46 497
Chris@46 498 auto mainLayout = new QGridLayout;
Chris@47 499 mainLayout->setSpacing(0);
Chris@46 500 dialog.setLayout(mainLayout);
Chris@46 501
Chris@46 502 int mainRow = 0;
Chris@46 503
Chris@47 504 auto checkAll = new QCheckBox;
Chris@53 505 checkAll->setChecked(true);
Chris@47 506 mainLayout->addWidget(checkAll, mainRow, 0, Qt::AlignHCenter);
Chris@47 507 ++mainRow;
Chris@47 508
Chris@47 509 auto checkArrow = new QLabel("&#9660;");
Chris@47 510 checkArrow->setTextFormat(Qt::RichText);
Chris@47 511 mainLayout->addWidget(checkArrow, mainRow, 0, Qt::AlignHCenter);
Chris@47 512 ++mainRow;
Chris@47 513
Chris@46 514 auto scroll = new QScrollArea;
Chris@47 515 scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
Chris@46 516 mainLayout->addWidget(scroll, mainRow, 0, 1, 2);
Chris@46 517 mainLayout->setRowStretch(mainRow, 10);
Chris@46 518 ++mainRow;
Chris@46 519
Chris@46 520 auto selectionFrame = new QWidget;
Chris@46 521
Chris@46 522 auto selectionLayout = new QGridLayout;
Chris@46 523 selectionFrame->setLayout(selectionLayout);
Chris@46 524 int selectionRow = 0;
Chris@42 525
Chris@67 526 map<QString, QCheckBox *> checkBoxMap; // filename -> checkbox
Chris@67 527 map<QString, LibraryInfo> libFileInfo; // filename -> info
Chris@43 528
Chris@67 529 map<QString, LibraryInfo, function<bool (QString, QString)>>
Chris@49 530 orderedInfo
Chris@49 531 ([](QString k1, QString k2) {
Chris@49 532 return k1.localeAwareCompare(k2) < 0;
Chris@49 533 });
Chris@43 534 for (auto info: libraries) {
Chris@43 535 orderedInfo[info.title] = info;
Chris@43 536 }
Chris@53 537
Chris@43 538 for (auto ip: orderedInfo) {
Chris@46 539
Chris@46 540 auto cb = new QCheckBox;
Chris@53 541 cb->setChecked(true);
Chris@53 542
Chris@47 543 selectionLayout->addWidget(cb, selectionRow, 0,
Chris@47 544 Qt::AlignTop | Qt::AlignHCenter);
Chris@46 545
Chris@43 546 LibraryInfo info = ip.second;
Chris@47 547 /*
Chris@47 548 int n = info.pluginTitles.size();
Chris@47 549 QString contents;
Chris@47 550
Chris@47 551 if (n > 0) {
Chris@47 552 int max = 4;
Chris@47 553 QStringList titles;
Chris@47 554 for (int i = 0; i < max && i < int(info.pluginTitles.size()); ++i) {
Chris@47 555 titles.push_back(info.pluginTitles[i]);
Chris@47 556 }
Chris@47 557 QString titleText = titles.join(", ");
Chris@47 558 if (max < int(info.pluginTitles.size())) {
Chris@47 559 titleText = QObject::tr("%1 ...").arg(titleText);
Chris@47 560 }
Chris@47 561 contents = QObject::tr("Plugins: %1").arg(titleText);
Chris@47 562 }
Chris@47 563 */
Chris@51 564 QString text = QObject::tr("<b>%1</b><br><i>%2</i><br>%3")
Chris@47 565 .arg(info.title)
Chris@47 566 .arg(info.maker)
Chris@47 567 .arg(info.description);
Chris@46 568
Chris@46 569 auto label = new QLabel(text);
Chris@47 570 label->setWordWrap(true);
Chris@47 571 label->setMinimumWidth(800);
Chris@46 572
Chris@46 573 selectionLayout->addWidget(label, selectionRow, 1, Qt::AlignTop);
Chris@46 574
Chris@46 575 ++selectionRow;
Chris@46 576
Chris@43 577 checkBoxMap[info.fileName] = cb;
Chris@67 578 libFileInfo[info.fileName] = info;
Chris@42 579 }
Chris@42 580
Chris@46 581 scroll->setWidget(selectionFrame);
Chris@46 582
Chris@47 583 QObject::connect(checkAll, &QCheckBox::toggled,
Chris@47 584 [=]() {
Chris@47 585 bool toCheck = checkAll->isChecked();
Chris@47 586 for (auto p: checkBoxMap) {
Chris@47 587 p.second->setChecked(toCheck);
Chris@47 588 }
Chris@47 589 });
Chris@47 590
Chris@42 591 auto bb = new QDialogButtonBox(QDialogButtonBox::Ok |
Chris@42 592 QDialogButtonBox::Cancel);
Chris@46 593 mainLayout->addWidget(bb, mainRow, 0, 1, 2);
Chris@46 594 ++mainRow;
Chris@47 595
Chris@47 596 int cw = 50;
Chris@47 597 mainLayout->setColumnMinimumWidth(0, cw + 20); //!!!
Chris@47 598 mainLayout->setColumnStretch(1, 10);
Chris@47 599 selectionLayout->setColumnMinimumWidth(0, cw); //!!!
Chris@53 600 selectionLayout->setColumnMinimumWidth(1, 820); //!!!
Chris@53 601 selectionLayout->setColumnStretch(1, 10);
Chris@47 602
Chris@42 603 QObject::connect(bb, SIGNAL(accepted()), &dialog, SLOT(accept()));
Chris@42 604 QObject::connect(bb, SIGNAL(rejected()), &dialog, SLOT(reject()));
Chris@53 605
Chris@42 606 if (dialog.exec() == QDialog::Accepted) {
Chris@51 607 SVCERR << "accepted" << endl;
Chris@42 608 } else {
Chris@51 609 SVCERR << "rejected" << endl;
Chris@42 610 }
Chris@42 611
Chris@67 612 map<QString, LibraryInfo> approved;
Chris@42 613 for (const auto &p: checkBoxMap) {
Chris@42 614 if (p.second->isChecked()) {
Chris@67 615 approved[p.first] = libFileInfo[p.first];
Chris@33 616 }
Chris@42 617 }
Chris@42 618
Chris@42 619 return approved;
Chris@42 620 }
Chris@42 621
Chris@42 622 int main(int argc, char **argv)
Chris@42 623 {
Chris@42 624 QApplication app(argc, argv);
Chris@42 625
Chris@51 626 QApplication::setOrganizationName("sonic-visualiser");
Chris@51 627 QApplication::setOrganizationDomain("sonicvisualiser.org");
Chris@51 628 QApplication::setApplicationName(QApplication::tr("Vamp Plugin Pack Installer"));
Chris@51 629
Chris@51 630 #ifdef Q_OS_WIN32
Chris@51 631 QFont font(QApplication::font());
Chris@51 632 QString preferredFamily = "Segoe UI";
Chris@51 633 font.setFamily(preferredFamily);
Chris@51 634 if (QFontInfo(font).family() == preferredFamily) {
Chris@51 635 font.setPointSize(10);
Chris@51 636 QApplication::setFont(font);
Chris@51 637 }
Chris@51 638 #endif
Chris@51 639
Chris@42 640 QString target = getDefaultInstallDirectory();
Chris@42 641 if (target == "") {
Chris@42 642 return 1;
Chris@42 643 }
Chris@42 644
Chris@42 645 QStringList libraries = getPluginLibraryList();
Chris@42 646
Chris@43 647 auto rdfStore = loadLibrariesRdf();
Chris@43 648
Chris@43 649 auto info = getLibraryInfo(*rdfStore, libraries);
Chris@43 650
Chris@67 651 map<QString, LibraryInfo> toInstall = getUserApprovedPluginLibraries(info);
Chris@52 652
Chris@52 653 if (!toInstall.empty()) {
Chris@52 654 if (!QDir(target).exists()) {
Chris@52 655 QDir().mkpath(target);
Chris@52 656 }
Chris@52 657 }
Chris@42 658
Chris@42 659 for (auto lib: toInstall) {
Chris@67 660 installLibrary(lib.first, lib.second, target);
Chris@33 661 }
Chris@33 662
Chris@32 663 return 0;
Chris@32 664 }