From 85c098f11c0e09424c49bef973ef25a97944d199 Mon Sep 17 00:00:00 2001 From: Neil Horne Date: Wed, 7 Dec 2022 10:37:14 +1100 Subject: [PATCH] cpn(fix): Release list order use version when available otherwise use date (#2823) --- companion/src/helpers.cpp | 185 +++++++++++++++++----- companion/src/helpers.h | 37 +++-- companion/src/updates/repomodels.cpp | 15 +- companion/src/updates/repomodels.h | 1 + companion/src/updates/updateinterface.cpp | 10 +- companion/src/updates/updateinterface.h | 2 +- 6 files changed, 191 insertions(+), 59 deletions(-) diff --git a/companion/src/helpers.cpp b/companion/src/helpers.cpp index 5c89fe82a..5289b98af 100644 --- a/companion/src/helpers.cpp +++ b/companion/src/helpers.cpp @@ -38,6 +38,7 @@ #include #include #include +#include using namespace Helpers; @@ -306,22 +307,22 @@ void Helpers::exportAppSettings(QWidget * dlgParent) } unsigned int Helpers::getBitmappedValue(const unsigned int & field, const unsigned int index, const unsigned int numbits, const unsigned int offset) - { - int mask = -1; - mask = mask << numbits; - mask = ~mask; - return (field >> (numbits * index + offset)) & (unsigned int)mask; - } +{ + int mask = -1; + mask = mask << numbits; + mask = ~mask; + return (field >> (numbits * index + offset)) & (unsigned int)mask; +} - void Helpers::setBitmappedValue(unsigned int & field, unsigned int value, unsigned int index, unsigned int numbits, unsigned int offset) - { - int mask = -1; - mask = mask << numbits; - mask = ~mask; +void Helpers::setBitmappedValue(unsigned int & field, unsigned int value, unsigned int index, unsigned int numbits, unsigned int offset) +{ + int mask = -1; + mask = mask << numbits; + mask = ~mask; - unsigned int fieldmask = ((unsigned int)mask << (numbits * index + offset)); - field = (field & ~fieldmask) | (value << (numbits * index + offset)); - } + unsigned int fieldmask = ((unsigned int)mask << (numbits * index + offset)); + field = (field & ~fieldmask) | (value << (numbits * index + offset)); +} void startSimulation(QWidget * parent, RadioData & radioData, int modelIdx) { @@ -727,42 +728,131 @@ QString Helpers::removeAccents(const QString & str) return result; } -SemanticVersion::SemanticVersion(QString vers) +/* + SemanticVersion + + Based on Semantic Versioning 2.0.0 refer https://semver.org/ +*/ + +SemanticVersion::SemanticVersion(const QString vers) +{ + fromString(vers); +} + +bool SemanticVersion::fromString(QString vers) { if (!isValid(vers)) - return; + return false; + + vers = vers.trimmed(); + + if (vers.toLower().startsWith("v")) + vers = vers.mid(1); QStringList strl = vers.split("."); version.major = strl.at(0).toInt(); version.minor = strl.at(1).toInt(); + if (strl.count() > 2) { if (!strl.at(2).contains("-")) { version.patch = strl.at(2).toInt(); } else { QStringList ptch = strl.at(2).toLower().split("-"); version.patch = ptch.at(0).toInt(); - if (ptch.at(1).left(2) == "rc") { - version.preReleaseType = PR_RC; - version.preReleaseNumber = ptch.at(1).mid(2).toInt(); - } else if (ptch.at(1).left(5) == "alpha") { - version.preReleaseType = PR_ALPHA; - version.preReleaseNumber = ptch.at(1).mid(5).toInt(); - } else if (ptch.at(1).left(4) == "beta") { - version.preReleaseType = PR_BETA; - version.preReleaseNumber = ptch.at(1).mid(4).toInt(); + + int offset = 0; + QString relType; + + for (int i = 0; i < ptch.at(1).size(); i++) { + QString c(ptch.at(1).mid(i, 1)); + if (c >= "0" && c <= "9") { + break; + } else if (c == ".") { + offset++; + break; + } + + offset++; + relType.append(c); } + + version.preReleaseType = preReleaseTypeToInt(relType); + + if (version.preReleaseType > -1 && offset < ptch.at(1).size()) + version.preReleaseNumber = ptch.at(1).mid(offset).toInt(); + else + version.preReleaseType = PR_NONE; } } + + //qDebug() << "vers:" << vers << "toString:" << toString() << "toInt:" << toInt(); + + return true; } -bool SemanticVersion::isValid(QString vers) +SemanticVersion& SemanticVersion::operator=(const SemanticVersion& rhs) { - if (vers.trimmed().isEmpty()) - return false; + version.major = rhs.version.major; + version.minor = rhs.version.minor; + version.patch = rhs.version.patch; + version.preReleaseType = rhs.version.preReleaseType; + version.preReleaseNumber = rhs.version.preReleaseNumber; + return *this; +} - QStringList strl = vers.split("."); - if (strl.count() < 2) +bool SemanticVersion::isValid(const QString vers) +{ + QString v(vers); + + v = v.trimmed(); + if (v.toLower().startsWith("v")) + v = v.mid(1); + +#if 0 + // Keep for testing full standard + // Note: regexp adapted for Qt ie extra escaping + QRegularExpression rx1("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); + + // Qt only test as not all patterns supported and can change in later releases + if (!rx1.isValid()) { + qDebug() << "Full standard is an invalid Qt regular expression"; return false; + } + + if (!rx1.match(v).hasMatch()) { + qDebug() << vers << "is not a valid Semantic Version - "; + return false; + } +#endif // 0 + + // we only support a subset of the standard alpha, beta, rc with period optional and number optional + // format: major.minor.patch[-[alpha|beta|rc][.|][n|]] + + QRegularExpression rx2("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-(alpha|beta|rc)\\.?(0|[1-9]\\d*)?)*$"); + + // Qt only test as not all patterns supported and can change in later releases + if (!rx2.isValid()) { + qDebug() << "Standard subset is an invalid Qt regular expression"; + return false; + } + + if (!rx2.match(v.toLower()).hasMatch()) { + //qDebug() << vers << "is not a valid Semantic Version subset - "; + return false; + } + + return isValid(); +} + +bool SemanticVersion::isValid() +{ + // range checks to support 32 bit OS when components compounded + if (version.major < 0 || version.major > 255 || version.minor < 0 || version.minor > 255 || version.patch < 0 || version.patch > 255 || + version.preReleaseType < 0 || version.preReleaseType > PR_NONE || version.preReleaseNumber < 0 || version.preReleaseNumber > 15) { + qDebug() << "Cannot convert to supported Semantec Version"; + version = SemanticVersion().version; + return false; + } return true; } @@ -772,15 +862,9 @@ QString SemanticVersion::toString() const QString ret(QString("%1.%2.%3").arg(version.major).arg(version.minor).arg(version.patch)); if (version.preReleaseType != PR_NONE) { - ret.append("-"); - if (version.preReleaseType == PR_RC) - ret.append("RC"); - else if (version.preReleaseType == PR_ALPHA) - ret.append("ALPHA"); - else if (version.preReleaseType == PR_BETA) - ret.append("BETA"); - - ret.append(QString::number(version.preReleaseNumber)); + ret = QString("%1-%2").arg(ret).arg(preReleaseTypeToString()); + if (version.preReleaseNumber > 0) + ret = QString("%1.%2").arg(ret).arg(version.preReleaseNumber); } return ret; @@ -810,3 +894,26 @@ int SemanticVersion::compare(const SemanticVersion& other) return 0; } + +unsigned int SemanticVersion::toInt() const +{ + // limit to 32 bits for OS backward compatibility + unsigned int val = 0; + setBitmappedValue(val, version.major, 0, 8, 24); + setBitmappedValue(val, version.minor, 0, 8, 16); + setBitmappedValue(val, version.patch, 0, 8, 8); + setBitmappedValue(val, version.preReleaseType, 0, 4, 4); + setBitmappedValue(val, version.preReleaseNumber, 0, 4); + return val; +} + +bool SemanticVersion::fromInt(const unsigned int val) +{ + // assumption val was generated by toInt() but validate anyway + version.major = Helpers::getBitmappedValue(val, 0, 8, 24); + version.minor = Helpers::getBitmappedValue(val, 0, 8, 16); + version.patch = Helpers::getBitmappedValue(val, 0, 8, 8); + version.preReleaseType = Helpers::getBitmappedValue(val, 0, 4, 4); + version.preReleaseNumber = Helpers::getBitmappedValue(val, 0, 4); + return isValid(); +} diff --git a/companion/src/helpers.h b/companion/src/helpers.h index 2f0e5ee4e..5a48abaf6 100644 --- a/companion/src/helpers.h +++ b/companion/src/helpers.h @@ -113,8 +113,8 @@ namespace Helpers QString getChecklistFilename(const ModelData * model); QString getChecklistFilePath(const ModelData * model); QString removeAccents(const QString & str); - unsigned int getBitmappedValue(const unsigned int & field, const unsigned int index, const unsigned int numbits = 1, const unsigned int offset = 0); - void setBitmappedValue(unsigned int & field, unsigned int value, unsigned int index, unsigned int numbits = 1, unsigned int offset = 0); + unsigned int getBitmappedValue(const unsigned int & field, const unsigned int index = 0, const unsigned int numbits = 1, const unsigned int offset = 0); + void setBitmappedValue(unsigned int & field, unsigned int value, unsigned int index = 0, unsigned int numbits = 1, unsigned int offset = 0); } // namespace Helpers @@ -244,11 +244,18 @@ extern Stopwatch gStopwatch; class SemanticVersion { public: - explicit SemanticVersion(QString vers); - ~SemanticVersion() = default;; + explicit SemanticVersion(const QString vers); + explicit SemanticVersion() {} + ~SemanticVersion() {} bool isValid(const QString vers); + bool isValid(); + bool fromString(const QString vers); QString toString() const; + unsigned int toInt() const; + bool fromInt(const unsigned int val); + + SemanticVersion& operator=(const SemanticVersion& rhs); bool operator==(const SemanticVersion& rhs) { return compare(rhs) == 0; @@ -275,23 +282,27 @@ class SemanticVersion } private: - enum PreRelease { + enum PreReleaseTypes { PR_ALPHA = 0, - PR_BETA = 1, - PR_RC = 2, - PR_NONE = 3 + PR_BETA, + PR_RC, + PR_NONE }; + const QStringList PreReleaseTypesStringList = { "alpha", "beta", "rc"}; + struct Version { - int major = 0; - int minor = 1; - int patch = 0; - PreRelease preReleaseType = PR_NONE; - int preReleaseNumber = 0; + int major = 0; + int minor = 0; + int patch = 0; + int preReleaseType = PR_NONE; + int preReleaseNumber = 0; }; Version version; int compare(const SemanticVersion& other); + inline QString preReleaseTypeToString() const { return PreReleaseTypesStringList.value(version.preReleaseType, ""); } + inline int preReleaseTypeToInt(QString preRelType) const { return PreReleaseTypesStringList.indexOf(preRelType); } }; diff --git a/companion/src/updates/repomodels.cpp b/companion/src/updates/repomodels.cpp index 8ccf3504d..8890384dc 100644 --- a/companion/src/updates/repomodels.cpp +++ b/companion/src/updates/repomodels.cpp @@ -21,6 +21,7 @@ #include "repomodels.h" #include "appdata.h" +#include "helpers.h" #include #include @@ -52,7 +53,7 @@ ReleasesItemModel::ReleasesItemModel() : m_releaseChannel(-1), m_refreshRequired(true) { - setSortRole(UpdatesItemModel::IMDR_Date); + setSortRole(UpdatesItemModel::IMDR_SortOrder); } void ReleasesItemModel::setReleaseChannel(const int channel) @@ -137,6 +138,18 @@ void ReleasesItemModel::parseJsonObject(const QJsonObject & obj) setDynamicItemData(item); + SemanticVersion sv; + + if (tag.toLower() == m_nightlyName) + sv.fromString("255.255.255"); + else + sv.fromString(tag); + + if (sv.isValid()) + item->setData(sv.toInt(), UpdatesItemModel::IMDR_SortOrder); + else + item->setData(item->data(IMDR_Date).toInt(), UpdatesItemModel::IMDR_SortOrder); + appendRow(item); } diff --git a/companion/src/updates/repomodels.h b/companion/src/updates/repomodels.h index 9abc2d097..468704b0e 100644 --- a/companion/src/updates/repomodels.h +++ b/companion/src/updates/repomodels.h @@ -49,6 +49,7 @@ class UpdatesItemModel : public QStandardItemModel enum ItemModelDataRoles { IMDR_Id = Qt::UserRole, IMDR_Date, + IMDR_SortOrder, IMDR_Tag, IMDR_Prerelease, IMDR_Content, diff --git a/companion/src/updates/updateinterface.cpp b/companion/src/updates/updateinterface.cpp index eb20ada41..1c1bc9f0e 100644 --- a/companion/src/updates/updateinterface.cpp +++ b/companion/src/updates/updateinterface.cpp @@ -317,7 +317,7 @@ QString UpdateInterface::downloadDataTypeToString(DownloadDataType val) } // static -QString UpdateInterface::semanticVersion(QString version) +QStringList UpdateInterface::versionToStringList(QString version) { QStringList strl = version.split("."); @@ -325,7 +325,7 @@ QString UpdateInterface::semanticVersion(QString version) strl.append("0"); } - return strl.join("."); + return strl; } void UpdateInterface::reportProgress(const QString & text, const int type) @@ -493,10 +493,10 @@ const bool UpdateInterface::isUpdateAvailable() const bool UpdateInterface::isLatestVersion(const QString & installed, const QString & latest) { - QStringList a = semanticVersion(installed).split("."); - QStringList b = semanticVersion(latest).split("."); + QStringList a = versionToStringList(installed); + QStringList b = versionToStringList(latest); - // not equal comparision used to force update if client upgraded and the release is subsequently withdrawn or subscribed to nightlies + // not equal comparision used as version identifiers are inconsistent eg release vs nightly vs older for (int i = 0; i < 4; i++) { if (a.at(i) != b.at(i)) { return false; diff --git a/companion/src/updates/updateinterface.h b/companion/src/updates/updateinterface.h index 6ecd3861e..9afdde241 100644 --- a/companion/src/updates/updateinterface.h +++ b/companion/src/updates/updateinterface.h @@ -239,7 +239,7 @@ class UpdateInterface : public QWidget bool downloadSuccess; - static QString semanticVersion(QString version); + static QStringList versionToStringList(QString version); void initAppSettings(); bool setRunFolders();