1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the QtLocation module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL3$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include "qgeofiletilecacheosm.h"
38#include <QtLocation/private/qgeotilespec_p.h>
39#include <QDir>
40#include <QDirIterator>
41#include <QPair>
42#include <QDateTime>
43#include <QtConcurrent>
44#include <QThread>
45
46QT_BEGIN_NAMESPACE
47
48QGeoFileTileCacheOsm::QGeoFileTileCacheOsm(const QVector<QGeoTileProviderOsm *> &providers,
49 const QString &offlineDirectory,
50 const QString &directory,
51 QObject *parent)
52: QGeoFileTileCache(directory, parent), m_offlineDirectory(offlineDirectory), m_offlineData(false), m_providers(providers)
53{
54 m_highDpi.resize(asize: providers.size());
55 if (!offlineDirectory.isEmpty()) {
56 m_offlineDirectory = QDir(offlineDirectory);
57 if (m_offlineDirectory.exists())
58 m_offlineData = true;
59 }
60 for (int i = 0; i < providers.size(); i++) {
61 providers[i]->setParent(this);
62 m_highDpi[i] = providers[i]->isHighDpi();
63 connect(sender: providers[i], signal: &QGeoTileProviderOsm::resolutionFinished, receiver: this, slot: &QGeoFileTileCacheOsm::onProviderResolutionFinished);
64 connect(sender: providers[i], signal: &QGeoTileProviderOsm::resolutionError, receiver: this, slot: &QGeoFileTileCacheOsm::onProviderResolutionFinished);
65 }
66}
67
68QGeoFileTileCacheOsm::~QGeoFileTileCacheOsm()
69{
70}
71
72QSharedPointer<QGeoTileTexture> QGeoFileTileCacheOsm::get(const QGeoTileSpec &spec)
73{
74 QSharedPointer<QGeoTileTexture> tt = getFromMemory(spec);
75 if (tt)
76 return tt;
77 if ((tt = getFromOfflineStorage(spec)))
78 return tt;
79 return getFromDisk(spec);
80}
81
82void QGeoFileTileCacheOsm::onProviderResolutionFinished(const QGeoTileProviderOsm *provider)
83{
84 clearObsoleteTiles(p: provider);
85 Q_UNUSED(provider);
86 for (int i = 0; i < m_providers.size(); i++) {
87 if (m_providers[i]->isHighDpi() != m_highDpi[i]) { // e.g., HiDpi was requested but only LoDpi is available
88 int mapId = m_providers[i]->mapType().mapId();
89 m_highDpi[i] = m_providers[i]->isHighDpi();
90
91 // reload cache for mapId i
92 dropTiles(mapId);
93 loadTiles(mapId);
94
95 // send signal to clear scene in all maps created through this provider that use the reloaded tiles
96 emit mapDataUpdated(mapId);
97 }
98 }
99}
100
101// On resolution error the provider is removed.
102// This happens ONLY if there is no enabled hardcoded fallback for the mapId.
103// Hardcoded fallbacks also have a timestamp, that can get updated with Qt releases.
104void QGeoFileTileCacheOsm::onProviderResolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error)
105{
106 Q_UNUSED(error);
107 clearObsoleteTiles(p: provider); // this still removes tiles who happen to be older than qgeotileproviderosm.cpp defaultTs
108}
109
110// init() is always called before the provider resolution starts
111void QGeoFileTileCacheOsm::init()
112{
113 if (directory_.isEmpty())
114 directory_ = baseLocationCacheDirectory();
115 QDir::root().mkpath(dirPath: directory_);
116
117 // find max mapId
118 int max = 0;
119 for (auto p: m_providers)
120 if (p->mapType().mapId() > max)
121 max = p->mapType().mapId();
122 // Create a mapId to maxTimestamp LUT..
123 m_maxMapIdTimestamps.resize(asize: max+1); // initializes to invalid QDateTime
124
125 // .. by finding the newest file in each tileset (tileset = mapId).
126 QDir dir(directory_);
127 QStringList formats;
128 formats << QLatin1String("*.*");
129 QStringList files = dir.entryList(nameFilters: formats, filters: QDir::Files);
130
131 for (const QString &tileFileName : files) {
132 QGeoTileSpec spec = filenameToTileSpec(filename: tileFileName);
133 if (spec.zoom() == -1)
134 continue;
135 QFileInfo fi(dir.filePath(fileName: tileFileName));
136 if (fi.lastModified() > m_maxMapIdTimestamps[spec.mapId()])
137 m_maxMapIdTimestamps[spec.mapId()] = fi.lastModified();
138 }
139
140 // Base class ::init()
141 QGeoFileTileCache::init();
142
143 for (QGeoTileProviderOsm * p: m_providers)
144 clearObsoleteTiles(p);
145}
146
147QSharedPointer<QGeoTileTexture> QGeoFileTileCacheOsm::getFromOfflineStorage(const QGeoTileSpec &spec)
148{
149 if (!m_offlineData)
150 return QSharedPointer<QGeoTileTexture>();
151
152 int providerId = spec.mapId() - 1;
153 if (providerId < 0 || providerId >= m_providers.size())
154 return QSharedPointer<QGeoTileTexture>();
155
156 const QString fileName = tileSpecToFilename(spec, QStringLiteral("*"), providerId);
157 QStringList validTiles = m_offlineDirectory.entryList(nameFilters: {fileName});
158 if (!validTiles.size())
159 return QSharedPointer<QGeoTileTexture>();
160
161 QFile file(m_offlineDirectory.absoluteFilePath(fileName: validTiles.first()));
162 if (!file.open(flags: QIODevice::ReadOnly))
163 return QSharedPointer<QGeoTileTexture>();
164 QByteArray bytes = file.readAll();
165 file.close();
166
167 QImage image;
168 if (!image.loadFromData(data: bytes)) {
169 handleError(spec, errorString: QLatin1String("Problem with tile image"));
170 return QSharedPointer<QGeoTileTexture>(0);
171 }
172
173 addToMemoryCache(spec, bytes, format: QString());
174 return addToTextureCache(spec, image);
175}
176
177void QGeoFileTileCacheOsm::dropTiles(int mapId)
178{
179 QList<QGeoTileSpec> keys;
180 keys = textureCache_.keys();
181 for (const QGeoTileSpec &k : keys)
182 if (k.mapId() == mapId)
183 textureCache_.remove(key: k);
184
185 keys = memoryCache_.keys();
186 for (const QGeoTileSpec &k : keys)
187 if (k.mapId() == mapId)
188 memoryCache_.remove(key: k);
189
190 keys = diskCache_.keys();
191 for (const QGeoTileSpec &k : keys)
192 if (k.mapId() == mapId)
193 diskCache_.remove(key: k);
194}
195
196void QGeoFileTileCacheOsm::loadTiles(int mapId)
197{
198 QStringList formats;
199 formats << QLatin1String("*.*");
200
201 QDir dir(directory_);
202 QStringList files = dir.entryList(nameFilters: formats, filters: QDir::Files);
203
204 for (int i = 0; i < files.size(); ++i) {
205 QGeoTileSpec spec = filenameToTileSpec(filename: files.at(i));
206 if (spec.zoom() == -1 || spec.mapId() != mapId)
207 continue;
208 QString filename = dir.filePath(fileName: files.at(i));
209 addToDiskCache(spec, filename);
210 }
211}
212
213QString QGeoFileTileCacheOsm::tileSpecToFilename(const QGeoTileSpec &spec, const QString &format, const QString &directory) const
214{
215 int providerId = spec.mapId() - 1;
216 if (providerId < 0 || providerId >= m_providers.size())
217 return QString();
218
219 QDir dir = QDir(directory);
220 return dir.filePath(fileName: tileSpecToFilename(spec, format, providerId));
221}
222
223QString QGeoFileTileCacheOsm::tileSpecToFilename(const QGeoTileSpec &spec, const QString &format, int providerId) const
224{
225 QString filename = spec.plugin();
226 filename += QLatin1String("-");
227 filename += (m_providers[providerId]->isHighDpi()) ? QLatin1Char('h') : QLatin1Char('l');
228 filename += QLatin1String("-");
229 filename += QString::number(spec.mapId());
230 filename += QLatin1String("-");
231 filename += QString::number(spec.zoom());
232 filename += QLatin1String("-");
233 filename += QString::number(spec.x());
234 filename += QLatin1String("-");
235 filename += QString::number(spec.y());
236
237 //Append version if real version number to ensure backwards compatibility and eviction of old tiles
238 if (spec.version() != -1) {
239 filename += QLatin1String("-");
240 filename += QString::number(spec.version());
241 }
242
243 filename += QLatin1String(".");
244 filename += format;
245 return filename;
246}
247
248QGeoTileSpec QGeoFileTileCacheOsm::filenameToTileSpec(const QString &filename) const
249{
250 QGeoTileSpec emptySpec;
251
252 QStringList parts = filename.split(sep: '.');
253
254 if (parts.length() != 2)
255 return emptySpec;
256
257 QString name = parts.at(i: 0);
258 QStringList fields = name.split(sep: '-');
259
260 int length = fields.length();
261 if (length != 6 && length != 7)
262 return emptySpec;
263
264 QList<int> numbers;
265
266 bool ok = false;
267 for (int i = 2; i < length; ++i) {
268 ok = false;
269 int value = fields.at(i).toInt(ok: &ok);
270 if (!ok)
271 return emptySpec;
272 numbers.append(t: value);
273 }
274
275 if (numbers.at(i: 0) > m_providers.size())
276 return emptySpec;
277
278 bool highDpi = m_providers[numbers.at(i: 0) - 1]->isHighDpi();
279 if (highDpi && fields.at(i: 1) != QLatin1Char('h'))
280 return emptySpec;
281 else if (!highDpi && fields.at(i: 1) != QLatin1Char('l'))
282 return emptySpec;
283
284 //File name without version, append default
285 if (numbers.length() < 5)
286 numbers.append(t: -1);
287
288 return QGeoTileSpec(fields.at(i: 0),
289 numbers.at(i: 0),
290 numbers.at(i: 1),
291 numbers.at(i: 2),
292 numbers.at(i: 3),
293 numbers.at(i: 4));
294}
295
296void QGeoFileTileCacheOsm::clearObsoleteTiles(const QGeoTileProviderOsm *p)
297{
298 // process initialized providers, and connect the others
299
300 if (p->isResolved()) {
301 if (m_maxMapIdTimestamps[p->mapType().mapId()].isValid() && // there are tiles in the cache
302 p->timestamp() > m_maxMapIdTimestamps[p->mapType().mapId()]) { // and they are older than the provider
303 qInfo() << "provider for " << p->mapType().name() << " timestamp: " << p->timestamp()
304 << " -- data last modified: " << m_maxMapIdTimestamps[p->mapType().mapId()] << ". Clearing.";
305 clearMapId(mapId: p->mapType().mapId());
306 m_maxMapIdTimestamps[p->mapType().mapId()] = p->timestamp(); // don't do it again.
307 }
308 } else {
309 connect(sender: p, signal: &QGeoTileProviderOsm::resolutionFinished,
310 receiver: this, slot: &QGeoFileTileCacheOsm::onProviderResolutionFinished);
311#if 0 // If resolution fails, better not try to remove anything. Beside, on error, resolutionFinished is also emitted.
312 connect(p, &QGeoTileProviderOsm::resolutionError,
313 this, &QGeoFileTileCacheOsm::onProviderResolutionError);
314#endif
315 }
316}
317
318QT_END_NAMESPACE
319

source code of qtlocation/src/plugins/geoservices/osm/qgeofiletilecacheosm.cpp