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@46
|
247 //!!! at top: title and check/uncheck all button
|
Chris@46
|
248
|
Chris@47
|
249 auto checkAll = new QCheckBox;
|
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@42
|
280
|
Chris@43
|
281 for (auto ip: orderedInfo) {
|
Chris@46
|
282
|
Chris@46
|
283 auto cb = new QCheckBox;
|
Chris@47
|
284 selectionLayout->addWidget(cb, selectionRow, 0,
|
Chris@47
|
285 Qt::AlignTop | Qt::AlignHCenter);
|
Chris@46
|
286
|
Chris@43
|
287 LibraryInfo info = ip.second;
|
Chris@47
|
288 /*
|
Chris@47
|
289 int n = info.pluginTitles.size();
|
Chris@47
|
290 QString contents;
|
Chris@47
|
291
|
Chris@47
|
292 if (n > 0) {
|
Chris@47
|
293 int max = 4;
|
Chris@47
|
294 QStringList titles;
|
Chris@47
|
295 for (int i = 0; i < max && i < int(info.pluginTitles.size()); ++i) {
|
Chris@47
|
296 titles.push_back(info.pluginTitles[i]);
|
Chris@47
|
297 }
|
Chris@47
|
298 QString titleText = titles.join(", ");
|
Chris@47
|
299 if (max < int(info.pluginTitles.size())) {
|
Chris@47
|
300 titleText = QObject::tr("%1 ...").arg(titleText);
|
Chris@47
|
301 }
|
Chris@47
|
302 contents = QObject::tr("Plugins: %1").arg(titleText);
|
Chris@47
|
303 }
|
Chris@47
|
304 */
|
Chris@51
|
305 QString text = QObject::tr("<b>%1</b><br><i>%2</i><br>%3")
|
Chris@47
|
306 .arg(info.title)
|
Chris@47
|
307 .arg(info.maker)
|
Chris@47
|
308 .arg(info.description);
|
Chris@46
|
309
|
Chris@46
|
310 auto label = new QLabel(text);
|
Chris@47
|
311 label->setWordWrap(true);
|
Chris@47
|
312 label->setMinimumWidth(800);
|
Chris@46
|
313
|
Chris@46
|
314 selectionLayout->addWidget(label, selectionRow, 1, Qt::AlignTop);
|
Chris@46
|
315
|
Chris@46
|
316 ++selectionRow;
|
Chris@46
|
317
|
Chris@43
|
318 checkBoxMap[info.fileName] = cb;
|
Chris@42
|
319 }
|
Chris@42
|
320
|
Chris@46
|
321 scroll->setWidget(selectionFrame);
|
Chris@46
|
322
|
Chris@47
|
323 QObject::connect(checkAll, &QCheckBox::toggled,
|
Chris@47
|
324 [=]() {
|
Chris@47
|
325 bool toCheck = checkAll->isChecked();
|
Chris@47
|
326 for (auto p: checkBoxMap) {
|
Chris@47
|
327 p.second->setChecked(toCheck);
|
Chris@47
|
328 }
|
Chris@47
|
329 });
|
Chris@47
|
330
|
Chris@42
|
331 auto bb = new QDialogButtonBox(QDialogButtonBox::Ok |
|
Chris@42
|
332 QDialogButtonBox::Cancel);
|
Chris@46
|
333 mainLayout->addWidget(bb, mainRow, 0, 1, 2);
|
Chris@46
|
334 ++mainRow;
|
Chris@47
|
335
|
Chris@47
|
336 int cw = 50;
|
Chris@47
|
337 mainLayout->setColumnMinimumWidth(0, cw + 20); //!!!
|
Chris@47
|
338 mainLayout->setColumnStretch(1, 10);
|
Chris@47
|
339 selectionLayout->setColumnMinimumWidth(0, cw); //!!!
|
Chris@47
|
340
|
Chris@42
|
341 QObject::connect(bb, SIGNAL(accepted()), &dialog, SLOT(accept()));
|
Chris@42
|
342 QObject::connect(bb, SIGNAL(rejected()), &dialog, SLOT(reject()));
|
Chris@42
|
343
|
Chris@42
|
344 if (dialog.exec() == QDialog::Accepted) {
|
Chris@51
|
345 SVCERR << "accepted" << endl;
|
Chris@42
|
346 } else {
|
Chris@51
|
347 SVCERR << "rejected" << endl;
|
Chris@42
|
348 }
|
Chris@42
|
349
|
Chris@42
|
350 QStringList approved;
|
Chris@42
|
351 for (const auto &p: checkBoxMap) {
|
Chris@42
|
352 if (p.second->isChecked()) {
|
Chris@42
|
353 approved.push_back(p.first);
|
Chris@33
|
354 }
|
Chris@42
|
355 }
|
Chris@42
|
356
|
Chris@42
|
357 return approved;
|
Chris@42
|
358 }
|
Chris@42
|
359
|
Chris@42
|
360 int main(int argc, char **argv)
|
Chris@42
|
361 {
|
Chris@42
|
362 QApplication app(argc, argv);
|
Chris@42
|
363
|
Chris@51
|
364 QApplication::setOrganizationName("sonic-visualiser");
|
Chris@51
|
365 QApplication::setOrganizationDomain("sonicvisualiser.org");
|
Chris@51
|
366 QApplication::setApplicationName(QApplication::tr("Vamp Plugin Pack Installer"));
|
Chris@51
|
367
|
Chris@51
|
368 #ifdef Q_OS_WIN32
|
Chris@51
|
369 QFont font(QApplication::font());
|
Chris@51
|
370 QString preferredFamily = "Segoe UI";
|
Chris@51
|
371 font.setFamily(preferredFamily);
|
Chris@51
|
372 if (QFontInfo(font).family() == preferredFamily) {
|
Chris@51
|
373 font.setPointSize(10);
|
Chris@51
|
374 QApplication::setFont(font);
|
Chris@51
|
375 }
|
Chris@51
|
376 #endif
|
Chris@51
|
377
|
Chris@42
|
378 QString target = getDefaultInstallDirectory();
|
Chris@42
|
379 if (target == "") {
|
Chris@42
|
380 return 1;
|
Chris@42
|
381 }
|
Chris@42
|
382
|
Chris@42
|
383 QStringList libraries = getPluginLibraryList();
|
Chris@42
|
384
|
Chris@43
|
385 auto rdfStore = loadLibrariesRdf();
|
Chris@43
|
386
|
Chris@43
|
387 auto info = getLibraryInfo(*rdfStore, libraries);
|
Chris@43
|
388
|
Chris@43
|
389 QStringList toInstall = getUserApprovedPluginLibraries(info);
|
Chris@52
|
390
|
Chris@52
|
391 if (!toInstall.empty()) {
|
Chris@52
|
392 if (!QDir(target).exists()) {
|
Chris@52
|
393 QDir().mkpath(target);
|
Chris@52
|
394 }
|
Chris@52
|
395 }
|
Chris@42
|
396
|
Chris@42
|
397 for (auto lib: toInstall) {
|
Chris@42
|
398 installLibrary(lib, target);
|
Chris@33
|
399 }
|
Chris@33
|
400
|
Chris@32
|
401 return 0;
|
Chris@32
|
402 }
|