blob: 3bb1ea7b5ac3c7c7b2117fe5f9eb079f9abb36db [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtLocation module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL3$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or later as published by the Free
** Software Foundation and appearing in the file LICENSE.GPL included in
** the packaging of this file. Please review the following information to
** ensure the GNU General Public License version 2.0 requirements will be
** met: http://www.gnu.org/licenses/gpl-2.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qgeofiletilecacheosm.h"
#include <QtLocation/private/qgeotilespec_p.h>
#include <QDir>
#include <QDirIterator>
#include <QPair>
#include <QDateTime>
#include <QtConcurrent>
#include <QThread>
QT_BEGIN_NAMESPACE
QGeoFileTileCacheOsm::QGeoFileTileCacheOsm(const QVector<QGeoTileProviderOsm *> &providers,
const QString &offlineDirectory,
const QString &directory,
QObject *parent)
: QGeoFileTileCache(directory, parent), m_offlineDirectory(offlineDirectory), m_offlineData(false), m_providers(providers)
{
m_highDpi.resize(providers.size());
if (!offlineDirectory.isEmpty()) {
m_offlineDirectory = QDir(offlineDirectory);
if (m_offlineDirectory.exists())
m_offlineData = true;
}
for (int i = 0; i < providers.size(); i++) {
providers[i]->setParent(this);
m_highDpi[i] = providers[i]->isHighDpi();
connect(providers[i], &QGeoTileProviderOsm::resolutionFinished, this, &QGeoFileTileCacheOsm::onProviderResolutionFinished);
connect(providers[i], &QGeoTileProviderOsm::resolutionError, this, &QGeoFileTileCacheOsm::onProviderResolutionFinished);
}
}
QGeoFileTileCacheOsm::~QGeoFileTileCacheOsm()
{
}
QSharedPointer<QGeoTileTexture> QGeoFileTileCacheOsm::get(const QGeoTileSpec &spec)
{
QSharedPointer<QGeoTileTexture> tt = getFromMemory(spec);
if (tt)
return tt;
if ((tt = getFromOfflineStorage(spec)))
return tt;
return getFromDisk(spec);
}
void QGeoFileTileCacheOsm::onProviderResolutionFinished(const QGeoTileProviderOsm *provider)
{
clearObsoleteTiles(provider);
Q_UNUSED(provider);
for (int i = 0; i < m_providers.size(); i++) {
if (m_providers[i]->isHighDpi() != m_highDpi[i]) { // e.g., HiDpi was requested but only LoDpi is available
int mapId = m_providers[i]->mapType().mapId();
m_highDpi[i] = m_providers[i]->isHighDpi();
// reload cache for mapId i
dropTiles(mapId);
loadTiles(mapId);
// send signal to clear scene in all maps created through this provider that use the reloaded tiles
emit mapDataUpdated(mapId);
}
}
}
// On resolution error the provider is removed.
// This happens ONLY if there is no enabled hardcoded fallback for the mapId.
// Hardcoded fallbacks also have a timestamp, that can get updated with Qt releases.
void QGeoFileTileCacheOsm::onProviderResolutionError(const QGeoTileProviderOsm *provider, QNetworkReply::NetworkError error)
{
Q_UNUSED(error);
clearObsoleteTiles(provider); // this still removes tiles who happen to be older than qgeotileproviderosm.cpp defaultTs
}
// init() is always called before the provider resolution starts
void QGeoFileTileCacheOsm::init()
{
if (directory_.isEmpty())
directory_ = baseLocationCacheDirectory();
QDir::root().mkpath(directory_);
// find max mapId
int max = 0;
for (auto p: m_providers)
if (p->mapType().mapId() > max)
max = p->mapType().mapId();
// Create a mapId to maxTimestamp LUT..
m_maxMapIdTimestamps.resize(max+1); // initializes to invalid QDateTime
// .. by finding the newest file in each tileset (tileset = mapId).
QDir dir(directory_);
QStringList formats;
formats << QLatin1String("*.*");
QStringList files = dir.entryList(formats, QDir::Files);
for (const QString &tileFileName : files) {
QGeoTileSpec spec = filenameToTileSpec(tileFileName);
if (spec.zoom() == -1)
continue;
QFileInfo fi(dir.filePath(tileFileName));
if (fi.lastModified() > m_maxMapIdTimestamps[spec.mapId()])
m_maxMapIdTimestamps[spec.mapId()] = fi.lastModified();
}
// Base class ::init()
QGeoFileTileCache::init();
for (QGeoTileProviderOsm * p: m_providers)
clearObsoleteTiles(p);
}
QSharedPointer<QGeoTileTexture> QGeoFileTileCacheOsm::getFromOfflineStorage(const QGeoTileSpec &spec)
{
if (!m_offlineData)
return QSharedPointer<QGeoTileTexture>();
int providerId = spec.mapId() - 1;
if (providerId < 0 || providerId >= m_providers.size())
return QSharedPointer<QGeoTileTexture>();
const QString fileName = tileSpecToFilename(spec, QStringLiteral("*"), providerId);
QStringList validTiles = m_offlineDirectory.entryList({fileName});
if (!validTiles.size())
return QSharedPointer<QGeoTileTexture>();
QFile file(m_offlineDirectory.absoluteFilePath(validTiles.first()));
if (!file.open(QIODevice::ReadOnly))
return QSharedPointer<QGeoTileTexture>();
QByteArray bytes = file.readAll();
file.close();
QImage image;
if (!image.loadFromData(bytes)) {
handleError(spec, QLatin1String("Problem with tile image"));
return QSharedPointer<QGeoTileTexture>(0);
}
addToMemoryCache(spec, bytes, QString());
return addToTextureCache(spec, image);
}
void QGeoFileTileCacheOsm::dropTiles(int mapId)
{
QList<QGeoTileSpec> keys;
keys = textureCache_.keys();
for (const QGeoTileSpec &k : keys)
if (k.mapId() == mapId)
textureCache_.remove(k);
keys = memoryCache_.keys();
for (const QGeoTileSpec &k : keys)
if (k.mapId() == mapId)
memoryCache_.remove(k);
keys = diskCache_.keys();
for (const QGeoTileSpec &k : keys)
if (k.mapId() == mapId)
diskCache_.remove(k);
}
void QGeoFileTileCacheOsm::loadTiles(int mapId)
{
QStringList formats;
formats << QLatin1String("*.*");
QDir dir(directory_);
QStringList files = dir.entryList(formats, QDir::Files);
for (int i = 0; i < files.size(); ++i) {
QGeoTileSpec spec = filenameToTileSpec(files.at(i));
if (spec.zoom() == -1 || spec.mapId() != mapId)
continue;
QString filename = dir.filePath(files.at(i));
addToDiskCache(spec, filename);
}
}
QString QGeoFileTileCacheOsm::tileSpecToFilename(const QGeoTileSpec &spec, const QString &format, const QString &directory) const
{
int providerId = spec.mapId() - 1;
if (providerId < 0 || providerId >= m_providers.size())
return QString();
QDir dir = QDir(directory);
return dir.filePath(tileSpecToFilename(spec, format, providerId));
}
QString QGeoFileTileCacheOsm::tileSpecToFilename(const QGeoTileSpec &spec, const QString &format, int providerId) const
{
QString filename = spec.plugin();
filename += QLatin1String("-");
filename += (m_providers[providerId]->isHighDpi()) ? QLatin1Char('h') : QLatin1Char('l');
filename += QLatin1String("-");
filename += QString::number(spec.mapId());
filename += QLatin1String("-");
filename += QString::number(spec.zoom());
filename += QLatin1String("-");
filename += QString::number(spec.x());
filename += QLatin1String("-");
filename += QString::number(spec.y());
//Append version if real version number to ensure backwards compatibility and eviction of old tiles
if (spec.version() != -1) {
filename += QLatin1String("-");
filename += QString::number(spec.version());
}
filename += QLatin1String(".");
filename += format;
return filename;
}
QGeoTileSpec QGeoFileTileCacheOsm::filenameToTileSpec(const QString &filename) const
{
QGeoTileSpec emptySpec;
QStringList parts = filename.split('.');
if (parts.length() != 2)
return emptySpec;
QString name = parts.at(0);
QStringList fields = name.split('-');
int length = fields.length();
if (length != 6 && length != 7)
return emptySpec;
QList<int> numbers;
bool ok = false;
for (int i = 2; i < length; ++i) {
ok = false;
int value = fields.at(i).toInt(&ok);
if (!ok)
return emptySpec;
numbers.append(value);
}
if (numbers.at(0) > m_providers.size())
return emptySpec;
bool highDpi = m_providers[numbers.at(0) - 1]->isHighDpi();
if (highDpi && fields.at(1) != QLatin1Char('h'))
return emptySpec;
else if (!highDpi && fields.at(1) != QLatin1Char('l'))
return emptySpec;
//File name without version, append default
if (numbers.length() < 5)
numbers.append(-1);
return QGeoTileSpec(fields.at(0),
numbers.at(0),
numbers.at(1),
numbers.at(2),
numbers.at(3),
numbers.at(4));
}
void QGeoFileTileCacheOsm::clearObsoleteTiles(const QGeoTileProviderOsm *p)
{
// process initialized providers, and connect the others
if (p->isResolved()) {
if (m_maxMapIdTimestamps[p->mapType().mapId()].isValid() && // there are tiles in the cache
p->timestamp() > m_maxMapIdTimestamps[p->mapType().mapId()]) { // and they are older than the provider
qInfo() << "provider for " << p->mapType().name() << " timestamp: " << p->timestamp()
<< " -- data last modified: " << m_maxMapIdTimestamps[p->mapType().mapId()] << ". Clearing.";
clearMapId(p->mapType().mapId());
m_maxMapIdTimestamps[p->mapType().mapId()] = p->timestamp(); // don't do it again.
}
} else {
connect(p, &QGeoTileProviderOsm::resolutionFinished,
this, &QGeoFileTileCacheOsm::onProviderResolutionFinished);
#if 0 // If resolution fails, better not try to remove anything. Beside, on error, resolutionFinished is also emitted.
connect(p, &QGeoTileProviderOsm::resolutionError,
this, &QGeoFileTileCacheOsm::onProviderResolutionError);
#endif
}
}
QT_END_NAMESPACE