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 | |
22 | using namespace KSyntaxHighlighting; |
23 | |
24 | class KSyntaxHighlighting::DefinitionDownloaderPrivate |
25 | { |
26 | public: |
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 | |
41 | void 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 | |
69 | void 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 | |
90 | void 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 | |
109 | void 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 | |
138 | void 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 | |
149 | DefinitionDownloader::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 | |
166 | DefinitionDownloader::~DefinitionDownloader() |
167 | { |
168 | } |
169 | |
170 | void 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 | |