KOSMIndoorMap

mapcssloader.cpp
1/*
2 SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "mapcssloader.h"
7#include "mapcssparser.h"
8#include "mapcssstyle.h"
9#include "logging.h"
10#include "network/useragent_p.h"
11
12#include <QCryptographicHash>
13#include <QDir>
14#include <QDirIterator>
15#include <QFileInfo>
16#include <QGuiApplication>
17#include <QNetworkAccessManager>
18#include <QNetworkReply>
19#include <QNetworkRequest>
20#include <QPalette>
21#include <QStandardPaths>
22
23using namespace Qt::Literals::StringLiterals;
24using namespace KOSMIndoorMap;
25
26class KOSMIndoorMap::MapCSSLoaderPrivate
27{
28public:
29 QUrl m_styleUrl;
30 MapCSSStyle m_style;
31 MapCSSParser::Error m_error = MapCSSParser::SyntaxError;
32 QString m_errorMsg;
33 QSet<QUrl> m_alreadyDownloaded;
34 NetworkAccessManagerFactory m_nam;
35};
36
38 : QObject(parent)
39 , d(std::make_unique<MapCSSLoaderPrivate>())
40{
41 d->m_styleUrl = style;
42 d->m_nam = nam;
43}
44
45MapCSSLoader::~MapCSSLoader() = default;
46
48{
50 d->m_style = p.parse(d->m_styleUrl);
51 d->m_error = p.error();
52 d->m_errorMsg = p.errorMessage();
53
54 if (d->m_error == MapCSSParser::FileNotFoundError) {
55 download(p.url());
56 } else {
58 }
59}
60
62{
63 return std::move(d->m_style);
64}
65
67{
68 return d->m_error != MapCSSParser::NoError;
69}
70
71QString MapCSSLoader::errorMessage() const
72{
73 return d->m_errorMsg;
74}
75
76[[nodiscard]] static QUrl pathToUrl(const QString &path)
77{
78 if (path.startsWith(':'_L1)) {
79 QUrl url;
80 url.setScheme(u"qrc"_s);
81 url.setHost(u""_s);
82 url.setPath(path.mid(1));
83 return url;
84 }
85
86 return QUrl::fromLocalFile(path);
87}
88
89QUrl MapCSSLoader::resolve(const QString &style, const QUrl &baseUrl)
90{
91 if (style.isEmpty() || style == "default"_L1) {
93 return resolve(u"breeze-dark"_s, baseUrl);
94 }
95 return resolve(u"breeze-light"_s, baseUrl);
96 }
97
98 if (style.startsWith("http://"_L1)) {
99 qCWarning(Log) << "not loading MapCSS from insecure HTTP source!" << style;
100 return {};
101 }
102
103 if (style.startsWith("https://"_L1)) {
104 return QUrl(style);
105 }
106
107 QString fileName = style;
108 if (style.startsWith("file:/"_L1) || style.startsWith("qrc:/"_L1)) {
109 fileName = toLocalFile(QUrl(style));
110 }
111
112 QFileInfo fi(fileName);
113 if (fi.isAbsolute()) {
114 return pathToUrl(fi.absoluteFilePath());
115 }
116
117 QUrl resolved;
118 if (!baseUrl.isEmpty()) {
119 resolved = baseUrl.resolved(QUrl(style));
120 if (QFile::exists(toLocalFile(resolved))) {
121 return resolved;
122 }
123 }
124
125#ifndef Q_OS_ANDROID
127#else
129#endif
130 searchPaths.push_back(u":"_s);
131 for (const auto &searchPath : std::as_const(searchPaths)) {
132 QString f = searchPath + "/org.kde.kosmindoormap/assets/css/"_L1 + style + (style.endsWith(".mapcss"_L1) ? ""_L1 : ".mapcss"_L1);
133 if (QFile::exists(f)) {
134 qCDebug(Log) << "resolved stylesheet" << style << "to" << f;
135 return pathToUrl(f);
136 }
137 }
138
139 return resolved;
140}
141
142[[nodiscard]] static QString cacheBasePath()
143{
145}
146
148{
149 if (url.isLocalFile() || url.scheme() == "file"_L1) {
150 return url.toLocalFile();
151 }
152 if (url.scheme() == "qrc"_L1) {
153 return ':'_L1 + url.path();
154 }
155
156 if (url.scheme() == "https"_L1) {
158 }
159
160 return {};
161}
162
164{
165 const auto expireDt = QDateTime::currentDateTimeUtc().addDays(-1);
166 for (QDirIterator it(cacheBasePath(), QDir::Files | QDir::NoSymLinks); it.hasNext();) {
167 it.next();
168 if (it.fileInfo().lastModified() < expireDt) {
169 qCDebug(Log) << "expiring" << it.filePath();
170 QFile::remove(it.filePath());
171 }
172 }
173}
174
175void MapCSSLoader::download(const QUrl &url)
176{
177 // don't try to download the same thing twice, even if we fail due to network issues etc
178 if (!url.isValid() || url.scheme() != "https"_L1 || d->m_alreadyDownloaded.contains(url)) {
180 return;
181 }
182 d->m_alreadyDownloaded.insert(url);
183
184 QNetworkRequest req(url);
185 req.setAttribute(QNetworkRequest::Http2AllowedAttribute, true);
187 req.setHeader(QNetworkRequest::UserAgentHeader, KOSMIndoorMap::userAgent());
188 qCDebug(Log) << "retrieving" << url;
189 auto reply = d->m_nam()->get(req);
190 reply->setParent(this);
191 connect(reply, &QNetworkReply::finished, this, [this, reply, url]() {
192 reply->deleteLater();
193 if (reply->error() != QNetworkReply::NoError) {
194 d->m_errorMsg = reply->errorString();
195 d->m_error = MapCSSParser::NetworkError;
196 Q_EMIT finished();
197 return;
198 }
199
200 QDir().mkpath(cacheBasePath());
201 QFile cacheFile(MapCSSLoader::toLocalFile(url));
202 if (!cacheFile.open(QFile::WriteOnly)) {
203 d->m_errorMsg = cacheFile.errorString();
204 d->m_error = MapCSSParser::FileIOError;
206 return;
207 }
208 cacheFile.write(reply->readAll());
209 cacheFile.close();
210
211 start();
212 });
213}
214
215#include "moc_mapcssloader.cpp"
void start()
Start loading.
static QString toLocalFile(const QUrl &url)
Translate local or remote URL to locally loadable (cache) file.
bool hasError() const
Check whether loading or parsing failed in some way.
MapCSSLoader(const QUrl &style, const NetworkAccessManagerFactory &nam, QObject *parent=nullptr)
Create MapCSS loading/parsing job for style.
static QUrl resolve(const QString &style, const QUrl &baseUrl={})
Resolve style to an absolute URL to load.
static void expire()
Expire locally cached remote MapCSS assets.
MapCSSStyle && takeStyle()
The fully loaded and parsed style.
void finished()
Loading is done, successfully or with an error.
QUrl url() const
URL of the parsed MapCSS style sheet.
A parsed MapCSS style sheet.
Definition mapcssstyle.h:33
OSM-based multi-floor indoor maps for buildings.
std::function< QNetworkAccessManager *()> NetworkAccessManagerFactory
Network access manager factory.
QByteArray toHex(char separator) const const
QCoreApplication * instance()
QByteArray hash(QByteArrayView data, Algorithm method)
QDateTime addDays(qint64 ndays) const const
QDateTime currentDateTimeUtc()
bool hasNext() const const
bool exists() const const
bool remove()
QString absoluteFilePath() const const
bool isAbsolute() const const
QPalette palette()
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
QStringList standardLocations(StandardLocation type)
QString writableLocation(StandardLocation type)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QString mid(qsizetype position, qsizetype n) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toUtf8() const const
QUrl fromLocalFile(const QString &localFile)
bool isEmpty() const const
bool isLocalFile() const const
bool isValid() const const
QString path(ComponentFormattingOptions options) const const
QUrl resolved(const QUrl &relative) const const
QString scheme() const const
void setHost(const QString &host, ParsingMode mode)
void setPath(const QString &path, ParsingMode mode)
void setScheme(const QString &scheme)
QString toLocalFile() const const
QString toString(FormattingOptions options) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:57:46 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.