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