annotate installer.cpp @ 52:eeab56dd59d6

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