summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKatja Marttila <katja.marttila@qt.io>2022-12-20 09:16:08 +0200
committerKatja Marttila <katja.marttila@qt.io>2022-12-22 10:02:11 +0200
commit7828f6f9c4f49afc4583d67b42ce31323256312a (patch)
tree0de0541c09a22cea188104af8f80a6d3b779a9d6
parentfa94c6f7bc97a112ec0b27f0e896a92845f2a894 (diff)
Optimize Updates.xml parsing
QDomDocument::setContent is time consuming operation especially when there are thousands of Updates.xml to be parsed. Using QXmlStreamReader for parsing the xml content instead, the Updates.xml content is already validated in metadata job after download. In Windows, using QXmlStreamReader instead of QDomDocument decreases the xml parsing time from ~10 seconds to ~1 seconds when there are thousands of Updates.xml to be parsed. Task-number: QTIFW-2924 Change-Id: I14f5eba5b72f451a12ab4e3bf6b2f00b5f66faaf Reviewed-by: Arttu Tarkiainen <arttu.tarkiainen@qt.io>
-rw-r--r--src/libs/kdtools/updatesinfo.cpp230
-rw-r--r--src/libs/kdtools/updatesinfodata_p.h10
-rw-r--r--tests/auto/installer/componentmodel/data/updates.xml2
-rw-r--r--tests/auto/installer/componentmodel/tst_componentmodel.cpp14
4 files changed, 131 insertions, 125 deletions
diff --git a/src/libs/kdtools/updatesinfo.cpp b/src/libs/kdtools/updatesinfo.cpp
index 339118d42..fafeb4f18 100644
--- a/src/libs/kdtools/updatesinfo.cpp
+++ b/src/libs/kdtools/updatesinfo.cpp
@@ -28,13 +28,14 @@
#include "updatesinfo_p.h"
#include "utils.h"
+#include "constants.h"
-#include <QDomDocument>
#include <QFile>
#include <QLocale>
#include <QPair>
#include <QVector>
#include <QUrl>
+#include <QXmlStreamReader>
using namespace KDUpdater;
@@ -62,35 +63,24 @@ void UpdatesInfoData::parseFile(const QString &updateXmlFile)
return;
}
- QDomDocument doc;
- QString parseErrorMessage;
- int parseErrorLine, parseErrorColumn;
- if (!doc.setContent(&file, &parseErrorMessage, &parseErrorLine, &parseErrorColumn)) {
- error = UpdatesInfo::InvalidXmlError;
- errorMessage = tr("Parse error in %1 at %2, %3: %4").arg(updateXmlFile,
- QString::number(parseErrorLine), QString::number(parseErrorColumn), parseErrorMessage);
- return;
- }
-
- QDomElement rootE = doc.documentElement();
- if (rootE.tagName() != QLatin1String("Updates")) {
- setInvalidContentError(tr("Root element %1 unexpected, should be \"Updates\".").arg(rootE.tagName()));
- return;
- }
-
- QDomNodeList childNodes = rootE.childNodes();
- for(int i = 0; i < childNodes.count(); i++) {
- const QDomElement childE = childNodes.at(i).toElement();
- if (childE.isNull())
- continue;
-
- if (childE.tagName() == QLatin1String("ApplicationName"))
- applicationName = childE.text();
- else if (childE.tagName() == QLatin1String("ApplicationVersion"))
- applicationVersion = childE.text();
- else if (childE.tagName() == QLatin1String("PackageUpdate")) {
- if (!parsePackageUpdateElement(childE))
- return; //error handled in subroutine
+ QXmlStreamReader reader(&file);
+ if (reader.readNextStartElement()) {
+ if (reader.name() == QLatin1String("Updates")) {
+ while (reader.readNextStartElement()) {
+ if (reader.name() == QLatin1String("ApplicationName")) {
+ applicationName = reader.readElementText();
+ } else if (reader.name() == QLatin1String("ApplicationVersion")) {
+ applicationVersion = reader.readElementText();
+ } else if (reader.name() == QLatin1String("PackageUpdate")) {
+ if (!parsePackageUpdateElement(reader))
+ return; //error handled in subroutine
+ } else {
+ reader.skipCurrentElement();
+ }
+ }
+ } else {
+ setInvalidContentError(tr("Root element %1 unexpected, should be \"Updates\".").arg(reader.name()));
+ return;
}
}
@@ -108,85 +98,52 @@ void UpdatesInfoData::parseFile(const QString &updateXmlFile)
error = UpdatesInfo::NoError;
}
-bool UpdatesInfoData::parsePackageUpdateElement(const QDomElement &updateE)
+bool UpdatesInfoData::parsePackageUpdateElement(QXmlStreamReader &reader)
{
- if (updateE.isNull())
- return false;
-
UpdateInfo info;
- QMap<QString, QString> localizedDescriptions;
QHash<QString, QVariant> scriptHash;
- for (int i = 0; i < updateE.childNodes().count(); i++) {
- QDomElement childE = updateE.childNodes().at(i).toElement();
- if (childE.isNull())
+ while (reader.readNext()) {
+ const QString elementName = reader.name().toString();
+ if ((reader.name() == QLatin1String("PackageUpdate"))
+ && (reader.tokenType() == QXmlStreamReader::EndElement)) {
+ break;
+ }
+ if (elementName.isEmpty() || reader.tokenType() == QXmlStreamReader::EndElement)
continue;
-
- if (childE.tagName() == QLatin1String("ReleaseNotes")) {
- info.data[childE.tagName()] = QUrl(childE.text());
- } else if (childE.tagName() == QLatin1String("Licenses")) {
- QHash<QString, QVariant> licenseHash;
- const QDomNodeList licenseNodes = childE.childNodes();
- for (int index = 0; index < licenseNodes.count(); ++index) {
- const QDomNode licenseNode = licenseNodes.at(index);
- if (licenseNode.nodeName() == QLatin1String("License")) {
- QDomElement element = licenseNode.toElement();
- QVariantMap attributes;
- attributes.insert(QLatin1String("file"), element.attributeNode(QLatin1String("file")).value());
- if (!element.attributeNode(QLatin1String("priority")).isNull())
- attributes.insert(QLatin1String("priority"), element.attributeNode(QLatin1String("priority")).value());
- else
- attributes.insert(QLatin1String("priority"), QLatin1String("0"));
- licenseHash.insert(element.attributeNode(QLatin1String("name")).value(), attributes);
- }
- }
- if (!licenseHash.isEmpty())
- info.data.insert(QLatin1String("Licenses"), licenseHash);
- } else if (childE.tagName() == QLatin1String("TreeName")) {
- const bool moveChildren = QVariant(childE.attribute(QLatin1String("moveChildren"))).toBool();
- const QPair<QString, bool> treeNamePair(childE.text(), moveChildren);
+ if (elementName == QLatin1String("Licenses")) {
+ parseLicenses(reader, info.data);
+ } else if (elementName == QLatin1String("TreeName")) {
+ const QXmlStreamAttributes attr = reader.attributes();
+ const bool moveChildren = attr.value(QLatin1String("moveChildren")).toString().toLower() == QInstaller::scTrue ? true : false;
+ const QPair<QString, bool> treeNamePair(reader.readElementText(), moveChildren);
info.data.insert(QLatin1String("TreeName"), QVariant::fromValue(treeNamePair));
- } else if (childE.tagName() == QLatin1String("Version")) {
+ } else if (elementName == QLatin1String("Version")) {
+ const QXmlStreamAttributes attr = reader.attributes();
info.data.insert(QLatin1String("inheritVersionFrom"),
- childE.attribute(QLatin1String("inheritVersionFrom")));
- info.data[childE.tagName()] = childE.text();
- } else if (childE.tagName() == QLatin1String("DisplayName")) {
- processLocalizedTag(childE, info.data);
- } else if (childE.tagName() == QLatin1String("Description")) {
- if (!childE.hasAttribute(QLatin1String("xml:lang")))
- info.data[QLatin1String("Description")] = childE.text();
- QString languageAttribute = childE.attribute(QLatin1String("xml:lang"), QLatin1String("en"));
- localizedDescriptions.insert(languageAttribute.toLower(), childE.text());
- } else if (childE.tagName() == QLatin1String("UpdateFile")) {
- info.data[QLatin1String("CompressedSize")] = childE.attribute(QLatin1String("CompressedSize"));
- info.data[QLatin1String("UncompressedSize")] = childE.attribute(QLatin1String("UncompressedSize"));
- } else if (childE.tagName() == QLatin1String("Operations")) {
- const QDomNodeList operationNodes = childE.childNodes();
- QVariant operationListVariant = parseOperations(childE.childNodes());
- info.data.insert(QLatin1String("Operations"), operationListVariant);
- } else if (childE.tagName() == QLatin1String("Script")) {
- const bool postLoad = QVariant(childE.attribute(QLatin1String("postLoad"))).toBool();
+ attr.value(QLatin1String("inheritVersionFrom")).toString());
+ info.data[elementName] = reader.readElementText();
+ } else if (elementName == QLatin1String("DisplayName")
+ || elementName == QLatin1String("Description")) {
+ processLocalizedTag(reader, info.data);
+ } else if (elementName == QLatin1String("UpdateFile")) {
+ info.data[QLatin1String("CompressedSize")] = reader.attributes().value(QLatin1String("CompressedSize")).toString();
+ info.data[QLatin1String("UncompressedSize")] = reader.attributes().value(QLatin1String("UncompressedSize")).toString();
+ } else if (elementName == QLatin1String("Operations")) {
+ parseOperations(reader, info.data);
+ } else if (elementName == QLatin1String("Script")) {
+ const QXmlStreamAttributes attr = reader.attributes();
+ const bool postLoad = attr.value(QLatin1String("postLoad")).toString().toLower() == QInstaller::scTrue ? true : false;
if (postLoad)
- scriptHash.insert(QLatin1String("postLoadScript"), childE.text());
+ scriptHash.insert(QLatin1String("postLoadScript"), reader.readElementText());
else
- scriptHash.insert(QLatin1String("installScript"), childE.text());
+ scriptHash.insert(QLatin1String("installScript"), reader.readElementText());
} else {
- info.data[childE.tagName()] = childE.text();
+ info.data[elementName] = reader.readElementText();
}
}
if (!scriptHash.isEmpty())
info.data.insert(QLatin1String("Script"), scriptHash);
- QStringList candidates;
- static const QStringList uiLanguages = QLocale().uiLanguages();
- foreach (const QString &lang, uiLanguages)
- candidates << QInstaller::localeCandidates(lang.toLower());
- foreach (const QString &candidate, candidates) {
- if (localizedDescriptions.contains(candidate)) {
- info.data[QLatin1String("Description")] = localizedDescriptions.value(candidate);
- break;
- }
- }
-
if (!info.data.contains(QLatin1String("Name"))) {
setInvalidContentError(tr("PackageUpdate element without Name"));
return false;
@@ -204,44 +161,77 @@ bool UpdatesInfoData::parsePackageUpdateElement(const QDomElement &updateE)
return true;
}
-void UpdatesInfoData::processLocalizedTag(const QDomElement &childE, QHash<QString, QVariant> &info) const
+void UpdatesInfoData::processLocalizedTag(QXmlStreamReader &reader, QHash<QString, QVariant> &info) const
{
- QString languageAttribute = childE.attribute(QLatin1String("xml:lang")).toLower();
- if (!info.contains(childE.tagName()) && (languageAttribute.isEmpty()))
- info[childE.tagName()] = childE.text();
+ const QString languageAttribute = reader.attributes().value(QLatin1String("xml:lang")).toString().toLower();
+ const QString elementName = reader.name().toString();
+ if (!info.contains(elementName) && (languageAttribute.isEmpty()))
+ info[elementName] = reader.readElementText();
+ if (languageAttribute.isEmpty())
+ return;
// overwrite default if we have a language specific description
if (QLocale().name().startsWith(languageAttribute, Qt::CaseInsensitive))
- info[childE.tagName()] = childE.text();
+ info[elementName] = reader.readElementText();
}
-QVariant UpdatesInfoData::parseOperations(const QDomNodeList &operationNodes)
+void UpdatesInfoData::parseOperations(QXmlStreamReader &reader, QHash<QString, QVariant> &info) const
{
- QVariant operationListVariant;
QList<QPair<QString, QVariant>> operationsList;
- for (int i = 0; i < operationNodes.count(); ++i) {
- const QDomNode operationNode = operationNodes.at(i);
- if (operationNode.nodeName() == QLatin1String("Operation")) {
- const QDomNodeList argumentNodes = operationNode.childNodes();
- QStringList attributes;
- for (int index = 0; index < argumentNodes.count(); ++index) {
- const QDomNode argumentNode = argumentNodes.at(index);
- if (argumentNode.nodeName() == QLatin1String("Argument")) {
- QDomElement argumentElement = argumentNode.toElement();
- attributes.append(argumentElement.text());
- }
+ while (reader.readNext()) {
+ const QString subElementName = reader.name().toString();
+ // End of parsing operations
+ if ((subElementName == QLatin1String("Operations"))
+ && (reader.tokenType() == QXmlStreamReader::EndElement)) {
+ break;
+ }
+ if (subElementName != QLatin1String("Operation") || reader.tokenType() == QXmlStreamReader::EndElement)
+ continue;
+ QStringList attributes;
+ const QXmlStreamAttributes attr = reader.attributes();
+ while (reader.readNext()) {
+ const QString subElementName2 = reader.name().toString();
+ // End of parsing single operation
+ if ((subElementName2 == QLatin1String("Operation"))
+ && (reader.tokenType() == QXmlStreamReader::EndElement)) {
+ break;
}
- QPair<QString, QVariant> pair;
- pair.first = operationNode.toElement().attributeNode(QLatin1String("name")).value();
- pair.second = attributes;
- operationsList.append(pair);
+ if (subElementName2 != QLatin1String("Argument") || reader.tokenType() == QXmlStreamReader::EndElement)
+ continue;
+ attributes.append(reader.readElementText());
}
+ QPair<QString, QVariant> pair;
+ pair.first = attr.value(QLatin1String("name")).toString();
+ pair.second = attributes;
+ operationsList.append(pair);
}
- operationListVariant.setValue(operationsList);
- return operationListVariant;
+ info.insert(QLatin1String("Operations"), QVariant::fromValue(operationsList));
}
-
+void UpdatesInfoData::parseLicenses(QXmlStreamReader &reader, QHash<QString, QVariant> &info) const
+{
+ QHash<QString, QVariant> licenseHash;
+ while (reader.readNext()) {
+ const QString subElementName = reader.name().toString();
+ // End of parsing Licenses
+ if ((subElementName == QLatin1String("Licenses"))
+ && (reader.tokenType() == QXmlStreamReader::EndElement)) {
+ break;
+ }
+ if (subElementName != QLatin1String("License") || reader.tokenType() == QXmlStreamReader::EndElement)
+ continue;
+ const QXmlStreamAttributes attr = reader.attributes();
+ QVariantMap attributes;
+ attributes.insert(QLatin1String("file"), attr.value(QLatin1String("file")).toString());
+ if (!attr.value(QLatin1String("priority")).isNull())
+ attributes.insert(QLatin1String("priority"), attr.value(QLatin1String("priority")).toString());
+ else
+ attributes.insert(QLatin1String("priority"), QLatin1String("0"));
+ licenseHash.insert(attr.value(QLatin1String("name")).toString(), attributes);
+ }
+ if (!licenseHash.isEmpty())
+ info.insert(QLatin1String("Licenses"), licenseHash);
+}
//
// UpdatesInfo
//
diff --git a/src/libs/kdtools/updatesinfodata_p.h b/src/libs/kdtools/updatesinfodata_p.h
index 07da6fcf0..9c3c7dce0 100644
--- a/src/libs/kdtools/updatesinfodata_p.h
+++ b/src/libs/kdtools/updatesinfodata_p.h
@@ -32,8 +32,7 @@
#include <QCoreApplication>
#include <QSharedData>
-QT_FORWARD_DECLARE_CLASS(QDomElement)
-QT_FORWARD_DECLARE_CLASS(QDomNodeList)
+QT_FORWARD_DECLARE_CLASS(QXmlStreamReader)
namespace KDUpdater {
@@ -55,13 +54,14 @@ public:
QList<UpdateInfo> updateInfoList;
void parseFile(const QString &updateXmlFile);
- bool parsePackageUpdateElement(const QDomElement &updateE);
+ bool parsePackageUpdateElement(QXmlStreamReader &reader);
void setInvalidContentError(const QString &detail);
private:
- void processLocalizedTag(const QDomElement &childE, QHash<QString, QVariant> &info) const;
- QVariant parseOperations(const QDomNodeList &operationNodes);
+ void processLocalizedTag(QXmlStreamReader &reader, QHash<QString, QVariant> &info) const;
+ void parseOperations(QXmlStreamReader &reader, QHash<QString, QVariant> &info) const;
+ void parseLicenses(QXmlStreamReader &reader, QHash<QString, QVariant> &info) const;
};
} // namespace KDUpdater
diff --git a/tests/auto/installer/componentmodel/data/updates.xml b/tests/auto/installer/componentmodel/data/updates.xml
index 3a6139446..cc2da0f8e 100644
--- a/tests/auto/installer/componentmodel/data/updates.xml
+++ b/tests/auto/installer/componentmodel/data/updates.xml
@@ -8,6 +8,8 @@
<DisplayName xml:lang="ru_RU">Корневая компонента</DisplayName>
<DisplayName xml:lang="de_DE">Wurzel Komponente</DisplayName>
<Description>Install this example.</Description>
+ <Description xml:lang="ru_RU">Установите этот пример.</Description>
+ <Description xml:lang="de_DE">Installieren Sie dieses Beispiel.</Description>
<Version>0.1.0-1</Version>
<ReleaseDate>2010-09-21</ReleaseDate>
<Default>true</Default>
diff --git a/tests/auto/installer/componentmodel/tst_componentmodel.cpp b/tests/auto/installer/componentmodel/tst_componentmodel.cpp
index 463485d96..2836c80df 100644
--- a/tests/auto/installer/componentmodel/tst_componentmodel.cpp
+++ b/tests/auto/installer/componentmodel/tst_componentmodel.cpp
@@ -59,6 +59,12 @@ static const QMap<QString, QString> rootComponentDisplayNames = {
{"de_de", QString::fromUtf8("Wurzel Komponente")}
};
+static const QMap<QString, QString> rootComponentDescriptions = {
+ {"", QLatin1String("Install this example.")},
+ {"ru_ru", QString::fromUtf8("Установите этот пример.")},
+ {"de_de", QString::fromUtf8("Installieren Sie dieses Beispiel.")}
+};
+
class tst_ComponentModel : public QObject
{
Q_OBJECT
@@ -372,6 +378,10 @@ private slots:
? rootComponentDisplayNames[localeToTest.toLower()]
: rootComponentDisplayNames[QString()];
+ QString expectedDescription = rootComponentDescriptions.contains(localeToTest.toLower())
+ ? rootComponentDescriptions[localeToTest.toLower()]
+ : rootComponentDescriptions[QString()];
+
setPackageManagerOptions(NoFlags);
QList<Component*> rootComponents = loadComponents();
@@ -384,6 +394,9 @@ private slots:
const QModelIndex root = model.indexFromComponentName(vendorProduct);
QCOMPARE(model.data(root, Qt::DisplayRole).toString(), expectedName);
+ Component *comp = model.componentFromIndex(root);
+ QCOMPARE(comp->value("Description"), expectedDescription);
+
qDeleteAll(rootComponents);
}
}
@@ -529,6 +542,7 @@ private:
component->setValue("Virtual", info.data.value("Virtual").toString());
component->setValue("DisplayName", info.data.value("DisplayName").toString());
component->setValue("Checkable", info.data.value("Checkable").toString());
+ component->setValue("Description", info.data.value("Description").toString());
QString forced = info.data.value("ForcedInstallation", scFalse).toString().toLower();
if (m_core.noForceInstallation())