File indexing completed on 2024-05-12 15:50:04

0001 /*
0002     SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: MIT
0005 */
0006 
0007 #include "definitiondownloader.h"
0008 #include "definition.h"
0009 #include "ksyntaxhighlighting_logging.h"
0010 #include "ksyntaxhighlighting_version.h"
0011 #include "repository.h"
0012 
0013 #include <QDir>
0014 #include <QFile>
0015 #include <QNetworkAccessManager>
0016 #include <QNetworkReply>
0017 #include <QNetworkRequest>
0018 #include <QStandardPaths>
0019 #include <QTimer>
0020 #include <QXmlStreamReader>
0021 
0022 using namespace KSyntaxHighlighting;
0023 
0024 class KSyntaxHighlighting::DefinitionDownloaderPrivate
0025 {
0026 public:
0027     DefinitionDownloader *q;
0028     Repository *repo;
0029     QNetworkAccessManager *nam;
0030     QString downloadLocation;
0031     int pendingDownloads;
0032     bool needsReload;
0033 
0034     void definitionListDownloadFinished(QNetworkReply *reply);
0035     void updateDefinition(QXmlStreamReader &parser);
0036     void downloadDefinition(const QUrl &url);
0037     void downloadDefinitionFinished(QNetworkReply *reply);
0038     void checkDone();
0039 };
0040 
0041 void DefinitionDownloaderPrivate::definitionListDownloadFinished(QNetworkReply *reply)
0042 {
0043     const auto networkError = reply->error();
0044     if (networkError != QNetworkReply::NoError) {
0045         qCWarning(Log) << networkError;
0046         Q_EMIT q->done(); // TODO return error
0047         return;
0048     }
0049 
0050     QXmlStreamReader parser(reply);
0051     while (!parser.atEnd()) {
0052         switch (parser.readNext()) {
0053         case QXmlStreamReader::StartElement:
0054             if (parser.name() == QLatin1String("Definition")) {
0055                 updateDefinition(parser);
0056             }
0057             break;
0058         default:
0059             break;
0060         }
0061     }
0062 
0063     if (pendingDownloads == 0) {
0064         Q_EMIT q->informationMessage(QObject::tr("All syntax definitions are up-to-date."));
0065     }
0066     checkDone();
0067 }
0068 
0069 void DefinitionDownloaderPrivate::updateDefinition(QXmlStreamReader &parser)
0070 {
0071     const auto name = parser.attributes().value(QLatin1String("name"));
0072     if (name.isEmpty()) {
0073         return;
0074     }
0075 
0076     auto localDef = repo->definitionForName(name.toString());
0077     if (!localDef.isValid()) {
0078         Q_EMIT q->informationMessage(QObject::tr("Downloading new syntax definition for '%1'...").arg(name));
0079         downloadDefinition(QUrl(parser.attributes().value(QLatin1String("url")).toString()));
0080         return;
0081     }
0082 
0083     const auto version = parser.attributes().value(QLatin1String("version"));
0084     if (localDef.version() < version.toFloat()) {
0085         Q_EMIT q->informationMessage(QObject::tr("Updating syntax definition for '%1' to version %2...").arg(name, version));
0086         downloadDefinition(QUrl(parser.attributes().value(QLatin1String("url")).toString()));
0087     }
0088 }
0089 
0090 void DefinitionDownloaderPrivate::downloadDefinition(const QUrl &downloadUrl)
0091 {
0092     if (!downloadUrl.isValid()) {
0093         return;
0094     }
0095     auto url = downloadUrl;
0096     if (url.scheme() == QLatin1String("http")) {
0097         url.setScheme(QStringLiteral("https"));
0098     }
0099 
0100     QNetworkRequest req(url);
0101     auto reply = nam->get(req);
0102     QObject::connect(reply, &QNetworkReply::finished, q, [this, reply]() {
0103         downloadDefinitionFinished(reply);
0104     });
0105     ++pendingDownloads;
0106     needsReload = true;
0107 }
0108 
0109 void DefinitionDownloaderPrivate::downloadDefinitionFinished(QNetworkReply *reply)
0110 {
0111     --pendingDownloads;
0112 
0113     const auto networkError = reply->error();
0114     if (networkError != QNetworkReply::NoError) {
0115         qCWarning(Log) << "Failed to download definition file" << reply->url() << networkError;
0116         checkDone();
0117         return;
0118     }
0119 
0120     // handle redirects
0121     // needs to be done manually, download server redirects to unsafe http links
0122     const auto redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
0123     if (!redirectUrl.isEmpty()) {
0124         downloadDefinition(reply->url().resolved(redirectUrl));
0125         checkDone();
0126         return;
0127     }
0128 
0129     QFile file(downloadLocation + QLatin1Char('/') + reply->url().fileName());
0130     if (!file.open(QFile::WriteOnly)) {
0131         qCWarning(Log) << "Failed to open" << file.fileName() << file.error();
0132     } else {
0133         file.write(reply->readAll());
0134     }
0135     checkDone();
0136 }
0137 
0138 void DefinitionDownloaderPrivate::checkDone()
0139 {
0140     if (pendingDownloads == 0) {
0141         if (needsReload) {
0142             repo->reload();
0143         }
0144 
0145         Q_EMIT QTimer::singleShot(0, q, &DefinitionDownloader::done);
0146     }
0147 }
0148 
0149 DefinitionDownloader::DefinitionDownloader(Repository *repo, QObject *parent)
0150     : QObject(parent)
0151     , d(new DefinitionDownloaderPrivate())
0152 {
0153     Q_ASSERT(repo);
0154 
0155     d->q = this;
0156     d->repo = repo;
0157     d->nam = new QNetworkAccessManager(this);
0158     d->pendingDownloads = 0;
0159     d->needsReload = false;
0160 
0161     d->downloadLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/org.kde.syntax-highlighting/syntax");
0162     QDir().mkpath(d->downloadLocation);
0163     Q_ASSERT(QFile::exists(d->downloadLocation));
0164 }
0165 
0166 DefinitionDownloader::~DefinitionDownloader()
0167 {
0168 }
0169 
0170 void DefinitionDownloader::start()
0171 {
0172     const QString url = QLatin1String("https://www.kate-editor.org/syntax/update-") + QString::number(SyntaxHighlighting_VERSION_MAJOR) + QLatin1Char('.')
0173         + QString::number(SyntaxHighlighting_VERSION_MINOR) + QLatin1String(".xml");
0174     auto req = QNetworkRequest(QUrl(url));
0175     req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
0176     auto reply = d->nam->get(req);
0177     QObject::connect(reply, &QNetworkReply::finished, this, [=]() {
0178         d->definitionListDownloadFinished(reply);
0179     });
0180 }
0181 
0182 #include "moc_definitiondownloader.cpp"