File indexing completed on 2024-05-12 04:02:18
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(KSYNTAXHIGHLIGHTING_VERSION_MAJOR) + QLatin1Char('.') 0173 + QString::number(KSYNTAXHIGHLIGHTING_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, [this, reply]() { 0178 d->definitionListDownloadFinished(reply); 0179 }); 0180 } 0181 0182 #include "moc_definitiondownloader.cpp"