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;
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 mkpath(const QString &dirPath) const const
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 Mon Nov 18 2024 12:17:55 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.