diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index a9eb2f5c8..3cc8c1534 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -48,6 +48,7 @@ utils/misc.h utils/net.h utils/random.h utils/string.h +utils/version.h filesystemwatcher.h iconprovider.h indexrange.h diff --git a/src/base/base.pri b/src/base/base.pri index 92e781d0c..ef9906d14 100644 --- a/src/base/base.pri +++ b/src/base/base.pri @@ -54,6 +54,7 @@ HEADERS += \ $$PWD/utils/net.h \ $$PWD/utils/random.h \ $$PWD/utils/string.h \ + $$PWD/utils/version.h \ $$PWD/profile.h \ $$PWD/private/profile_p.h \ $$PWD/unicodestrings.h \ diff --git a/src/base/searchengine.cpp b/src/base/searchengine.cpp index df86e2312..56e7b684f 100644 --- a/src/base/searchengine.cpp +++ b/src/base/searchengine.cpp @@ -177,11 +177,11 @@ void SearchEngine::installPlugin(const QString &source) void SearchEngine::installPlugin_impl(const QString &name, const QString &path) { - qreal newVersion = getPluginVersion(path); - qDebug("Version to be installed: %.2f", newVersion); + PluginVersion newVersion = getPluginVersion(path); + qDebug() << "Version to be installed:" << newVersion; PluginInfo *plugin = pluginInfo(name); - if (plugin && (plugin->version >= newVersion)) { + if (plugin && !(plugin->version < newVersion)) { qDebug("Apparently update is not needed, we have a more recent version"); emit pluginUpdateFailed(name, tr("A more recent version of this plugin is already installed.")); return; @@ -608,7 +608,7 @@ void SearchEngine::parseVersionInfo(const QByteArray &info) { qDebug("Checking if update is needed"); - QHash updateInfo; + QHash updateInfo; bool dataCorrect = false; QList lines = info.split('\n'); foreach (QByteArray line, lines) { @@ -624,14 +624,17 @@ void SearchEngine::parseVersionInfo(const QByteArray &info) pluginName.chop(1); // remove trailing ':' bool ok; - qreal version = list.last().toFloat(&ok); - qDebug("read line %s: %.2f", qPrintable(pluginName), version); + qreal versionParseTest = list.last().toFloat(&ok); + qDebug("read line %s: %.2f", qPrintable(pluginName), versionParseTest); if (!ok) continue; - dataCorrect = true; - if (isUpdateNeeded(pluginName, version)) { - qDebug("Plugin: %s is outdated", qPrintable(pluginName)); - updateInfo[pluginName] = version; + PluginVersion version = PluginVersion::tryParse(list.last(), {}); + if (version != PluginVersion()) { + dataCorrect = true; + if (isUpdateNeeded(pluginName, version)) { + qDebug("Plugin: %s is outdated", qPrintable(pluginName)); + updateInfo[pluginName] = version; + } } } @@ -641,13 +644,13 @@ void SearchEngine::parseVersionInfo(const QByteArray &info) emit checkForUpdatesFinished(updateInfo); } -bool SearchEngine::isUpdateNeeded(QString pluginName, qreal newVersion) const +bool SearchEngine::isUpdateNeeded(QString pluginName, PluginVersion newVersion) const { PluginInfo *plugin = pluginInfo(pluginName); if (!plugin) return true; - qreal oldVersion = plugin->version; - qDebug("IsUpdate needed? to be installed: %.2f, already installed: %.2f", newVersion, oldVersion); + PluginVersion oldVersion = plugin->version; + qDebug() << "IsUpdate needed? to be installed:" << newVersion << ", already installed:" << oldVersion; return (newVersion > oldVersion); } @@ -673,27 +676,26 @@ QHash SearchEngine::initializeCategoryNames() return result; } -qreal SearchEngine::getPluginVersion(QString filePath) +PluginVersion SearchEngine::getPluginVersion(QString filePath) { QFile plugin(filePath); if (!plugin.exists()) { qDebug("%s plugin does not exist, returning 0.0", qPrintable(filePath)); - return 0.0; + return {}; } if (!plugin.open(QIODevice::ReadOnly | QIODevice::Text)) - return 0.0; + return {}; - qreal version = 0.0; + PluginVersion version; while (!plugin.atEnd()) { QByteArray line = plugin.readLine(); if (line.startsWith("#VERSION: ")) { line = line.split(' ').last().trimmed(); - version = line.toFloat(); - qDebug("plugin %s version: %.2f", qPrintable(filePath), version); + version = PluginVersion::tryParse(line, {}); + qDebug() << "plugin" << filePath << "version: " << version; break; } } - return version; } diff --git a/src/base/searchengine.h b/src/base/searchengine.h index aa360fa2f..b44b8e55d 100644 --- a/src/base/searchengine.h +++ b/src/base/searchengine.h @@ -30,18 +30,23 @@ #ifndef SEARCHENGINE_H #define SEARCHENGINE_H -#include #include -#include #include +#include +#include + +#include "base/utils/version.h" class QProcess; class QTimer; +using PluginVersion = Utils::Version; +Q_DECLARE_METATYPE(PluginVersion) + struct PluginInfo { QString name; - qreal version; + PluginVersion version; QString fullName; QString url; QStringList supportedCategories; @@ -86,7 +91,7 @@ public: void downloadTorrent(const QString &siteUrl, const QString &url); - static qreal getPluginVersion(QString filePath); + static PluginVersion getPluginVersion(QString filePath); static QString categoryFullName(const QString &categoryName); QString pluginFullName(const QString &pluginName); static QString pluginsLocation(); @@ -102,7 +107,7 @@ signals: void pluginUpdated(const QString &name); void pluginUpdateFailed(const QString &name, const QString &reason); - void checkForUpdatesFinished(const QHash &updateInfo); + void checkForUpdatesFinished(const QHash &updateInfo); void checkForUpdatesFailed(const QString &reason); void torrentFileDownloaded(const QString &path); @@ -123,7 +128,7 @@ private: bool parseSearchResult(const QString &line, SearchResult &searchResult); void parseVersionInfo(const QByteArray &info); void installPlugin_impl(const QString &name, const QString &path); - bool isUpdateNeeded(QString pluginName, qreal newVersion) const; + bool isUpdateNeeded(QString pluginName, PluginVersion newVersion) const; static QString engineLocation(); static QString pluginPath(const QString &name); diff --git a/src/base/utils/version.h b/src/base/utils/version.h new file mode 100644 index 000000000..ca708f524 --- /dev/null +++ b/src/base/utils/version.h @@ -0,0 +1,195 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2016 Eugene Shalygin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#ifndef QBITTORRENT_UTILS_VERSION_H +#define QBITTORRENT_UTILS_VERSION_H + +#include +#include + +#include +#include +#include + +namespace Utils +{ + template + class Version + { + static_assert(N > 0, "The number of version components may not be smaller than 1"); + static_assert(N >= Mandatory, + "The number of mandatory components may not be larger than the total number of components"); + + public: + typedef T ComponentType; + typedef Version ThisType; + + constexpr Version() + : m_components {} + { + } + + constexpr Version(const ThisType &other) = default; + + template + constexpr Version(Other ... components) + : m_components {{components ...}} + { + } + + /** + * @brief Creates version from string in format "x.y.z" + * + * @param version Version string in format "x.y.z" + * @throws std::runtime_error if parsing fails + */ + Version(const QString &version) + : Version {version.split(QLatin1Char('.'))} + { + } + + /** + * @brief Creates version from byte array in format "x.y.z" + * + * @param version Version string in format "x.y.z" + * @throws std::runtime_error if parsing fails + */ + Version(const QByteArray &version) + : Version {version.split('.')} + { + } + + ComponentType majorNumber() const + { + static_assert(N >= 1, "The number of version components is too small"); + return (*this)[0]; + } + + ComponentType minorNumber() const + { + static_assert(N >= 2, "The number of version components is too small"); + return (*this)[1]; + } + + ComponentType revisionNumber() const + { + static_assert(N >= 3, "The number of version components is too small"); + return (*this)[2]; + } + + ComponentType patchNumber() const + { + static_assert(N >= 4, "The number of version components is too small"); + return (*this)[3]; + } + + ComponentType operator[](std::size_t i) const + { + return m_components.at(i); + } + + operator QString() const + { + // find the last one non-zero component + std::size_t lastSignificantIndex = N - 1; + while (lastSignificantIndex >= 0 && (*this)[lastSignificantIndex] == 0) + --lastSignificantIndex; + + if (lastSignificantIndex + 1 < Mandatory) // lastSignificantIndex >= 0 + lastSignificantIndex = Mandatory - 1; // and Mandatory >= 1 + + QString res = QString::number((*this)[0]); + for (std::size_t i = 1; i <= lastSignificantIndex; ++i) + res += QLatin1Char('.') + QString::number((*this)[i]); + return res; + } + + bool operator==(const ThisType &other) const + { + return m_components == other.m_components; + } + + bool operator<(const ThisType &other) const + { + return m_components < other.m_components; + } + + bool operator>(const ThisType &other) const + { + return m_components > other.m_components; + } + + template + static Version tryParse(const StringClassWithSplitMethod &s, const Version &defaultVersion) + { + try { + return Version(s); + } + catch (std::runtime_error &er) { + qDebug() << "Error parsing version:" << er.what(); + return defaultVersion; + } + } + + private: + using ComponentsArray = std::array; + + template + static ComponentsArray parseList(const StringsList &versionParts) + { + if ((static_cast(versionParts.size()) > N) + || (static_cast(versionParts.size()) < Mandatory)) + throw std::runtime_error ("Incorrect number of version components"); + + bool ok = false; + ComponentsArray res; + for (std::size_t i = 0; i < static_cast(versionParts.size()); ++i) { + res[i] = static_cast(versionParts[i].toInt(&ok)); + if (!ok) + throw std::runtime_error("Can not parse version component"); + } + return res; + } + + template + Version(const StringsList &versionParts) + : m_components (parseList(versionParts)) // GCC 4.8.4 has problems with the uniform initializer here + { + } + + ComponentsArray m_components; + }; + + template + inline bool operator!=(const Version &left, const Version &right) + { + return !(left == right); + } +} + +#endif // QBITTORRENT_UTILS_VERSION_H diff --git a/src/gui/search/pluginselectdlg.cpp b/src/gui/search/pluginselectdlg.cpp index af910b596..c53c9e325 100644 --- a/src/gui/search/pluginselectdlg.cpp +++ b/src/gui/search/pluginselectdlg.cpp @@ -298,7 +298,7 @@ void PluginSelectDlg::addNewPlugin(QString pluginName) connect(handler, SIGNAL(downloadFinished(QString, QString)), this, SLOT(iconDownloaded(QString, QString))); connect(handler, SIGNAL(downloadFailed(QString, QString)), this, SLOT(iconDownloadFailed(QString, QString))); } - item->setText(PLUGIN_VERSION, QString::number(plugin->version, 'f', 2)); + item->setText(PLUGIN_VERSION, plugin->version); } void PluginSelectDlg::startAsyncOp() @@ -424,9 +424,9 @@ void PluginSelectDlg::pluginInstallationFailed(const QString &name, const QStrin void PluginSelectDlg::pluginUpdated(const QString &name) { finishAsyncOp(); - qreal version = m_pluginManager->pluginInfo(name)->version; + PluginVersion version = m_pluginManager->pluginInfo(name)->version; QTreeWidgetItem *item = findItemWithID(name); - item->setText(PLUGIN_VERSION, QString::number(version, 'f', 2)); + item->setText(PLUGIN_VERSION, version); QMessageBox::information(this, tr("Search plugin install"), tr("\"%1\" search engine plugin was successfully updated.", "%1 is the name of the search engine").arg(name)); }