/**************************************************************************** ** ** 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 #include #include #include #include #include #include QT_BEGIN_NAMESPACE QGeoFileTileCacheOsm::QGeoFileTileCacheOsm(const QList &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 QGeoFileTileCacheOsm::get(const QGeoTileSpec &spec) { QSharedPointer 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 QGeoFileTileCacheOsm::getFromOfflineStorage(const QGeoTileSpec &spec) { if (!m_offlineData) return QSharedPointer(); int providerId = spec.mapId() - 1; if (providerId < 0 || providerId >= m_providers.size()) return QSharedPointer(); const QString fileName = tileSpecToFilename(spec, QStringLiteral("*"), providerId); QStringList validTiles = m_offlineDirectory.entryList({fileName}); if (!validTiles.size()) return QSharedPointer(); QFile file(m_offlineDirectory.absoluteFilePath(validTiles.first())); if (!file.open(QIODevice::ReadOnly)) return QSharedPointer(); QByteArray bytes = file.readAll(); file.close(); QImage image; if (!image.loadFromData(bytes)) { handleError(spec, QLatin1String("Problem with tile image")); return QSharedPointer(0); } addToMemoryCache(spec, bytes, QString()); return addToTextureCache(spec, image); } void QGeoFileTileCacheOsm::dropTiles(int mapId) { QList 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 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