annotate installer.cpp @ 64:0c94d3065ecd

Fix app signing; remove plugin signing from project files, as we now need to separate out the plugin build, plugin signing, and installer build phases on every platform
author Chris Cannam
date Tue, 11 Feb 2020 10:16:49 +0000
parents df3b00f82e8d
children 85768d48e6ce
rev   line source
Chris@33 1
Chris@33 2 #include <QApplication>
Chris@33 3 #include <QString>
Chris@33 4 #include <QFile>
Chris@33 5 #include <QDir>
Chris@33 6
Chris@42 7 #include <QDialog>
Chris@42 8 #include <QFrame>
Chris@42 9 #include <QVBoxLayout>
Chris@42 10 #include <QCheckBox>
Chris@46 11 #include <QScrollArea>
Chris@42 12 #include <QDialogButtonBox>
Chris@46 13 #include <QLabel>
Chris@51 14 #include <QFont>
Chris@51 15 #include <QFontInfo>
Chris@42 16
Chris@41 17 #include <vamp-hostsdk/PluginHostAdapter.h>
Chris@41 18
Chris@43 19 #include <dataquay/BasicStore.h>
Chris@43 20 #include <dataquay/RDFException.h>
Chris@43 21
Chris@33 22 #include <iostream>
Chris@58 23 #include <memory>
Chris@43 24 #include <set>
Chris@43 25
Chris@51 26 #include "base/Debug.h"
Chris@51 27
Chris@33 28 using namespace std;
Chris@43 29 using namespace Dataquay;
Chris@32 30
Chris@42 31 QString
Chris@42 32 getDefaultInstallDirectory()
Chris@32 33 {
Chris@41 34 auto pathList = Vamp::PluginHostAdapter::getPluginPath();
Chris@41 35 if (pathList.empty()) {
Chris@51 36 SVCERR << "Failed to look up Vamp plugin path" << endl;
Chris@42 37 return QString();
Chris@41 38 }
Chris@41 39
Chris@42 40 auto firstPath = *pathList.begin();
Chris@42 41 QString target = QString::fromUtf8(firstPath.c_str(), firstPath.size());
Chris@42 42 return target;
Chris@42 43 }
Chris@42 44
Chris@42 45 QStringList
Chris@42 46 getPluginLibraryList()
Chris@42 47 {
Chris@33 48 QDir dir(":out/");
Chris@33 49 auto entries = dir.entryList({ "*.so", "*.dll", "*.dylib" });
Chris@33 50
Chris@33 51 for (auto e: entries) {
Chris@51 52 SVCERR << e.toStdString() << endl;
Chris@33 53 }
Chris@33 54
Chris@42 55 return entries;
Chris@42 56 }
Chris@33 57
Chris@50 58 void
Chris@50 59 loadLibraryRdf(BasicStore &store, QString filename)
Chris@50 60 {
Chris@50 61 QFile f(filename);
Chris@50 62 if (!f.open(QFile::ReadOnly | QFile::Text)) {
Chris@51 63 SVCERR << "Failed to open RDF resource file "
Chris@51 64 << filename.toStdString() << endl;
Chris@50 65 return;
Chris@50 66 }
Chris@50 67
Chris@50 68 QByteArray content = f.readAll();
Chris@50 69 f.close();
Chris@50 70
Chris@50 71 try {
Chris@50 72 store.importString(QString::fromUtf8(content),
Chris@50 73 Uri("file:" + filename),
Chris@50 74 BasicStore::ImportIgnoreDuplicates);
Chris@50 75 } catch (const RDFException &ex) {
Chris@51 76 SVCERR << "Failed to import RDF resource file "
Chris@51 77 << filename.toStdString() << ": " << ex.what() << endl;
Chris@50 78 }
Chris@50 79 }
Chris@50 80
Chris@43 81 unique_ptr<BasicStore>
Chris@43 82 loadLibrariesRdf()
Chris@43 83 {
Chris@43 84 unique_ptr<BasicStore> store(new BasicStore);
Chris@43 85
Chris@50 86 vector<QString> dirs { ":rdf/plugins", ":out" };
Chris@43 87
Chris@50 88 for (auto d: dirs) {
Chris@50 89 for (auto e: QDir(d).entryList({ "*.ttl", "*.n3" })) {
Chris@50 90 loadLibraryRdf(*store, d + "/" + e);
Chris@43 91 }
Chris@43 92 }
Chris@43 93
Chris@43 94 return store;
Chris@43 95 }
Chris@43 96
Chris@43 97 struct LibraryInfo {
Chris@43 98 QString id;
Chris@43 99 QString fileName;
Chris@43 100 QString title;
Chris@43 101 QString maker;
Chris@43 102 QString description;
Chris@47 103 QStringList pluginTitles;
Chris@43 104 };
Chris@43 105
Chris@43 106 vector<LibraryInfo>
Chris@43 107 getLibraryInfo(const Store &store, QStringList libraries)
Chris@43 108 {
Chris@43 109 /* e.g.
Chris@43 110
Chris@43 111 plugbase:library a vamp:PluginLibrary ;
Chris@43 112 vamp:identifier "qm-vamp-plugins" ;
Chris@43 113 dc:title "Queen Mary plugin set"
Chris@43 114 */
Chris@43 115
Chris@43 116 Triples tt = store.match(Triple(Node(),
Chris@43 117 Uri("a"),
Chris@43 118 store.expand("vamp:PluginLibrary")));
Chris@43 119
Chris@43 120 std::map<QString, QString> wanted; // basename -> full lib name
Chris@43 121 for (auto lib: libraries) {
Chris@43 122 wanted[QFileInfo(lib).baseName()] = lib;
Chris@43 123 }
Chris@43 124
Chris@43 125 vector<LibraryInfo> results;
Chris@43 126
Chris@43 127 for (auto t: tt) {
Chris@43 128
Chris@43 129 Node libId = store.complete(Triple(t.subject(),
Chris@43 130 store.expand("vamp:identifier"),
Chris@43 131 Node()));
Chris@43 132 if (libId.type != Node::Literal) {
Chris@43 133 continue;
Chris@43 134 }
Chris@43 135 auto wi = wanted.find(libId.value);
Chris@43 136 if (wi == wanted.end()) {
Chris@43 137 continue;
Chris@43 138 }
Chris@43 139
Chris@50 140 Node title = store.complete(Triple(t.subject(),
Chris@50 141 store.expand("dc:title"),
Chris@50 142 Node()));
Chris@50 143 if (title.type != Node::Literal) {
Chris@50 144 continue;
Chris@50 145 }
Chris@50 146
Chris@43 147 LibraryInfo info;
Chris@43 148 info.id = wi->first;
Chris@43 149 info.fileName = wi->second;
Chris@50 150 info.title = title.value;
Chris@43 151
Chris@43 152 Node maker = store.complete(Triple(t.subject(),
Chris@43 153 store.expand("foaf:maker"),
Chris@43 154 Node()));
Chris@43 155 if (maker.type == Node::Literal) {
Chris@43 156 info.maker = maker.value;
Chris@46 157 } else if (maker != Node()) {
Chris@46 158 maker = store.complete(Triple(maker,
Chris@46 159 store.expand("foaf:name"),
Chris@46 160 Node()));
Chris@46 161 if (maker.type == Node::Literal) {
Chris@46 162 info.maker = maker.value;
Chris@46 163 }
Chris@43 164 }
Chris@46 165
Chris@43 166 Node desc = store.complete(Triple(t.subject(),
Chris@43 167 store.expand("dc:description"),
Chris@43 168 Node()));
Chris@43 169 if (desc.type == Node::Literal) {
Chris@43 170 info.description = desc.value;
Chris@43 171 }
Chris@43 172
Chris@47 173 Triples pp = store.match(Triple(t.subject(),
Chris@47 174 store.expand("vamp:available_plugin"),
Chris@47 175 Node()));
Chris@47 176 for (auto p: pp) {
Chris@47 177 Node ptitle = store.complete(Triple(p.object(),
Chris@47 178 store.expand("dc:title"),
Chris@47 179 Node()));
Chris@47 180 if (ptitle.type == Node::Literal) {
Chris@47 181 info.pluginTitles.push_back(ptitle.value);
Chris@47 182 }
Chris@47 183 }
Chris@47 184
Chris@43 185 results.push_back(info);
Chris@50 186 wanted.erase(libId.value);
Chris@43 187 }
Chris@43 188
Chris@50 189 for (auto wp: wanted) {
Chris@51 190 SVCERR << "Failed to find any RDF information about library "
Chris@51 191 << wp.second << endl;
Chris@50 192 }
Chris@50 193
Chris@43 194 return results;
Chris@43 195 }
Chris@43 196
Chris@42 197 void
Chris@42 198 installLibrary(QString library, QString target)
Chris@42 199 {
Chris@52 200 QString source = ":out";
Chris@52 201 QFile f(source + "/" + library);
Chris@42 202 QString destination = target + "/" + library;
Chris@52 203
Chris@51 204 SVCERR << "Copying " << library.toStdString() << " to "
Chris@51 205 << destination.toStdString() << "..." << endl;
Chris@42 206 if (!f.copy(destination)) {
Chris@51 207 SVCERR << "Failed to copy " << library.toStdString()
Chris@51 208 << " to target " << destination.toStdString() << endl;
Chris@42 209 return;
Chris@42 210 }
Chris@42 211 if (!QFile::setPermissions
Chris@42 212 (destination,
Chris@42 213 QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner |
Chris@42 214 QFile::ReadGroup | QFile::ExeGroup |
Chris@42 215 QFile::ReadOther | QFile::ExeOther)) {
Chris@51 216 SVCERR << "Failed to set permissions on "
Chris@51 217 << library.toStdString() << endl;
Chris@42 218 return;
Chris@42 219 }
Chris@52 220
Chris@52 221 QString base = QFileInfo(library).baseName();
Chris@52 222 QDir dir(source);
Chris@52 223 auto entries = dir.entryList({ base + "*" });
Chris@52 224 for (auto e: entries) {
Chris@52 225 if (e == library) continue;
Chris@52 226 QString destination = target + "/" + e;
Chris@52 227 SVCERR << "Copying " << e.toStdString() << " to "
Chris@52 228 << destination.toStdString() << "..." << endl;
Chris@52 229 if (!QFile(source + "/" + e).copy(destination)) {
Chris@52 230 SVCERR << "Failed to copy " << e.toStdString()
Chris@52 231 << " to target " << destination.toStdString()
Chris@52 232 << " (ignoring)" << endl;
Chris@52 233 }
Chris@52 234 }
Chris@42 235 }
Chris@42 236
Chris@42 237 QStringList
Chris@43 238 getUserApprovedPluginLibraries(vector<LibraryInfo> libraries)
Chris@42 239 {
Chris@42 240 QDialog dialog;
Chris@46 241
Chris@46 242 auto mainLayout = new QGridLayout;
Chris@47 243 mainLayout->setSpacing(0);
Chris@46 244 dialog.setLayout(mainLayout);
Chris@46 245
Chris@46 246 int mainRow = 0;
Chris@46 247
Chris@47 248 auto checkAll = new QCheckBox;
Chris@53 249 checkAll->setChecked(true);
Chris@47 250 mainLayout->addWidget(checkAll, mainRow, 0, Qt::AlignHCenter);
Chris@47 251 ++mainRow;
Chris@47 252
Chris@47 253 auto checkArrow = new QLabel("&#9660;");
Chris@47 254 checkArrow->setTextFormat(Qt::RichText);
Chris@47 255 mainLayout->addWidget(checkArrow, mainRow, 0, Qt::AlignHCenter);
Chris@47 256 ++mainRow;
Chris@47 257
Chris@46 258 auto scroll = new QScrollArea;
Chris@47 259 scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
Chris@46 260 mainLayout->addWidget(scroll, mainRow, 0, 1, 2);
Chris@46 261 mainLayout->setRowStretch(mainRow, 10);
Chris@46 262 ++mainRow;
Chris@46 263
Chris@46 264 auto selectionFrame = new QWidget;
Chris@46 265
Chris@46 266 auto selectionLayout = new QGridLayout;
Chris@46 267 selectionFrame->setLayout(selectionLayout);
Chris@46 268 int selectionRow = 0;
Chris@42 269
Chris@43 270 map<QString, QCheckBox *> checkBoxMap;
Chris@43 271
Chris@49 272 map<QString, LibraryInfo, std::function<bool (QString, QString)>>
Chris@49 273 orderedInfo
Chris@49 274 ([](QString k1, QString k2) {
Chris@49 275 return k1.localeAwareCompare(k2) < 0;
Chris@49 276 });
Chris@43 277 for (auto info: libraries) {
Chris@43 278 orderedInfo[info.title] = info;
Chris@43 279 }
Chris@53 280
Chris@43 281 for (auto ip: orderedInfo) {
Chris@46 282
Chris@46 283 auto cb = new QCheckBox;
Chris@53 284 cb->setChecked(true);
Chris@53 285
Chris@47 286 selectionLayout->addWidget(cb, selectionRow, 0,
Chris@47 287 Qt::AlignTop | Qt::AlignHCenter);
Chris@46 288
Chris@43 289 LibraryInfo info = ip.second;
Chris@47 290 /*
Chris@47 291 int n = info.pluginTitles.size();
Chris@47 292 QString contents;
Chris@47 293
Chris@47 294 if (n > 0) {
Chris@47 295 int max = 4;
Chris@47 296 QStringList titles;
Chris@47 297 for (int i = 0; i < max && i < int(info.pluginTitles.size()); ++i) {
Chris@47 298 titles.push_back(info.pluginTitles[i]);
Chris@47 299 }
Chris@47 300 QString titleText = titles.join(", ");
Chris@47 301 if (max < int(info.pluginTitles.size())) {
Chris@47 302 titleText = QObject::tr("%1 ...").arg(titleText);
Chris@47 303 }
Chris@47 304 contents = QObject::tr("Plugins: %1").arg(titleText);
Chris@47 305 }
Chris@47 306 */
Chris@51 307 QString text = QObject::tr("<b>%1</b><br><i>%2</i><br>%3")
Chris@47 308 .arg(info.title)
Chris@47 309 .arg(info.maker)
Chris@47 310 .arg(info.description);
Chris@46 311
Chris@46 312 auto label = new QLabel(text);
Chris@47 313 label->setWordWrap(true);
Chris@47 314 label->setMinimumWidth(800);
Chris@46 315
Chris@46 316 selectionLayout->addWidget(label, selectionRow, 1, Qt::AlignTop);
Chris@46 317
Chris@46 318 ++selectionRow;
Chris@46 319
Chris@43 320 checkBoxMap[info.fileName] = cb;
Chris@42 321 }
Chris@42 322
Chris@46 323 scroll->setWidget(selectionFrame);
Chris@46 324
Chris@47 325 QObject::connect(checkAll, &QCheckBox::toggled,
Chris@47 326 [=]() {
Chris@47 327 bool toCheck = checkAll->isChecked();
Chris@47 328 for (auto p: checkBoxMap) {
Chris@47 329 p.second->setChecked(toCheck);
Chris@47 330 }
Chris@47 331 });
Chris@47 332
Chris@42 333 auto bb = new QDialogButtonBox(QDialogButtonBox::Ok |
Chris@42 334 QDialogButtonBox::Cancel);
Chris@46 335 mainLayout->addWidget(bb, mainRow, 0, 1, 2);
Chris@46 336 ++mainRow;
Chris@47 337
Chris@47 338 int cw = 50;
Chris@47 339 mainLayout->setColumnMinimumWidth(0, cw + 20); //!!!
Chris@47 340 mainLayout->setColumnStretch(1, 10);
Chris@47 341 selectionLayout->setColumnMinimumWidth(0, cw); //!!!
Chris@53 342 selectionLayout->setColumnMinimumWidth(1, 820); //!!!
Chris@53 343 selectionLayout->setColumnStretch(1, 10);
Chris@47 344
Chris@42 345 QObject::connect(bb, SIGNAL(accepted()), &dialog, SLOT(accept()));
Chris@42 346 QObject::connect(bb, SIGNAL(rejected()), &dialog, SLOT(reject()));
Chris@53 347
Chris@42 348 if (dialog.exec() == QDialog::Accepted) {
Chris@51 349 SVCERR << "accepted" << endl;
Chris@42 350 } else {
Chris@51 351 SVCERR << "rejected" << endl;
Chris@42 352 }
Chris@42 353
Chris@42 354 QStringList approved;
Chris@42 355 for (const auto &p: checkBoxMap) {
Chris@42 356 if (p.second->isChecked()) {
Chris@42 357 approved.push_back(p.first);
Chris@33 358 }
Chris@42 359 }
Chris@42 360
Chris@42 361 return approved;
Chris@42 362 }
Chris@42 363
Chris@42 364 int main(int argc, char **argv)
Chris@42 365 {
Chris@42 366 QApplication app(argc, argv);
Chris@42 367
Chris@51 368 QApplication::setOrganizationName("sonic-visualiser");
Chris@51 369 QApplication::setOrganizationDomain("sonicvisualiser.org");
Chris@51 370 QApplication::setApplicationName(QApplication::tr("Vamp Plugin Pack Installer"));
Chris@51 371
Chris@51 372 #ifdef Q_OS_WIN32
Chris@51 373 QFont font(QApplication::font());
Chris@51 374 QString preferredFamily = "Segoe UI";
Chris@51 375 font.setFamily(preferredFamily);
Chris@51 376 if (QFontInfo(font).family() == preferredFamily) {
Chris@51 377 font.setPointSize(10);
Chris@51 378 QApplication::setFont(font);
Chris@51 379 }
Chris@51 380 #endif
Chris@51 381
Chris@42 382 QString target = getDefaultInstallDirectory();
Chris@42 383 if (target == "") {
Chris@42 384 return 1;
Chris@42 385 }
Chris@42 386
Chris@42 387 QStringList libraries = getPluginLibraryList();
Chris@42 388
Chris@43 389 auto rdfStore = loadLibrariesRdf();
Chris@43 390
Chris@43 391 auto info = getLibraryInfo(*rdfStore, libraries);
Chris@43 392
Chris@43 393 QStringList toInstall = getUserApprovedPluginLibraries(info);
Chris@52 394
Chris@52 395 if (!toInstall.empty()) {
Chris@52 396 if (!QDir(target).exists()) {
Chris@52 397 QDir().mkpath(target);
Chris@52 398 }
Chris@52 399 }
Chris@42 400
Chris@42 401 for (auto lib: toInstall) {
Chris@42 402 installLibrary(lib, target);
Chris@33 403 }
Chris@33 404
Chris@32 405 return 0;
Chris@32 406 }