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 }