#include #include #include "updates_checker.h" #include "version.h" UpdatesChecker::UpdatesChecker() { connect(&m_networkAccessManager, &QNetworkAccessManager::finished, this, &UpdatesChecker::downloadFinished); } void UpdatesChecker::start() { QUrl url(APP_UPDATES_CHECKER_URL); QNetworkRequest request(url); m_networkAccessManager.get(request); } const QString &UpdatesChecker::message() const { return m_message; } bool UpdatesChecker::isLatest() const { return m_isLatest; } bool UpdatesChecker::hasError() const { return m_hasError; } bool UpdatesChecker::parseUpdateInfoXml(const QByteArray &updateInfoXml, std::vector *updateItems) { std::vector elementNameStack; QXmlStreamReader reader(updateInfoXml); while (!reader.atEnd()) { reader.readNext(); if (!reader.isStartElement() && !reader.isEndElement()) { if (!reader.name().toString().isEmpty()) qDebug() << "Skip xml element:" << reader.name().toString() << " tokenType:" << reader.tokenType(); continue; } QString baseName = reader.name().toString(); if (reader.isStartElement()) { elementNameStack.push_back(baseName); if (elementNameStack.size() > 10) { qDebug() << "Invalid xml, element name stack exceed limits"; return false; } } QStringList nameItems; for (const auto &nameItem: elementNameStack) { nameItems.append(nameItem); } QString fullName = nameItems.join("."); if (reader.isEndElement()) elementNameStack.pop_back(); if (reader.isStartElement()) { if (fullName == "updates.update") { UpdateItem updateItem; updateItem.forTags = reader.attributes().value("for").toString().toLower(); updateItem.version = reader.attributes().value("version").toString(); updateItem.humanVersion = reader.attributes().value("humanVersion").toString(); updateItem.descriptionUrl = reader.attributes().value("descriptionUrl").toString(); if (!updateItem.forTags.isEmpty() && !updateItem.version.isEmpty() && !updateItem.humanVersion.isEmpty() && !updateItem.descriptionUrl.isEmpty()) { if (updateItems->size() >= 100) return false; updateItem.forTags = "," + updateItem.forTags + ","; updateItems->push_back(updateItem); } } } } return true; } bool UpdatesChecker::isVersionLessThan(const QString &version, const QString &compareWith) { auto versionTokens = version.split("."); auto compareWithTokens = compareWith.split("."); if (versionTokens.size() > 4) { return false; } while (compareWithTokens.size() < 4) compareWithTokens.push_back("0"); if (compareWithTokens.size() > 4) { return false; } while (compareWithTokens.size() < 4) compareWithTokens.push_back("0"); for (size_t i = 0; i < 4; ++i) { int left = versionTokens[i].toInt(); int right = compareWithTokens[i].toInt(); if (left > right) return false; else if (left < right) return true; } return false; } const UpdatesChecker::UpdateItem &UpdatesChecker::matchedUpdateItem() const { return m_matchedUpdateItem; } void UpdatesChecker::downloadFinished(QNetworkReply *reply) { if (reply->error()) { qDebug() << "Download update info failed:" << qPrintable(reply->errorString()); m_message = tr("Fetch update info failed, please retry later"); } else { int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (200 != statusCode) { qDebug() << "Download update info failed, statusCode:" << statusCode; m_message = tr("Fetch update info failed, please retry later"); } else { std::vector updateItems; if (!parseUpdateInfoXml(reply->readAll(), &updateItems)) { m_message = tr("Fetch update info failed, please retry later"); } else { m_isLatest = true; m_hasError = false; m_message = tr("%1 %2 is currently the newest version available").arg(APP_NAME).arg(APP_HUMAN_VER); QString platform = QString(APP_PLATFORM).toLower(); if (!platform.isEmpty()) { platform = "," + platform + ","; for (const auto &it: updateItems) { if (-1 == it.forTags.indexOf(platform)) continue; if (isVersionLessThan(APP_VER, it.version)) { m_isLatest = false; m_message = tr("An update is available: %1 %2").arg(APP_NAME).arg(it.humanVersion); m_matchedUpdateItem = it; break; } } } } } } reply->deleteLater(); emit finished(); }