Chris@33: Chris@33: #include Chris@33: #include Chris@33: #include Chris@33: #include Chris@33: Chris@42: #include Chris@42: #include Chris@42: #include Chris@42: #include Chris@46: #include Chris@42: #include Chris@46: #include Chris@51: #include Chris@51: #include Chris@42: Chris@41: #include Chris@41: Chris@43: #include Chris@43: #include Chris@43: Chris@33: #include Chris@43: #include Chris@43: Chris@51: #include "base/Debug.h" Chris@51: Chris@33: using namespace std; Chris@43: using namespace Dataquay; Chris@32: Chris@42: QString Chris@42: getDefaultInstallDirectory() Chris@32: { Chris@41: auto pathList = Vamp::PluginHostAdapter::getPluginPath(); Chris@41: if (pathList.empty()) { Chris@51: SVCERR << "Failed to look up Vamp plugin path" << endl; Chris@42: return QString(); Chris@41: } Chris@41: Chris@42: auto firstPath = *pathList.begin(); Chris@42: QString target = QString::fromUtf8(firstPath.c_str(), firstPath.size()); Chris@42: return target; Chris@42: } Chris@42: Chris@42: QStringList Chris@42: getPluginLibraryList() Chris@42: { Chris@33: QDir dir(":out/"); Chris@33: auto entries = dir.entryList({ "*.so", "*.dll", "*.dylib" }); Chris@33: Chris@33: for (auto e: entries) { Chris@51: SVCERR << e.toStdString() << endl; Chris@33: } Chris@33: Chris@42: return entries; Chris@42: } Chris@33: Chris@50: void Chris@50: loadLibraryRdf(BasicStore &store, QString filename) Chris@50: { Chris@50: QFile f(filename); Chris@50: if (!f.open(QFile::ReadOnly | QFile::Text)) { Chris@51: SVCERR << "Failed to open RDF resource file " Chris@51: << filename.toStdString() << endl; Chris@50: return; Chris@50: } Chris@50: Chris@50: QByteArray content = f.readAll(); Chris@50: f.close(); Chris@50: Chris@50: try { Chris@50: store.importString(QString::fromUtf8(content), Chris@50: Uri("file:" + filename), Chris@50: BasicStore::ImportIgnoreDuplicates); Chris@50: } catch (const RDFException &ex) { Chris@51: SVCERR << "Failed to import RDF resource file " Chris@51: << filename.toStdString() << ": " << ex.what() << endl; Chris@50: } Chris@50: } Chris@50: Chris@43: unique_ptr Chris@43: loadLibrariesRdf() Chris@43: { Chris@43: unique_ptr store(new BasicStore); Chris@43: Chris@50: vector dirs { ":rdf/plugins", ":out" }; Chris@43: Chris@50: for (auto d: dirs) { Chris@50: for (auto e: QDir(d).entryList({ "*.ttl", "*.n3" })) { Chris@50: loadLibraryRdf(*store, d + "/" + e); Chris@43: } Chris@43: } Chris@43: Chris@43: return store; Chris@43: } Chris@43: Chris@43: struct LibraryInfo { Chris@43: QString id; Chris@43: QString fileName; Chris@43: QString title; Chris@43: QString maker; Chris@43: QString description; Chris@47: QStringList pluginTitles; Chris@43: }; Chris@43: Chris@43: vector Chris@43: getLibraryInfo(const Store &store, QStringList libraries) Chris@43: { Chris@43: /* e.g. Chris@43: Chris@43: plugbase:library a vamp:PluginLibrary ; Chris@43: vamp:identifier "qm-vamp-plugins" ; Chris@43: dc:title "Queen Mary plugin set" Chris@43: */ Chris@43: Chris@43: Triples tt = store.match(Triple(Node(), Chris@43: Uri("a"), Chris@43: store.expand("vamp:PluginLibrary"))); Chris@43: Chris@43: std::map wanted; // basename -> full lib name Chris@43: for (auto lib: libraries) { Chris@43: wanted[QFileInfo(lib).baseName()] = lib; Chris@43: } Chris@43: Chris@43: vector results; Chris@43: Chris@43: for (auto t: tt) { Chris@43: Chris@43: Node libId = store.complete(Triple(t.subject(), Chris@43: store.expand("vamp:identifier"), Chris@43: Node())); Chris@43: if (libId.type != Node::Literal) { Chris@43: continue; Chris@43: } Chris@43: auto wi = wanted.find(libId.value); Chris@43: if (wi == wanted.end()) { Chris@43: continue; Chris@43: } Chris@43: Chris@50: Node title = store.complete(Triple(t.subject(), Chris@50: store.expand("dc:title"), Chris@50: Node())); Chris@50: if (title.type != Node::Literal) { Chris@50: continue; Chris@50: } Chris@50: Chris@43: LibraryInfo info; Chris@43: info.id = wi->first; Chris@43: info.fileName = wi->second; Chris@50: info.title = title.value; Chris@43: Chris@43: Node maker = store.complete(Triple(t.subject(), Chris@43: store.expand("foaf:maker"), Chris@43: Node())); Chris@43: if (maker.type == Node::Literal) { Chris@43: info.maker = maker.value; Chris@46: } else if (maker != Node()) { Chris@46: maker = store.complete(Triple(maker, Chris@46: store.expand("foaf:name"), Chris@46: Node())); Chris@46: if (maker.type == Node::Literal) { Chris@46: info.maker = maker.value; Chris@46: } Chris@43: } Chris@46: Chris@43: Node desc = store.complete(Triple(t.subject(), Chris@43: store.expand("dc:description"), Chris@43: Node())); Chris@43: if (desc.type == Node::Literal) { Chris@43: info.description = desc.value; Chris@43: } Chris@43: Chris@47: Triples pp = store.match(Triple(t.subject(), Chris@47: store.expand("vamp:available_plugin"), Chris@47: Node())); Chris@47: for (auto p: pp) { Chris@47: Node ptitle = store.complete(Triple(p.object(), Chris@47: store.expand("dc:title"), Chris@47: Node())); Chris@47: if (ptitle.type == Node::Literal) { Chris@47: info.pluginTitles.push_back(ptitle.value); Chris@47: } Chris@47: } Chris@47: Chris@43: results.push_back(info); Chris@50: wanted.erase(libId.value); Chris@43: } Chris@43: Chris@50: for (auto wp: wanted) { Chris@51: SVCERR << "Failed to find any RDF information about library " Chris@51: << wp.second << endl; Chris@50: } Chris@50: Chris@43: return results; Chris@43: } Chris@43: Chris@42: void Chris@42: installLibrary(QString library, QString target) Chris@42: { Chris@52: QString source = ":out"; Chris@52: QFile f(source + "/" + library); Chris@42: QString destination = target + "/" + library; Chris@52: Chris@51: SVCERR << "Copying " << library.toStdString() << " to " Chris@51: << destination.toStdString() << "..." << endl; Chris@42: if (!f.copy(destination)) { Chris@51: SVCERR << "Failed to copy " << library.toStdString() Chris@51: << " to target " << destination.toStdString() << endl; Chris@42: return; Chris@42: } Chris@42: if (!QFile::setPermissions Chris@42: (destination, Chris@42: QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | Chris@42: QFile::ReadGroup | QFile::ExeGroup | Chris@42: QFile::ReadOther | QFile::ExeOther)) { Chris@51: SVCERR << "Failed to set permissions on " Chris@51: << library.toStdString() << endl; Chris@42: return; Chris@42: } Chris@52: Chris@52: QString base = QFileInfo(library).baseName(); Chris@52: QDir dir(source); Chris@52: auto entries = dir.entryList({ base + "*" }); Chris@52: for (auto e: entries) { Chris@52: if (e == library) continue; Chris@52: QString destination = target + "/" + e; Chris@52: SVCERR << "Copying " << e.toStdString() << " to " Chris@52: << destination.toStdString() << "..." << endl; Chris@52: if (!QFile(source + "/" + e).copy(destination)) { Chris@52: SVCERR << "Failed to copy " << e.toStdString() Chris@52: << " to target " << destination.toStdString() Chris@52: << " (ignoring)" << endl; Chris@52: } Chris@52: } Chris@42: } Chris@42: Chris@42: QStringList Chris@43: getUserApprovedPluginLibraries(vector libraries) Chris@42: { Chris@42: QDialog dialog; Chris@46: Chris@46: auto mainLayout = new QGridLayout; Chris@47: mainLayout->setSpacing(0); Chris@46: dialog.setLayout(mainLayout); Chris@46: Chris@46: int mainRow = 0; Chris@46: Chris@46: //!!! at top: title and check/uncheck all button Chris@46: Chris@47: auto checkAll = new QCheckBox; Chris@47: mainLayout->addWidget(checkAll, mainRow, 0, Qt::AlignHCenter); Chris@47: ++mainRow; Chris@47: Chris@47: auto checkArrow = new QLabel("▼"); Chris@47: checkArrow->setTextFormat(Qt::RichText); Chris@47: mainLayout->addWidget(checkArrow, mainRow, 0, Qt::AlignHCenter); Chris@47: ++mainRow; Chris@47: Chris@46: auto scroll = new QScrollArea; Chris@47: scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); Chris@46: mainLayout->addWidget(scroll, mainRow, 0, 1, 2); Chris@46: mainLayout->setRowStretch(mainRow, 10); Chris@46: ++mainRow; Chris@46: Chris@46: auto selectionFrame = new QWidget; Chris@46: Chris@46: auto selectionLayout = new QGridLayout; Chris@46: selectionFrame->setLayout(selectionLayout); Chris@46: int selectionRow = 0; Chris@42: Chris@43: map checkBoxMap; Chris@43: Chris@49: map> Chris@49: orderedInfo Chris@49: ([](QString k1, QString k2) { Chris@49: return k1.localeAwareCompare(k2) < 0; Chris@49: }); Chris@43: for (auto info: libraries) { Chris@43: orderedInfo[info.title] = info; Chris@43: } Chris@42: Chris@43: for (auto ip: orderedInfo) { Chris@46: Chris@46: auto cb = new QCheckBox; Chris@47: selectionLayout->addWidget(cb, selectionRow, 0, Chris@47: Qt::AlignTop | Qt::AlignHCenter); Chris@46: Chris@43: LibraryInfo info = ip.second; Chris@47: /* Chris@47: int n = info.pluginTitles.size(); Chris@47: QString contents; Chris@47: Chris@47: if (n > 0) { Chris@47: int max = 4; Chris@47: QStringList titles; Chris@47: for (int i = 0; i < max && i < int(info.pluginTitles.size()); ++i) { Chris@47: titles.push_back(info.pluginTitles[i]); Chris@47: } Chris@47: QString titleText = titles.join(", "); Chris@47: if (max < int(info.pluginTitles.size())) { Chris@47: titleText = QObject::tr("%1 ...").arg(titleText); Chris@47: } Chris@47: contents = QObject::tr("Plugins: %1").arg(titleText); Chris@47: } Chris@47: */ Chris@51: QString text = QObject::tr("%1
%2
%3") Chris@47: .arg(info.title) Chris@47: .arg(info.maker) Chris@47: .arg(info.description); Chris@46: Chris@46: auto label = new QLabel(text); Chris@47: label->setWordWrap(true); Chris@47: label->setMinimumWidth(800); Chris@46: Chris@46: selectionLayout->addWidget(label, selectionRow, 1, Qt::AlignTop); Chris@46: Chris@46: ++selectionRow; Chris@46: Chris@43: checkBoxMap[info.fileName] = cb; Chris@42: } Chris@42: Chris@46: scroll->setWidget(selectionFrame); Chris@46: Chris@47: QObject::connect(checkAll, &QCheckBox::toggled, Chris@47: [=]() { Chris@47: bool toCheck = checkAll->isChecked(); Chris@47: for (auto p: checkBoxMap) { Chris@47: p.second->setChecked(toCheck); Chris@47: } Chris@47: }); Chris@47: Chris@42: auto bb = new QDialogButtonBox(QDialogButtonBox::Ok | Chris@42: QDialogButtonBox::Cancel); Chris@46: mainLayout->addWidget(bb, mainRow, 0, 1, 2); Chris@46: ++mainRow; Chris@47: Chris@47: int cw = 50; Chris@47: mainLayout->setColumnMinimumWidth(0, cw + 20); //!!! Chris@47: mainLayout->setColumnStretch(1, 10); Chris@47: selectionLayout->setColumnMinimumWidth(0, cw); //!!! Chris@47: Chris@42: QObject::connect(bb, SIGNAL(accepted()), &dialog, SLOT(accept())); Chris@42: QObject::connect(bb, SIGNAL(rejected()), &dialog, SLOT(reject())); Chris@42: Chris@42: if (dialog.exec() == QDialog::Accepted) { Chris@51: SVCERR << "accepted" << endl; Chris@42: } else { Chris@51: SVCERR << "rejected" << endl; Chris@42: } Chris@42: Chris@42: QStringList approved; Chris@42: for (const auto &p: checkBoxMap) { Chris@42: if (p.second->isChecked()) { Chris@42: approved.push_back(p.first); Chris@33: } Chris@42: } Chris@42: Chris@42: return approved; Chris@42: } Chris@42: Chris@42: int main(int argc, char **argv) Chris@42: { Chris@42: QApplication app(argc, argv); Chris@42: Chris@51: QApplication::setOrganizationName("sonic-visualiser"); Chris@51: QApplication::setOrganizationDomain("sonicvisualiser.org"); Chris@51: QApplication::setApplicationName(QApplication::tr("Vamp Plugin Pack Installer")); Chris@51: Chris@51: #ifdef Q_OS_WIN32 Chris@51: QFont font(QApplication::font()); Chris@51: QString preferredFamily = "Segoe UI"; Chris@51: font.setFamily(preferredFamily); Chris@51: if (QFontInfo(font).family() == preferredFamily) { Chris@51: font.setPointSize(10); Chris@51: QApplication::setFont(font); Chris@51: } Chris@51: #endif Chris@51: Chris@42: QString target = getDefaultInstallDirectory(); Chris@42: if (target == "") { Chris@42: return 1; Chris@42: } Chris@42: Chris@42: QStringList libraries = getPluginLibraryList(); Chris@42: Chris@43: auto rdfStore = loadLibrariesRdf(); Chris@43: Chris@43: auto info = getLibraryInfo(*rdfStore, libraries); Chris@43: Chris@43: QStringList toInstall = getUserApprovedPluginLibraries(info); Chris@52: Chris@52: if (!toInstall.empty()) { Chris@52: if (!QDir(target).exists()) { Chris@52: QDir().mkpath(target); Chris@52: } Chris@52: } Chris@42: Chris@42: for (auto lib: toInstall) { Chris@42: installLibrary(lib, target); Chris@33: } Chris@33: Chris@32: return 0; Chris@32: }