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("▼");
|
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 }
|