Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qgeofiletilecacheosm.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:filesystem-manipulation
4
6#include <QtLocation/private/qgeotilespec_p.h>
7#include <QDir>
8#include <QDirIterator>
9#include <QPair>
10#include <QDateTime>
11
13
14QGeoFileTileCacheOsm::QGeoFileTileCacheOsm(const QList<QGeoTileProviderOsm *> &providers,
15 const QString &offlineDirectory,
16 const QString &directory, QObject *parent)
17 : QGeoFileTileCache(directory, parent),
18 m_offlineDirectory(offlineDirectory),
19 m_offlineData(false),
20 m_providers(providers)
21{
22 m_highDpi.resize(providers.size());
23 if (!offlineDirectory.isEmpty()) {
24 m_offlineDirectory = QDir(offlineDirectory);
25 if (m_offlineDirectory.exists())
26 m_offlineData = true;
27 }
28 for (int i = 0; i < providers.size(); i++) {
29 providers[i]->setParent(this);
30 m_highDpi[i] = providers[i]->isHighDpi();
31 connect(providers[i], &QGeoTileProviderOsm::resolutionFinished, this, &QGeoFileTileCacheOsm::onProviderResolutionFinished);
32 connect(providers[i], &QGeoTileProviderOsm::resolutionError, this, &QGeoFileTileCacheOsm::onProviderResolutionFinished);
33 }
34}
35
39
41{
42 QSharedPointer<QGeoTileTexture> tt = getFromMemory(spec);
43 if (tt)
44 return tt;
45 if ((tt = getFromOfflineStorage(spec)))
46 return tt;
47 return getFromDisk(spec);
48}
49
50void QGeoFileTileCacheOsm::onProviderResolutionFinished(const QGeoTileProviderOsm *provider)
51{
53 Q_UNUSED(provider);
54 for (int i = 0; i < m_providers.size(); i++) {
55 if (m_providers[i]->isHighDpi() != m_highDpi[i]) { // e.g., HiDpi was requested but only LoDpi is available
56 int mapId = m_providers[i]->mapType().mapId();
57 m_highDpi[i] = m_providers[i]->isHighDpi();
58
59 // reload cache for mapId i
60 dropTiles(mapId);
61 loadTiles(mapId);
62
63 // send signal to clear scene in all maps created through this provider that use the reloaded tiles
64 emit mapDataUpdated(mapId);
65 }
66 }
67}
68
69// On resolution error the provider is removed.
70// This happens ONLY if there is no enabled hardcoded fallback for the mapId.
71// Hardcoded fallbacks also have a timestamp, that can get updated with Qt releases.
72void QGeoFileTileCacheOsm::onProviderResolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error)
73{
74 Q_UNUSED(error);
75 clearObsoleteTiles(provider); // this still removes tiles who happen to be older than qgeotileproviderosm.cpp defaultTs
76}
77
78// init() is always called before the provider resolution starts
80{
81 if (directory_.isEmpty())
82 directory_ = baseLocationCacheDirectory();
83 QDir::root().mkpath(directory_);
84
85 // find max mapId
86 int max = 0;
87 for (auto p: m_providers)
88 if (p->mapType().mapId() > max)
89 max = p->mapType().mapId();
90 // Create a mapId to maxTimestamp LUT..
91 m_maxMapIdTimestamps.resize(max+1); // initializes to invalid QDateTime
92
93 // .. by finding the newest file in each tileset (tileset = mapId).
94 QDir dir(directory_);
95 QStringList formats;
96 formats << QLatin1String("*.*");
97 QStringList files = dir.entryList(formats, QDir::Files);
98
99 for (const QString &tileFileName : files) {
100 QGeoTileSpec spec = filenameToTileSpec(tileFileName);
101 if (spec.zoom() == -1)
102 continue;
103 QFileInfo fi(dir.filePath(tileFileName));
104 if (fi.lastModified() > m_maxMapIdTimestamps[spec.mapId()])
105 m_maxMapIdTimestamps[spec.mapId()] = fi.lastModified();
106 }
107
108 // Base class ::init()
109 QGeoFileTileCache::init();
110
111 for (QGeoTileProviderOsm * p: m_providers)
112 clearObsoleteTiles(p);
113}
114
116{
117 if (!m_offlineData)
118 return QSharedPointer<QGeoTileTexture>();
119
120 int providerId = spec.mapId() - 1;
121 if (providerId < 0 || providerId >= m_providers.size())
122 return QSharedPointer<QGeoTileTexture>();
123
124 const QString fileName = tileSpecToFilename(spec, QStringLiteral("*"), providerId);
125 QStringList validTiles = m_offlineDirectory.entryList({fileName});
126 if (!validTiles.size())
127 return QSharedPointer<QGeoTileTexture>();
128
129 QFile file(m_offlineDirectory.absoluteFilePath(validTiles.first()));
130 if (!file.open(QIODevice::ReadOnly))
131 return QSharedPointer<QGeoTileTexture>();
132 QByteArray bytes = file.readAll();
133 file.close();
134
135 QImage image;
136 if (!image.loadFromData(bytes)) {
137 handleError(spec, QLatin1String("Problem with tile image"));
138 return QSharedPointer<QGeoTileTexture>();
139 }
140
141 addToMemoryCache(spec, bytes, QString());
142 return addToTextureCache(spec, image);
143}
144
146{
147 QList<QGeoTileSpec> keys;
148 keys = textureCache_.keys();
149 for (const QGeoTileSpec &k : keys)
150 if (k.mapId() == mapId)
151 textureCache_.remove(k);
152
153 keys = memoryCache_.keys();
154 for (const QGeoTileSpec &k : keys)
155 if (k.mapId() == mapId)
156 memoryCache_.remove(k);
157
158 keys = diskCache_.keys();
159 for (const QGeoTileSpec &k : keys)
160 if (k.mapId() == mapId)
161 diskCache_.remove(k);
162}
163
165{
166 QStringList formats;
167 formats << QLatin1String("*.*");
168
169 QDir dir(directory_);
170 QStringList files = dir.entryList(formats, QDir::Files);
171
172 for (int i = 0; i < files.size(); ++i) {
173 QGeoTileSpec spec = filenameToTileSpec(files.at(i));
174 if (spec.zoom() == -1 || spec.mapId() != mapId)
175 continue;
176 QString filename = dir.filePath(files.at(i));
177 addToDiskCache(spec, filename);
178 }
179}
180
181QString QGeoFileTileCacheOsm::tileSpecToFilename(const QGeoTileSpec &spec, const QString &format, const QString &directory) const
182{
183 int providerId = spec.mapId() - 1;
184 if (providerId < 0 || providerId >= m_providers.size())
185 return QString();
186
187 QDir dir = QDir(directory);
188 return dir.filePath(tileSpecToFilename(spec, format, providerId));
189}
190
191QString QGeoFileTileCacheOsm::tileSpecToFilename(const QGeoTileSpec &spec, const QString &format, int providerId) const
192{
193 QString filename = spec.plugin();
194 filename += QLatin1String("-");
195 filename += (m_providers[providerId]->isHighDpi()) ? QLatin1Char('h') : QLatin1Char('l');
196 filename += QLatin1String("-");
197 filename += QString::number(spec.mapId());
198 filename += QLatin1String("-");
199 filename += QString::number(spec.zoom());
200 filename += QLatin1String("-");
201 filename += QString::number(spec.x());
202 filename += QLatin1String("-");
203 filename += QString::number(spec.y());
204
205 //Append version if real version number to ensure backwards compatibility and eviction of old tiles
206 if (spec.version() != -1) {
207 filename += QLatin1String("-");
208 filename += QString::number(spec.version());
209 }
210
211 filename += QLatin1String(".");
212 filename += format;
213 return filename;
214}
215
217{
218 QGeoTileSpec emptySpec;
219
220 QStringList parts = filename.split('.');
221
222 if (parts.length() != 2)
223 return emptySpec;
224
225 QString name = parts.at(0);
226 QStringList fields = name.split('-');
227
228 int length = fields.length();
229 if (length != 6 && length != 7)
230 return emptySpec;
231
232 QList<int> numbers;
233
234 bool ok = false;
235 for (int i = 2; i < length; ++i) {
236 ok = false;
237 int value = fields.at(i).toInt(&ok);
238 if (!ok)
239 return emptySpec;
240 numbers.append(value);
241 }
242
243 if (numbers.at(0) > m_providers.size())
244 return emptySpec;
245
246 bool highDpi = m_providers[numbers.at(0) - 1]->isHighDpi();
247 if (highDpi && fields.at(1) != QLatin1Char('h'))
248 return emptySpec;
249 else if (!highDpi && fields.at(1) != QLatin1Char('l'))
250 return emptySpec;
251
252 //File name without version, append default
253 if (numbers.length() < 5)
254 numbers.append(-1);
255
256 return QGeoTileSpec(fields.at(0),
257 numbers.at(0),
258 numbers.at(1),
259 numbers.at(2),
260 numbers.at(3),
261 numbers.at(4));
262}
263
265{
266 // process initialized providers, and connect the others
267
268 if (p->isResolved()) {
269 if (m_maxMapIdTimestamps[p->mapType().mapId()].isValid() && // there are tiles in the cache
270 p->timestamp() > m_maxMapIdTimestamps[p->mapType().mapId()]) { // and they are older than the provider
271 qInfo() << "provider for " << p->mapType().name() << " timestamp: " << p->timestamp()
272 << " -- data last modified: " << m_maxMapIdTimestamps[p->mapType().mapId()] << ". Clearing.";
273 clearMapId(p->mapType().mapId());
274 m_maxMapIdTimestamps[p->mapType().mapId()] = p->timestamp(); // don't do it again.
275 }
276 } else {
277 connect(p, &QGeoTileProviderOsm::resolutionFinished,
278 this, &QGeoFileTileCacheOsm::onProviderResolutionFinished);
279#if 0 // If resolution fails, better not try to remove anything. Beside, on error, resolutionFinished is also emitted.
280 connect(p, &QGeoTileProviderOsm::resolutionError,
281 this, &QGeoFileTileCacheOsm::onProviderResolutionError);
282#endif
283 }
284}
285
286QT_END_NAMESPACE
void onProviderResolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error)
QSharedPointer< QGeoTileTexture > getFromOfflineStorage(const QGeoTileSpec &spec)
QString tileSpecToFilename(const QGeoTileSpec &spec, const QString &format, const QString &directory) const override
QGeoTileSpec filenameToTileSpec(const QString &filename) const override
QString tileSpecToFilename(const QGeoTileSpec &spec, const QString &format, int providerId) const
QSharedPointer< QGeoTileTexture > get(const QGeoTileSpec &spec) override
void clearObsoleteTiles(const QGeoTileProviderOsm *p)
Combined button and popup list for selecting options.