Mercurial > hg > vamp-plugin-pack
diff 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 |
line wrap: on
line diff
--- a/installer.cpp Wed Feb 12 14:41:09 2020 +0000 +++ b/installer.cpp Thu Feb 13 10:56:15 2020 +0000 @@ -41,6 +41,10 @@ #include <QLabel> #include <QFont> #include <QFontInfo> +#include <QTemporaryFile> +#include <QMutex> +#include <QMutexLocker> +#include <QProcess> #include <vamp-hostsdk/PluginHostAdapter.h> @@ -51,6 +55,8 @@ #include <memory> #include <set> +#include <unistd.h> + #include "base/Debug.h" using namespace std; @@ -129,6 +135,7 @@ QString maker; QString description; QStringList pluginTitles; + map<QString, int> pluginVersions; // id -> version }; vector<LibraryInfo> @@ -145,7 +152,7 @@ Uri("a"), store.expand("vamp:PluginLibrary"))); - std::map<QString, QString> wanted; // basename -> full lib name + map<QString, QString> wanted; // basename -> full lib name for (auto lib: libraries) { wanted[QFileInfo(lib).baseName()] = lib; } @@ -208,6 +215,21 @@ if (ptitle.type == Node::Literal) { info.pluginTitles.push_back(ptitle.value); } + + Node pident = store.complete(Triple(p.object(), + store.expand("vamp:identifier"), + Node())); + Node pversion = store.complete(Triple(p.object(), + store.expand("owl:versionInfo"), + Node())); + if (pident.type == Node::Literal && + pversion.type == Node::Literal) { + bool ok = false; + int version = pversion.value.toInt(&ok); + if (ok) { + info.pluginVersions[pident.value] = version; + } + } } results.push_back(info); @@ -222,12 +244,204 @@ return results; } +struct TempFileDeleter { + ~TempFileDeleter() { + if (tempFile != "") { + QFile(tempFile).remove(); + } + } + QString tempFile; +}; + +map<QString, int> +getInstalledLibraryPluginVersions(QString libraryFilePath) +{ + static QMutex mutex; + static QString tempFileName; + static TempFileDeleter deleter; + static bool initHappened = false, initSucceeded = false; + + QMutexLocker locker (&mutex); + + if (!initHappened) { + initHappened = true; + + QTemporaryFile tempFile; + tempFile.setAutoRemove(false); + if (!tempFile.open()) { + SVCERR << "ERROR: Failed to open a temporary file" << endl; + return {}; + } + + // We can't make the QTemporaryFile static, as it will hold + // the file open and that prevents us from executing it. Hence + // the separate deleter. + + tempFileName = tempFile.fileName(); + deleter.tempFile = tempFileName; + +#ifdef Q_OS_WIN32 + QString helperPath = ":out/get-version.exe"; +#else + QString helperPath = ":out/get-version"; +#endif + QFile helper(helperPath); + if (!helper.open(QFile::ReadOnly)) { + SVCERR << "ERROR: Failed to read helper code" << endl; + return {}; + } + QByteArray content = helper.readAll(); + helper.close(); + + if (tempFile.write(content) != content.size()) { + SVCERR << "ERROR: Incomplete write to temporary file" << endl; + return {}; + } + tempFile.close(); + + if (!QFile::setPermissions + (tempFileName, + QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)) { + SVCERR << "ERROR: Failed to set execute permission on helper " + << tempFileName << endl; + return {}; + } + + initSucceeded = true; + } + + if (!initSucceeded) { + return {}; + } + + QProcess process; + process.start(tempFileName, { libraryFilePath }); + + if (!process.waitForStarted()) { + QProcess::ProcessError err = process.error(); + if (err == QProcess::FailedToStart) { + SVCERR << "Unable to start helper process " << tempFileName << endl; + } else if (err == QProcess::Crashed) { + SVCERR << "Helper process " << tempFileName + << " crashed on startup" << endl; + } else { + SVCERR << "Helper process " << tempFileName + << " failed on startup with error code " << err << endl; + } + return {}; + } + process.waitForFinished(); + + QByteArray stdOut = process.readAllStandardOutput(); + QByteArray stdErr = process.readAllStandardError(); + + QString errStr = QString::fromUtf8(stdErr); + if (!errStr.isEmpty()) { + SVCERR << "Note: Helper process stderr follows:" << endl; + SVCERR << errStr << endl; + SVCERR << "Note: Helper process stderr ends" << endl; + } + + QStringList lines = QString::fromUtf8(stdOut).split + (QRegExp("[\\r\\n]+"), QString::SkipEmptyParts); + map<QString, int> versions; + for (QString line: lines) { + QStringList parts = line.split(":"); + if (parts.size() != 2) { + SVCERR << "Unparseable output line: " << line << endl; + continue; + } + bool ok = false; + int version = parts[1].toInt(&ok); + if (!ok) { + SVCERR << "Unparseable version number in line: " << line << endl; + continue; + } + versions[parts[0]] = version; + } + + return versions; +} + +bool isLibraryNewer(map<QString, int> a, map<QString, int> b) +{ + // a and b are maps from plugin id to plugin version for libraries + // A and B. (There is no overarching library version number.) We + // deem library A to be newer than library B if: + // + // 1. A contains a plugin id that is also in B, whose version in + // A is newer than that in B, or + // + // 2. B is not newer than A according to rule 1, and neither A or + // B is empty, and A contains a plugin id that is not in B, and B + // does not contain any plugin id that is not in A + // + // (The not-empty part of rule 2 is just to avoid false positives + // when a library or its metadata could not be read at all.) + + auto containsANewerPlugin = [](const map<QString, int> &m1, + const map<QString, int> &m2) { + for (auto p: m1) { + if (m2.find(p.first) != m2.end() && + p.second > m2.at(p.first)) { + return true; + } + } + return false; + }; + + auto containsANovelPlugin = [](const map<QString, int> &m1, + const map<QString, int> &m2) { + for (auto p: m1) { + if (m2.find(p.first) == m2.end()) { + return true; + } + } + return false; + }; + + if (containsANewerPlugin(a, b)) { + return true; + } + + if (!containsANewerPlugin(b, a) && + !a.empty() && + !b.empty() && + containsANovelPlugin(a, b) && + !containsANovelPlugin(b, a)) { + return true; + } + + return false; +} + +QString +versionsString(const map<QString, int> &vv) +{ + QStringList pv; + for (auto v: vv) { + pv.push_back(QString("%1:%2").arg(v.first).arg(v.second)); + } + return "{ " + pv.join(", ") + " }"; +} + void -installLibrary(QString library, QString target) +installLibrary(QString library, LibraryInfo info, QString target) { QString source = ":out"; QFile f(source + "/" + library); QString destination = target + "/" + library; + + if (QFileInfo(destination).exists()) { + auto installed = getInstalledLibraryPluginVersions(destination); + SVCERR << "Note: comparing installed plugin versions " + << versionsString(installed) + << " to packaged versions " + << versionsString(info.pluginVersions) + << ": isLibraryNewer(installed, packaged) returns " + << isLibraryNewer(installed, info.pluginVersions) + << endl; + } SVCERR << "Copying " << library.toStdString() << " to " << destination.toStdString() << "..." << endl; @@ -262,7 +476,7 @@ } } -QStringList +map<QString, LibraryInfo> getUserApprovedPluginLibraries(vector<LibraryInfo> libraries) { QDialog dialog; @@ -295,9 +509,10 @@ selectionFrame->setLayout(selectionLayout); int selectionRow = 0; - map<QString, QCheckBox *> checkBoxMap; + map<QString, QCheckBox *> checkBoxMap; // filename -> checkbox + map<QString, LibraryInfo> libFileInfo; // filename -> info - map<QString, LibraryInfo, std::function<bool (QString, QString)>> + map<QString, LibraryInfo, function<bool (QString, QString)>> orderedInfo ([](QString k1, QString k2) { return k1.localeAwareCompare(k2) < 0; @@ -346,6 +561,7 @@ ++selectionRow; checkBoxMap[info.fileName] = cb; + libFileInfo[info.fileName] = info; } scroll->setWidget(selectionFrame); @@ -379,10 +595,10 @@ SVCERR << "rejected" << endl; } - QStringList approved; + map<QString, LibraryInfo> approved; for (const auto &p: checkBoxMap) { if (p.second->isChecked()) { - approved.push_back(p.first); + approved[p.first] = libFileInfo[p.first]; } } @@ -418,7 +634,7 @@ auto info = getLibraryInfo(*rdfStore, libraries); - QStringList toInstall = getUserApprovedPluginLibraries(info); + map<QString, LibraryInfo> toInstall = getUserApprovedPluginLibraries(info); if (!toInstall.empty()) { if (!QDir(target).exists()) { @@ -427,7 +643,7 @@ } for (auto lib: toInstall) { - installLibrary(lib, target); + installLibrary(lib.first, lib.second, target); } return 0;