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
46 QT_BEGIN_NAMESPACE
47
QGeoFileTileCacheOsm(const QVector<QGeoTileProviderOsm * > & providers,const QString & offlineDirectory,const QString & directory,QObject * parent)48 QGeoFileTileCacheOsm::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(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(providers[i], &QGeoTileProviderOsm::resolutionFinished, this, &QGeoFileTileCacheOsm::onProviderResolutionFinished);
64 connect(providers[i], &QGeoTileProviderOsm::resolutionError, this, &QGeoFileTileCacheOsm::onProviderResolutionFinished);
65 }
66 }
67
~QGeoFileTileCacheOsm()68 QGeoFileTileCacheOsm::~QGeoFileTileCacheOsm()
69 {
70 }
71
get(const QGeoTileSpec & spec)72 QSharedPointer<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
onProviderResolutionFinished(const QGeoTileProviderOsm * provider)82 void QGeoFileTileCacheOsm::onProviderResolutionFinished(const QGeoTileProviderOsm *provider)
83 {
84 clearObsoleteTiles(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.
onProviderResolutionError(const QGeoTileProviderOsm * provider,QNetworkReply::NetworkError error)104 void QGeoFileTileCacheOsm::onProviderResolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error)
105 {
106 Q_UNUSED(error);
107 clearObsoleteTiles(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
init()111 void QGeoFileTileCacheOsm::init()
112 {
113 if (directory_.isEmpty())
114 directory_ = baseLocationCacheDirectory();
115 QDir::root().mkpath(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(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(formats, QDir::Files);
130
131 for (const QString &tileFileName : files) {
132 QGeoTileSpec spec = filenameToTileSpec(tileFileName);
133 if (spec.zoom() == -1)
134 continue;
135 QFileInfo fi(dir.filePath(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
getFromOfflineStorage(const QGeoTileSpec & spec)147 QSharedPointer<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({fileName});
158 if (!validTiles.size())
159 return QSharedPointer<QGeoTileTexture>();
160
161 QFile file(m_offlineDirectory.absoluteFilePath(validTiles.first()));
162 if (!file.open(QIODevice::ReadOnly))
163 return QSharedPointer<QGeoTileTexture>();
164 QByteArray bytes = file.readAll();
165 file.close();
166
167 QImage image;
168 if (!image.loadFromData(bytes)) {
169 handleError(spec, QLatin1String("Problem with tile image"));
170 return QSharedPointer<QGeoTileTexture>(0);
171 }
172
173 addToMemoryCache(spec, bytes, QString());
174 return addToTextureCache(spec, image);
175 }
176
dropTiles(int mapId)177 void 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(k);
184
185 keys = memoryCache_.keys();
186 for (const QGeoTileSpec &k : keys)
187 if (k.mapId() == mapId)
188 memoryCache_.remove(k);
189
190 keys = diskCache_.keys();
191 for (const QGeoTileSpec &k : keys)
192 if (k.mapId() == mapId)
193 diskCache_.remove(k);
194 }
195
loadTiles(int mapId)196 void QGeoFileTileCacheOsm::loadTiles(int mapId)
197 {
198 QStringList formats;
199 formats << QLatin1String("*.*");
200
201 QDir dir(directory_);
202 QStringList files = dir.entryList(formats, QDir::Files);
203
204 for (int i = 0; i < files.size(); ++i) {
205 QGeoTileSpec spec = filenameToTileSpec(files.at(i));
206 if (spec.zoom() == -1 || spec.mapId() != mapId)
207 continue;
208 QString filename = dir.filePath(files.at(i));
209 addToDiskCache(spec, filename);
210 }
211 }
212
tileSpecToFilename(const QGeoTileSpec & spec,const QString & format,const QString & directory) const213 QString 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(tileSpecToFilename(spec, format, providerId));
221 }
222
tileSpecToFilename(const QGeoTileSpec & spec,const QString & format,int providerId) const223 QString 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
filenameToTileSpec(const QString & filename) const248 QGeoTileSpec QGeoFileTileCacheOsm::filenameToTileSpec(const QString &filename) const
249 {
250 QGeoTileSpec emptySpec;
251
252 QStringList parts = filename.split('.');
253
254 if (parts.length() != 2)
255 return emptySpec;
256
257 QString name = parts.at(0);
258 QStringList fields = name.split('-');
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);
270 if (!ok)
271 return emptySpec;
272 numbers.append(value);
273 }
274
275 if (numbers.at(0) > m_providers.size())
276 return emptySpec;
277
278 bool highDpi = m_providers[numbers.at(0) - 1]->isHighDpi();
279 if (highDpi && fields.at(1) != QLatin1Char('h'))
280 return emptySpec;
281 else if (!highDpi && fields.at(1) != QLatin1Char('l'))
282 return emptySpec;
283
284 //File name without version, append default
285 if (numbers.length() < 5)
286 numbers.append(-1);
287
288 return QGeoTileSpec(fields.at(0),
289 numbers.at(0),
290 numbers.at(1),
291 numbers.at(2),
292 numbers.at(3),
293 numbers.at(4));
294 }
295
clearObsoleteTiles(const QGeoTileProviderOsm * p)296 void 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(p->mapType().mapId());
306 m_maxMapIdTimestamps[p->mapType().mapId()] = p->timestamp(); // don't do it again.
307 }
308 } else {
309 connect(p, &QGeoTileProviderOsm::resolutionFinished,
310 this, &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
318 QT_END_NAMESPACE
319