1/*
2 SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: MIT
5*/
6
7#include "definitiondownloader.h"
8#include "definition.h"
9#include "ksyntaxhighlighting_logging.h"
10#include "ksyntaxhighlighting_version.h"
11#include "repository.h"
12
13#include <QDir>
14#include <QFile>
15#include <QNetworkAccessManager>
16#include <QNetworkReply>
17#include <QNetworkRequest>
18#include <QStandardPaths>
19#include <QTimer>
20#include <QXmlStreamReader>
21
22using namespace KSyntaxHighlighting;
23
24class KSyntaxHighlighting::DefinitionDownloaderPrivate
25{
26public:
27 DefinitionDownloader *q;
28 Repository *repo;
29 QNetworkAccessManager *nam;
30 QString downloadLocation;
31 int pendingDownloads;
32 bool needsReload;
33
34 void definitionListDownloadFinished(QNetworkReply *reply);
35 void updateDefinition(QXmlStreamReader &parser);
36 void downloadDefinition(const QUrl &url);
37 void downloadDefinitionFinished(QNetworkReply *reply);
38 void checkDone();
39};
40
41void DefinitionDownloaderPrivate::definitionListDownloadFinished(QNetworkReply *reply)
42{
43 const auto networkError = reply->error();
44 if (networkError != QNetworkReply::NoError) {
45 qCWarning(Log) << networkError;
46 Q_EMIT q->done(); // TODO return error
47 return;
48 }
49
50 QXmlStreamReader parser(reply);
51 while (!parser.atEnd()) {
52 switch (parser.readNext()) {
53 case QXmlStreamReader::StartElement:
54 if (parser.name() == QLatin1String("Definition")) {
55 updateDefinition(parser);
56 }
57 break;
58 default:
59 break;
60 }
61 }
62
63 if (pendingDownloads == 0) {
64 Q_EMIT q->informationMessage(msg: QObject::tr(s: "All syntax definitions are up-to-date."));
65 }
66 checkDone();
67}
68
69void DefinitionDownloaderPrivate::updateDefinition(QXmlStreamReader &parser)
70{
71 const auto name = parser.attributes().value(qualifiedName: QLatin1String("name"));
72 if (name.isEmpty()) {
73 return;
74 }
75
76 auto localDef = repo->definitionForName(defName: name.toString());
77 if (!localDef.isValid()) {
78 Q_EMIT q->informationMessage(msg: QObject::tr(s: "Downloading new syntax definition for '%1'...").arg(a: name));
79 downloadDefinition(url: QUrl(parser.attributes().value(qualifiedName: QLatin1String("url")).toString()));
80 return;
81 }
82
83 const auto version = parser.attributes().value(qualifiedName: QLatin1String("version"));
84 if (localDef.version() < version.toFloat()) {
85 Q_EMIT q->informationMessage(msg: QObject::tr(s: "Updating syntax definition for '%1' to version %2...").arg(args: name, args: version));
86 downloadDefinition(url: QUrl(parser.attributes().value(qualifiedName: QLatin1String("url")).toString()));
87 }
88}
89
90void DefinitionDownloaderPrivate::downloadDefinition(const QUrl &downloadUrl)
91{
92 if (!downloadUrl.isValid()) {
93 return;
94 }
95 auto url = downloadUrl;
96 if (url.scheme() == QLatin1String("http")) {
97 url.setScheme(QStringLiteral("https"));
98 }
99
100 QNetworkRequest req(url);
101 auto reply = nam->get(request: req);
102 QObject::connect(sender: reply, signal: &QNetworkReply::finished, context: q, slot: [this, reply]() {
103 downloadDefinitionFinished(reply);
104 });
105 ++pendingDownloads;
106 needsReload = true;
107}
108
109void DefinitionDownloaderPrivate::downloadDefinitionFinished(QNetworkReply *reply)
110{
111 --pendingDownloads;
112
113 const auto networkError = reply->error();
114 if (networkError != QNetworkReply::NoError) {
115 qCWarning(Log) << "Failed to download definition file" << reply->url() << networkError;
116 checkDone();
117 return;
118 }
119
120 // handle redirects
121 // needs to be done manually, download server redirects to unsafe http links
122 const auto redirectUrl = reply->attribute(code: QNetworkRequest::RedirectionTargetAttribute).toUrl();
123 if (!redirectUrl.isEmpty()) {
124 downloadDefinition(downloadUrl: reply->url().resolved(relative: redirectUrl));
125 checkDone();
126 return;
127 }
128
129 QFile file(downloadLocation + QLatin1Char('/') + reply->url().fileName());
130 if (!file.open(flags: QFile::WriteOnly)) {
131 qCWarning(Log) << "Failed to open" << file.fileName() << file.error();
132 } else {
133 file.write(data: reply->readAll());
134 }
135 checkDone();
136}
137
138void DefinitionDownloaderPrivate::checkDone()
139{
140 if (pendingDownloads == 0) {
141 if (needsReload) {
142 repo->reload();
143 }
144
145 Q_EMIT QTimer::singleShot(interval: 0, receiver: q, slot: &DefinitionDownloader::done);
146 }
147}
148
149DefinitionDownloader::DefinitionDownloader(Repository *repo, QObject *parent)
150 : QObject(parent)
151 , d(new DefinitionDownloaderPrivate())
152{
153 Q_ASSERT(repo);
154
155 d->q = this;
156 d->repo = repo;
157 d->nam = new QNetworkAccessManager(this);
158 d->pendingDownloads = 0;
159 d->needsReload = false;
160
161 d->downloadLocation = QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation) + QStringLiteral("/org.kde.syntax-highlighting/syntax");
162 QDir().mkpath(dirPath: d->downloadLocation);
163 Q_ASSERT(QFile::exists(d->downloadLocation));
164}
165
166DefinitionDownloader::~DefinitionDownloader()
167{
168}
169
170void DefinitionDownloader::start()
171{
172 const QString url = QLatin1String("https://www.kate-editor.org/syntax/update-") + QString::number(KSYNTAXHIGHLIGHTING_VERSION_MAJOR) + QLatin1Char('.')
173 + QString::number(KSYNTAXHIGHLIGHTING_VERSION_MINOR) + QLatin1String(".xml");
174 auto req = QNetworkRequest(QUrl(url));
175 req.setAttribute(code: QNetworkRequest::RedirectPolicyAttribute, value: QNetworkRequest::NoLessSafeRedirectPolicy);
176 auto reply = d->nam->get(request: req);
177 QObject::connect(sender: reply, signal: &QNetworkReply::finished, context: this, slot: [this, reply]() {
178 d->definitionListDownloadFinished(reply);
179 });
180}
181
182#include "moc_definitiondownloader.cpp"
183

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of syntax-highlighting/src/lib/definitiondownloader.cpp