annotate installer.cpp @ 53:c1e5e3f02e48

Update some RDF; make checkboxes be checked by default
author Chris Cannam
date Mon, 03 Feb 2020 14:43:46 +0000
parents eeab56dd59d6
children df3b00f82e8d
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@47 247 auto checkAll = new QCheckBox;
Chris@53 248 checkAll->setChecked(true);
Chris@47 249 mainLayout->addWidget(checkAll, mainRow, 0, Qt::AlignHCenter);
Chris@47 250 ++mainRow;
Chris@47 251
Chris@47 252 auto checkArrow = new QLabel("&#9660;");
Chris@47 253 checkArrow->setTextFormat(Qt::RichText);
Chris@47 254 mainLayout->addWidget(checkArrow, mainRow, 0, Qt::AlignHCenter);
Chris@47 255 ++mainRow;
Chris@47 256
Chris@46 257 auto scroll = new QScrollArea;
Chris@47 258 scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
Chris@46 259 mainLayout->addWidget(scroll, mainRow, 0, 1, 2);
Chris@46 260 mainLayout->setRowStretch(mainRow, 10);
Chris@46 261 ++mainRow;
Chris@46 262
Chris@46 263 auto selectionFrame = new QWidget;
Chris@46 264
Chris@46 265 auto selectionLayout = new QGridLayout;
Chris@46 266 selectionFrame->setLayout(selectionLayout);
Chris@46 267 int selectionRow = 0;
Chris@42 268
Chris@43 269 map<QString, QCheckBox *> checkBoxMap;
Chris@43 270
Chris@49 271 map<QString, LibraryInfo, std::function<bool (QString, QString)>>
Chris@49 272 orderedInfo
Chris@49 273 ([](QString k1, QString k2) {
Chris@49 274 return k1.localeAwareCompare(k2) < 0;
Chris@49 275 });
Chris@43 276 for (auto info: libraries) {
Chris@43 277 orderedInfo[info.title] = info;
Chris@43 278 }
Chris@53 279
Chris@43 280 for (auto ip: orderedInfo) {
Chris@46 281
Chris@46 282 auto cb = new QCheckBox;
Chris@53 283 cb->setChecked(true);
Chris@53 284
Chris@47 285 selectionLayout->addWidget(cb, selectionRow, 0,
Chris@47 286 Qt::AlignTop | Qt::AlignHCenter);
Chris@46 287
Chris@43 288 LibraryInfo info = ip.second;
Chris@47 289 /*
Chris@47 290 int n = info.pluginTitles.size();
Chris@47 291 QString contents;
Chris@47 292
Chris@47 293 if (n > 0) {
Chris@47 294 int max = 4;
Chris@47 295 QStringList titles;
Chris@47 296 for (int i = 0; i < max && i < int(info.pluginTitles.size()); ++i) {
Chris@47 297 titles.push_back(info.pluginTitles[i]);
Chris@47 298 }
Chris@47 299 QString titleText = titles.join(", ");
Chris@47 300 if (max < int(info.pluginTitles.size())) {
Chris@47 301 titleText = QObject::tr("%1 ...").arg(titleText);
Chris@47 302 }
Chris@47 303 contents = QObject::tr("Plugins: %1").arg(titleText);
Chris@47 304 }
Chris@47 305 */
Chris@51 306 QString text = QObject::tr("<b>%1</b><br><i>%2</i><br>%3")
Chris@47 307 .arg(info.title)
Chris@47 308 .arg(info.maker)
Chris@47 309 .arg(info.description);
Chris@46 310
Chris@46 311 auto label = new QLabel(text);
Chris@47 312 label->setWordWrap(true);
Chris@47 313 label->setMinimumWidth(800);
Chris@46 314
Chris@46 315 selectionLayout->addWidget(label, selectionRow, 1, Qt::AlignTop);
Chris@46 316
Chris@46 317 ++selectionRow;
Chris@46 318
Chris@43 319 checkBoxMap[info.fileName] = cb;
Chris@42 320 }
Chris@42 321
Chris@46 322 scroll->setWidget(selectionFrame);
Chris@46 323
Chris@47 324 QObject::connect(checkAll, &QCheckBox::toggled,
Chris@47 325 [=]() {
Chris@47 326 bool toCheck = checkAll->isChecked();
Chris@47 327 for (auto p: checkBoxMap) {
Chris@47 328 p.second->setChecked(toCheck);
Chris@47 329 }
Chris@47 330 });
Chris@47 331
Chris@42 332 auto bb = new QDialogButtonBox(QDialogButtonBox::Ok |
Chris@42 333 QDialogButtonBox::Cancel);
Chris@46 334 mainLayout->addWidget(bb, mainRow, 0, 1, 2);
Chris@46 335 ++mainRow;
Chris@47 336
Chris@47 337 int cw = 50;
Chris@47 338 mainLayout->setColumnMinimumWidth(0, cw + 20); //!!!
Chris@47 339 mainLayout->setColumnStretch(1, 10);
Chris@47 340 selectionLayout->setColumnMinimumWidth(0, cw); //!!!
Chris@53 341 selectionLayout->setColumnMinimumWidth(1, 820); //!!!
Chris@53 342 selectionLayout->setColumnStretch(1, 10);
Chris@47 343
Chris@42 344 QObject::connect(bb, SIGNAL(accepted()), &dialog, SLOT(accept()));
Chris@42 345 QObject::connect(bb, SIGNAL(rejected()), &dialog, SLOT(reject()));
Chris@53 346
Chris@42 347 if (dialog.exec() == QDialog::Accepted) {
Chris@51 348 SVCERR << "accepted" << endl;
Chris@42 349 } else {
Chris@51 350 SVCERR << "rejected" << endl;
Chris@42 351 }
Chris@42 352
Chris@42 353 QStringList approved;
Chris@42 354 for (const auto &p: checkBoxMap) {
Chris@42 355 if (p.second->isChecked()) {
Chris@42 356 approved.push_back(p.first);
Chris@33 357 }
Chris@42 358 }
Chris@42 359
Chris@42 360 return approved;
Chris@42 361 }
Chris@42 362
Chris@42 363 int main(int argc, char **argv)
Chris@42 364 {
Chris@42 365 QApplication app(argc, argv);
Chris@42 366
Chris@51 367 QApplication::setOrganizationName("sonic-visualiser");
Chris@51 368 QApplication::setOrganizationDomain("sonicvisualiser.org");
Chris@51 369 QApplication::setApplicationName(QApplication::tr("Vamp Plugin Pack Installer"));
Chris@51 370
Chris@51 371 #ifdef Q_OS_WIN32
Chris@51 372 QFont font(QApplication::font());
Chris@51 373 QString preferredFamily = "Segoe UI";
Chris@51 374 font.setFamily(preferredFamily);
Chris@51 375 if (QFontInfo(font).family() == preferredFamily) {
Chris@51 376 font.setPointSize(10);
Chris@51 377 QApplication::setFont(font);
Chris@51 378 }
Chris@51 379 #endif
Chris@51 380
Chris@42 381 QString target = getDefaultInstallDirectory();
Chris@42 382 if (target == "") {
Chris@42 383 return 1;
Chris@42 384 }
Chris@42 385
Chris@42 386 QStringList libraries = getPluginLibraryList();
Chris@42 387
Chris@43 388 auto rdfStore = loadLibrariesRdf();
Chris@43 389
Chris@43 390 auto info = getLibraryInfo(*rdfStore, libraries);
Chris@43 391
Chris@43 392 QStringList toInstall = getUserApprovedPluginLibraries(info);
Chris@52 393
Chris@52 394 if (!toInstall.empty()) {
Chris@52 395 if (!QDir(target).exists()) {
Chris@52 396 QDir().mkpath(target);
Chris@52 397 }
Chris@52 398 }
Chris@42 399
Chris@42 400 for (auto lib: toInstall) {
Chris@42 401 installLibrary(lib, target);
Chris@33 402 }
Chris@33 403
Chris@32 404 return 0;
Chris@32 405 }