annotate installer.cpp @ 49:ba8d1129a062

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