Mercurial > hg > vamp-plugin-pack
comparison installer.cpp @ 67:38cd115c91d4
Integrate version checker into installer program & build, + win32 signing bits
author | Chris Cannam |
---|---|
date | Thu, 13 Feb 2020 10:56:15 +0000 |
parents | 85768d48e6ce |
children | d70c50c4a07c |
comparison
equal
deleted
inserted
replaced
66:85768d48e6ce | 67:38cd115c91d4 |
---|---|
39 #include <QScrollArea> | 39 #include <QScrollArea> |
40 #include <QDialogButtonBox> | 40 #include <QDialogButtonBox> |
41 #include <QLabel> | 41 #include <QLabel> |
42 #include <QFont> | 42 #include <QFont> |
43 #include <QFontInfo> | 43 #include <QFontInfo> |
44 #include <QTemporaryFile> | |
45 #include <QMutex> | |
46 #include <QMutexLocker> | |
47 #include <QProcess> | |
44 | 48 |
45 #include <vamp-hostsdk/PluginHostAdapter.h> | 49 #include <vamp-hostsdk/PluginHostAdapter.h> |
46 | 50 |
47 #include <dataquay/BasicStore.h> | 51 #include <dataquay/BasicStore.h> |
48 #include <dataquay/RDFException.h> | 52 #include <dataquay/RDFException.h> |
49 | 53 |
50 #include <iostream> | 54 #include <iostream> |
51 #include <memory> | 55 #include <memory> |
52 #include <set> | 56 #include <set> |
57 | |
58 #include <unistd.h> | |
53 | 59 |
54 #include "base/Debug.h" | 60 #include "base/Debug.h" |
55 | 61 |
56 using namespace std; | 62 using namespace std; |
57 using namespace Dataquay; | 63 using namespace Dataquay; |
127 QString fileName; | 133 QString fileName; |
128 QString title; | 134 QString title; |
129 QString maker; | 135 QString maker; |
130 QString description; | 136 QString description; |
131 QStringList pluginTitles; | 137 QStringList pluginTitles; |
138 map<QString, int> pluginVersions; // id -> version | |
132 }; | 139 }; |
133 | 140 |
134 vector<LibraryInfo> | 141 vector<LibraryInfo> |
135 getLibraryInfo(const Store &store, QStringList libraries) | 142 getLibraryInfo(const Store &store, QStringList libraries) |
136 { | 143 { |
143 | 150 |
144 Triples tt = store.match(Triple(Node(), | 151 Triples tt = store.match(Triple(Node(), |
145 Uri("a"), | 152 Uri("a"), |
146 store.expand("vamp:PluginLibrary"))); | 153 store.expand("vamp:PluginLibrary"))); |
147 | 154 |
148 std::map<QString, QString> wanted; // basename -> full lib name | 155 map<QString, QString> wanted; // basename -> full lib name |
149 for (auto lib: libraries) { | 156 for (auto lib: libraries) { |
150 wanted[QFileInfo(lib).baseName()] = lib; | 157 wanted[QFileInfo(lib).baseName()] = lib; |
151 } | 158 } |
152 | 159 |
153 vector<LibraryInfo> results; | 160 vector<LibraryInfo> results; |
206 store.expand("dc:title"), | 213 store.expand("dc:title"), |
207 Node())); | 214 Node())); |
208 if (ptitle.type == Node::Literal) { | 215 if (ptitle.type == Node::Literal) { |
209 info.pluginTitles.push_back(ptitle.value); | 216 info.pluginTitles.push_back(ptitle.value); |
210 } | 217 } |
218 | |
219 Node pident = store.complete(Triple(p.object(), | |
220 store.expand("vamp:identifier"), | |
221 Node())); | |
222 Node pversion = store.complete(Triple(p.object(), | |
223 store.expand("owl:versionInfo"), | |
224 Node())); | |
225 if (pident.type == Node::Literal && | |
226 pversion.type == Node::Literal) { | |
227 bool ok = false; | |
228 int version = pversion.value.toInt(&ok); | |
229 if (ok) { | |
230 info.pluginVersions[pident.value] = version; | |
231 } | |
232 } | |
211 } | 233 } |
212 | 234 |
213 results.push_back(info); | 235 results.push_back(info); |
214 wanted.erase(libId.value); | 236 wanted.erase(libId.value); |
215 } | 237 } |
220 } | 242 } |
221 | 243 |
222 return results; | 244 return results; |
223 } | 245 } |
224 | 246 |
247 struct TempFileDeleter { | |
248 ~TempFileDeleter() { | |
249 if (tempFile != "") { | |
250 QFile(tempFile).remove(); | |
251 } | |
252 } | |
253 QString tempFile; | |
254 }; | |
255 | |
256 map<QString, int> | |
257 getInstalledLibraryPluginVersions(QString libraryFilePath) | |
258 { | |
259 static QMutex mutex; | |
260 static QString tempFileName; | |
261 static TempFileDeleter deleter; | |
262 static bool initHappened = false, initSucceeded = false; | |
263 | |
264 QMutexLocker locker (&mutex); | |
265 | |
266 if (!initHappened) { | |
267 initHappened = true; | |
268 | |
269 QTemporaryFile tempFile; | |
270 tempFile.setAutoRemove(false); | |
271 if (!tempFile.open()) { | |
272 SVCERR << "ERROR: Failed to open a temporary file" << endl; | |
273 return {}; | |
274 } | |
275 | |
276 // We can't make the QTemporaryFile static, as it will hold | |
277 // the file open and that prevents us from executing it. Hence | |
278 // the separate deleter. | |
279 | |
280 tempFileName = tempFile.fileName(); | |
281 deleter.tempFile = tempFileName; | |
282 | |
283 #ifdef Q_OS_WIN32 | |
284 QString helperPath = ":out/get-version.exe"; | |
285 #else | |
286 QString helperPath = ":out/get-version"; | |
287 #endif | |
288 QFile helper(helperPath); | |
289 if (!helper.open(QFile::ReadOnly)) { | |
290 SVCERR << "ERROR: Failed to read helper code" << endl; | |
291 return {}; | |
292 } | |
293 QByteArray content = helper.readAll(); | |
294 helper.close(); | |
295 | |
296 if (tempFile.write(content) != content.size()) { | |
297 SVCERR << "ERROR: Incomplete write to temporary file" << endl; | |
298 return {}; | |
299 } | |
300 tempFile.close(); | |
301 | |
302 if (!QFile::setPermissions | |
303 (tempFileName, | |
304 QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)) { | |
305 SVCERR << "ERROR: Failed to set execute permission on helper " | |
306 << tempFileName << endl; | |
307 return {}; | |
308 } | |
309 | |
310 initSucceeded = true; | |
311 } | |
312 | |
313 if (!initSucceeded) { | |
314 return {}; | |
315 } | |
316 | |
317 QProcess process; | |
318 process.start(tempFileName, { libraryFilePath }); | |
319 | |
320 if (!process.waitForStarted()) { | |
321 QProcess::ProcessError err = process.error(); | |
322 if (err == QProcess::FailedToStart) { | |
323 SVCERR << "Unable to start helper process " << tempFileName << endl; | |
324 } else if (err == QProcess::Crashed) { | |
325 SVCERR << "Helper process " << tempFileName | |
326 << " crashed on startup" << endl; | |
327 } else { | |
328 SVCERR << "Helper process " << tempFileName | |
329 << " failed on startup with error code " << err << endl; | |
330 } | |
331 return {}; | |
332 } | |
333 process.waitForFinished(); | |
334 | |
335 QByteArray stdOut = process.readAllStandardOutput(); | |
336 QByteArray stdErr = process.readAllStandardError(); | |
337 | |
338 QString errStr = QString::fromUtf8(stdErr); | |
339 if (!errStr.isEmpty()) { | |
340 SVCERR << "Note: Helper process stderr follows:" << endl; | |
341 SVCERR << errStr << endl; | |
342 SVCERR << "Note: Helper process stderr ends" << endl; | |
343 } | |
344 | |
345 QStringList lines = QString::fromUtf8(stdOut).split | |
346 (QRegExp("[\\r\\n]+"), QString::SkipEmptyParts); | |
347 map<QString, int> versions; | |
348 for (QString line: lines) { | |
349 QStringList parts = line.split(":"); | |
350 if (parts.size() != 2) { | |
351 SVCERR << "Unparseable output line: " << line << endl; | |
352 continue; | |
353 } | |
354 bool ok = false; | |
355 int version = parts[1].toInt(&ok); | |
356 if (!ok) { | |
357 SVCERR << "Unparseable version number in line: " << line << endl; | |
358 continue; | |
359 } | |
360 versions[parts[0]] = version; | |
361 } | |
362 | |
363 return versions; | |
364 } | |
365 | |
366 bool isLibraryNewer(map<QString, int> a, map<QString, int> b) | |
367 { | |
368 // a and b are maps from plugin id to plugin version for libraries | |
369 // A and B. (There is no overarching library version number.) We | |
370 // deem library A to be newer than library B if: | |
371 // | |
372 // 1. A contains a plugin id that is also in B, whose version in | |
373 // A is newer than that in B, or | |
374 // | |
375 // 2. B is not newer than A according to rule 1, and neither A or | |
376 // B is empty, and A contains a plugin id that is not in B, and B | |
377 // does not contain any plugin id that is not in A | |
378 // | |
379 // (The not-empty part of rule 2 is just to avoid false positives | |
380 // when a library or its metadata could not be read at all.) | |
381 | |
382 auto containsANewerPlugin = [](const map<QString, int> &m1, | |
383 const map<QString, int> &m2) { | |
384 for (auto p: m1) { | |
385 if (m2.find(p.first) != m2.end() && | |
386 p.second > m2.at(p.first)) { | |
387 return true; | |
388 } | |
389 } | |
390 return false; | |
391 }; | |
392 | |
393 auto containsANovelPlugin = [](const map<QString, int> &m1, | |
394 const map<QString, int> &m2) { | |
395 for (auto p: m1) { | |
396 if (m2.find(p.first) == m2.end()) { | |
397 return true; | |
398 } | |
399 } | |
400 return false; | |
401 }; | |
402 | |
403 if (containsANewerPlugin(a, b)) { | |
404 return true; | |
405 } | |
406 | |
407 if (!containsANewerPlugin(b, a) && | |
408 !a.empty() && | |
409 !b.empty() && | |
410 containsANovelPlugin(a, b) && | |
411 !containsANovelPlugin(b, a)) { | |
412 return true; | |
413 } | |
414 | |
415 return false; | |
416 } | |
417 | |
418 QString | |
419 versionsString(const map<QString, int> &vv) | |
420 { | |
421 QStringList pv; | |
422 for (auto v: vv) { | |
423 pv.push_back(QString("%1:%2").arg(v.first).arg(v.second)); | |
424 } | |
425 return "{ " + pv.join(", ") + " }"; | |
426 } | |
427 | |
225 void | 428 void |
226 installLibrary(QString library, QString target) | 429 installLibrary(QString library, LibraryInfo info, QString target) |
227 { | 430 { |
228 QString source = ":out"; | 431 QString source = ":out"; |
229 QFile f(source + "/" + library); | 432 QFile f(source + "/" + library); |
230 QString destination = target + "/" + library; | 433 QString destination = target + "/" + library; |
434 | |
435 if (QFileInfo(destination).exists()) { | |
436 auto installed = getInstalledLibraryPluginVersions(destination); | |
437 SVCERR << "Note: comparing installed plugin versions " | |
438 << versionsString(installed) | |
439 << " to packaged versions " | |
440 << versionsString(info.pluginVersions) | |
441 << ": isLibraryNewer(installed, packaged) returns " | |
442 << isLibraryNewer(installed, info.pluginVersions) | |
443 << endl; | |
444 } | |
231 | 445 |
232 SVCERR << "Copying " << library.toStdString() << " to " | 446 SVCERR << "Copying " << library.toStdString() << " to " |
233 << destination.toStdString() << "..." << endl; | 447 << destination.toStdString() << "..." << endl; |
234 if (!f.copy(destination)) { | 448 if (!f.copy(destination)) { |
235 SVCERR << "Failed to copy " << library.toStdString() | 449 SVCERR << "Failed to copy " << library.toStdString() |
260 << " (ignoring)" << endl; | 474 << " (ignoring)" << endl; |
261 } | 475 } |
262 } | 476 } |
263 } | 477 } |
264 | 478 |
265 QStringList | 479 map<QString, LibraryInfo> |
266 getUserApprovedPluginLibraries(vector<LibraryInfo> libraries) | 480 getUserApprovedPluginLibraries(vector<LibraryInfo> libraries) |
267 { | 481 { |
268 QDialog dialog; | 482 QDialog dialog; |
269 | 483 |
270 auto mainLayout = new QGridLayout; | 484 auto mainLayout = new QGridLayout; |
293 | 507 |
294 auto selectionLayout = new QGridLayout; | 508 auto selectionLayout = new QGridLayout; |
295 selectionFrame->setLayout(selectionLayout); | 509 selectionFrame->setLayout(selectionLayout); |
296 int selectionRow = 0; | 510 int selectionRow = 0; |
297 | 511 |
298 map<QString, QCheckBox *> checkBoxMap; | 512 map<QString, QCheckBox *> checkBoxMap; // filename -> checkbox |
299 | 513 map<QString, LibraryInfo> libFileInfo; // filename -> info |
300 map<QString, LibraryInfo, std::function<bool (QString, QString)>> | 514 |
515 map<QString, LibraryInfo, function<bool (QString, QString)>> | |
301 orderedInfo | 516 orderedInfo |
302 ([](QString k1, QString k2) { | 517 ([](QString k1, QString k2) { |
303 return k1.localeAwareCompare(k2) < 0; | 518 return k1.localeAwareCompare(k2) < 0; |
304 }); | 519 }); |
305 for (auto info: libraries) { | 520 for (auto info: libraries) { |
344 selectionLayout->addWidget(label, selectionRow, 1, Qt::AlignTop); | 559 selectionLayout->addWidget(label, selectionRow, 1, Qt::AlignTop); |
345 | 560 |
346 ++selectionRow; | 561 ++selectionRow; |
347 | 562 |
348 checkBoxMap[info.fileName] = cb; | 563 checkBoxMap[info.fileName] = cb; |
564 libFileInfo[info.fileName] = info; | |
349 } | 565 } |
350 | 566 |
351 scroll->setWidget(selectionFrame); | 567 scroll->setWidget(selectionFrame); |
352 | 568 |
353 QObject::connect(checkAll, &QCheckBox::toggled, | 569 QObject::connect(checkAll, &QCheckBox::toggled, |
377 SVCERR << "accepted" << endl; | 593 SVCERR << "accepted" << endl; |
378 } else { | 594 } else { |
379 SVCERR << "rejected" << endl; | 595 SVCERR << "rejected" << endl; |
380 } | 596 } |
381 | 597 |
382 QStringList approved; | 598 map<QString, LibraryInfo> approved; |
383 for (const auto &p: checkBoxMap) { | 599 for (const auto &p: checkBoxMap) { |
384 if (p.second->isChecked()) { | 600 if (p.second->isChecked()) { |
385 approved.push_back(p.first); | 601 approved[p.first] = libFileInfo[p.first]; |
386 } | 602 } |
387 } | 603 } |
388 | 604 |
389 return approved; | 605 return approved; |
390 } | 606 } |
416 | 632 |
417 auto rdfStore = loadLibrariesRdf(); | 633 auto rdfStore = loadLibrariesRdf(); |
418 | 634 |
419 auto info = getLibraryInfo(*rdfStore, libraries); | 635 auto info = getLibraryInfo(*rdfStore, libraries); |
420 | 636 |
421 QStringList toInstall = getUserApprovedPluginLibraries(info); | 637 map<QString, LibraryInfo> toInstall = getUserApprovedPluginLibraries(info); |
422 | 638 |
423 if (!toInstall.empty()) { | 639 if (!toInstall.empty()) { |
424 if (!QDir(target).exists()) { | 640 if (!QDir(target).exists()) { |
425 QDir().mkpath(target); | 641 QDir().mkpath(target); |
426 } | 642 } |
427 } | 643 } |
428 | 644 |
429 for (auto lib: toInstall) { | 645 for (auto lib: toInstall) { |
430 installLibrary(lib, target); | 646 installLibrary(lib.first, lib.second, target); |
431 } | 647 } |
432 | 648 |
433 return 0; | 649 return 0; |
434 } | 650 } |