summaryrefslogtreecommitdiff
path: root/src/location/maps/qgeofiletilecache.cpp
diff options
context:
space:
mode:
authorHarald Meyer <dev@meh.at>2015-10-10 17:01:07 +0200
committerHarald Meyer <dev@meh.at>2015-10-22 05:02:14 +0000
commit211fd58142a03387b8a693f20a6054a8578e9f8a (patch)
tree61df325a59ea5af6c3a14684c8b021bc34825202 /src/location/maps/qgeofiletilecache.cpp
parentf7d39c4aa2d16c0e7475a301e24818ae50c970c8 (diff)
downloadqtlocation-211fd58142a03387b8a693f20a6054a8578e9f8a.tar.gz
Split QGeoTileCache for easier customization of the tile cache.
The QGeoTileCache is split into two classes: The abstract class QAbstractGeoTileCache offers interfaces for inserting and getting tiles, as well as getting cache status information such as used texture memory. The new class QGeoFileTileCache implements QAbstractGeoTileCache and offers a file based caching implementation (the implementation is the same as before splitting the class). Change-Id: I2eab7491d489ac5b251c1296b67688901f5cc7d7 Reviewed-by: Laszlo Agocs <laszlo.agocs@theqtcompany.com>
Diffstat (limited to 'src/location/maps/qgeofiletilecache.cpp')
-rw-r--r--src/location/maps/qgeofiletilecache.cpp447
1 files changed, 447 insertions, 0 deletions
diff --git a/src/location/maps/qgeofiletilecache.cpp b/src/location/maps/qgeofiletilecache.cpp
new file mode 100644
index 00000000..4efe9696
--- /dev/null
+++ b/src/location/maps/qgeofiletilecache.cpp
@@ -0,0 +1,447 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 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 "qgeofiletilecache_p.h"
+
+#include "qgeotilespec_p.h"
+
+#include "qgeomappingmanager_p.h"
+
+#include <QDir>
+#include <QStandardPaths>
+#include <QMetaType>
+#include <QPixmap>
+#include <QDebug>
+
+Q_DECLARE_METATYPE(QList<QGeoTileSpec>)
+Q_DECLARE_METATYPE(QSet<QGeoTileSpec>)
+
+QT_BEGIN_NAMESPACE
+
+class QGeoCachedTileMemory
+{
+public:
+ ~QGeoCachedTileMemory()
+ {
+ if (cache)
+ cache->evictFromMemoryCache(this);
+ }
+
+ QGeoTileSpec spec;
+ QGeoFileTileCache *cache;
+ QByteArray bytes;
+ QString format;
+};
+
+void QCache3QTileEvictionPolicy::aboutToBeRemoved(const QGeoTileSpec &key, QSharedPointer<QGeoCachedTileDisk> obj)
+{
+ Q_UNUSED(key);
+ // set the cache pointer to zero so we can't call evictFromDiskCache
+ obj->cache = 0;
+}
+
+void QCache3QTileEvictionPolicy::aboutToBeEvicted(const QGeoTileSpec &key, QSharedPointer<QGeoCachedTileDisk> obj)
+{
+ Q_UNUSED(key);
+ Q_UNUSED(obj);
+ // leave the pointer set if it's a real eviction
+}
+
+QGeoCachedTileDisk::~QGeoCachedTileDisk()
+{
+ if (cache)
+ cache->evictFromDiskCache(this);
+}
+
+QGeoFileTileCache::QGeoFileTileCache(const QString &directory, QObject *parent)
+ : QAbstractGeoTileCache(parent), directory_(directory),
+ minTextureUsage_(0), extraTextureUsage_(0)
+{
+ const QString basePath = baseCacheDirectory();
+
+ // delete old tiles from QtLocation 5.4 or prior
+ // Newer version use plugin-specific subdirectories so those are not affected.
+ // TODO Remove cache cleanup in Qt 6
+ QDir baseDir(basePath);
+ if (baseDir.exists()) {
+ const QStringList oldCacheFiles = baseDir.entryList(QDir::Files);
+ foreach (const QString& file, oldCacheFiles)
+ baseDir.remove(file);
+ }
+
+ if (directory_.isEmpty()) {
+ directory_ = basePath;
+ qWarning() << "Plugin uses uninitialized QGeoFileTileCache directory which was deleted during startup";
+ }
+
+ QDir::root().mkpath(directory_);
+
+ // default values
+ setMaxDiskUsage(20 * 1024 * 1024);
+ setMaxMemoryUsage(3 * 1024 * 1024);
+ setExtraTextureUsage(6 * 1024 * 1024);
+
+ loadTiles();
+}
+
+void QGeoFileTileCache::loadTiles()
+{
+ QStringList formats;
+ formats << QLatin1String("*.*");
+
+ QDir dir(directory_);
+ QStringList files = dir.entryList(formats, QDir::Files);
+
+ // Method:
+ // 1. read each queue file then, if each file exists, deserialize the data into the appropriate
+ // cache queue.
+ for (int i = 1; i<=4; i++) {
+ QString filename = dir.filePath(QString::fromLatin1("queue") + QString::number(i));
+ QFile file(filename);
+ if (!file.open(QIODevice::ReadOnly))
+ continue;
+ QList<QSharedPointer<QGeoCachedTileDisk> > queue;
+ QList<QGeoTileSpec> specs;
+ QList<int> costs;
+ while (!file.atEnd()) {
+ QByteArray line = file.readLine().trimmed();
+ QString filename = QString::fromLatin1(line.constData(), line.length());
+ if (dir.exists(filename)){
+ files.removeOne(filename);
+ QGeoTileSpec spec = filenameToTileSpec(filename);
+ if (spec.zoom() == -1)
+ continue;
+ QSharedPointer<QGeoCachedTileDisk> tileDisk(new QGeoCachedTileDisk);
+ tileDisk->filename = dir.filePath(filename);
+ tileDisk->cache = this;
+ tileDisk->spec = spec;
+ QFileInfo fi(tileDisk->filename);
+ specs.append(spec);
+ queue.append(tileDisk);
+ costs.append(fi.size());
+ }
+ }
+
+ diskCache_.deserializeQueue(i, specs, queue, costs);
+ file.close();
+ }
+
+ // 2. remaining tiles that aren't registered in a queue get pushed into cache here
+ // this is a backup, in case the queue manifest files get deleted or out of sync due to
+ // the application not closing down properly
+ for (int i = 0; i < files.size(); ++i) {
+ QGeoTileSpec spec = filenameToTileSpec(files.at(i));
+ if (spec.zoom() == -1)
+ continue;
+ QString filename = dir.filePath(files.at(i));
+ addToDiskCache(spec, filename);
+ }
+}
+
+QGeoFileTileCache::~QGeoFileTileCache()
+{
+ // write disk cache queues to disk
+ QDir dir(directory_);
+ for (int i = 1; i<=4; i++) {
+ QString filename = dir.filePath(QString::fromLatin1("queue") + QString::number(i));
+ QFile file(filename);
+ if (!file.open(QIODevice::WriteOnly)){
+ qWarning() << "Unable to write tile cache file " << filename;
+ continue;
+ }
+ QList<QSharedPointer<QGeoCachedTileDisk> > queue;
+ diskCache_.serializeQueue(i, queue);
+ foreach (const QSharedPointer<QGeoCachedTileDisk> &tile, queue) {
+ if (tile.isNull())
+ continue;
+
+ // we just want the filename here, not the full path
+ int index = tile->filename.lastIndexOf(QLatin1Char('/'));
+ QByteArray filename = tile->filename.mid(index + 1).toLatin1() + '\n';
+ file.write(filename);
+ }
+ file.close();
+ }
+}
+
+void QGeoFileTileCache::printStats()
+{
+ textureCache_.printStats();
+ memoryCache_.printStats();
+ diskCache_.printStats();
+}
+
+void QGeoFileTileCache::setMaxDiskUsage(int diskUsage)
+{
+ diskCache_.setMaxCost(diskUsage);
+}
+
+int QGeoFileTileCache::maxDiskUsage() const
+{
+ return diskCache_.maxCost();
+}
+
+int QGeoFileTileCache::diskUsage() const
+{
+ return diskCache_.totalCost();
+}
+
+void QGeoFileTileCache::setMaxMemoryUsage(int memoryUsage)
+{
+ memoryCache_.setMaxCost(memoryUsage);
+}
+
+int QGeoFileTileCache::maxMemoryUsage() const
+{
+ return memoryCache_.maxCost();
+}
+
+int QGeoFileTileCache::memoryUsage() const
+{
+ return memoryCache_.totalCost();
+}
+
+void QGeoFileTileCache::setExtraTextureUsage(int textureUsage)
+{
+ extraTextureUsage_ = textureUsage;
+ textureCache_.setMaxCost(minTextureUsage_ + extraTextureUsage_);
+}
+
+void QGeoFileTileCache::setMinTextureUsage(int textureUsage)
+{
+ minTextureUsage_ = textureUsage;
+ textureCache_.setMaxCost(minTextureUsage_ + extraTextureUsage_);
+}
+
+int QGeoFileTileCache::maxTextureUsage() const
+{
+ return textureCache_.maxCost();
+}
+
+int QGeoFileTileCache::minTextureUsage() const
+{
+ return minTextureUsage_;
+}
+
+
+int QGeoFileTileCache::textureUsage() const
+{
+ return textureCache_.totalCost();
+}
+
+QSharedPointer<QGeoTileTexture> QGeoFileTileCache::get(const QGeoTileSpec &spec)
+{
+ QSharedPointer<QGeoTileTexture> tt = textureCache_.object(spec);
+ if (tt)
+ return tt;
+
+ QSharedPointer<QGeoCachedTileMemory> tm = memoryCache_.object(spec);
+ if (tm) {
+ QPixmap pixmap;
+ if (!pixmap.loadFromData(tm->bytes)) {
+ handleError(spec, QLatin1String("Problem with tile image"));
+ return QSharedPointer<QGeoTileTexture>(0);
+ }
+ QSharedPointer<QGeoTileTexture> tt = addToTextureCache(spec, pixmap);
+ if (tt)
+ return tt;
+ }
+
+ QSharedPointer<QGeoCachedTileDisk> td = diskCache_.object(spec);
+ if (td) {
+ const QString format = QFileInfo(td->filename).suffix();
+ QFile file(td->filename);
+ file.open(QIODevice::ReadOnly);
+ QByteArray bytes = file.readAll();
+ file.close();
+
+ QPixmap pixmap;
+ if (!pixmap.loadFromData(bytes)) {
+ handleError(spec, QLatin1String("Problem with tile image"));
+ return QSharedPointer<QGeoTileTexture>(0);
+ }
+
+ addToMemoryCache(spec, bytes, format);
+ QSharedPointer<QGeoTileTexture> tt = addToTextureCache(td->spec, pixmap);
+ if (tt)
+ return tt;
+ }
+
+ return QSharedPointer<QGeoTileTexture>();
+}
+
+void QGeoFileTileCache::insert(const QGeoTileSpec &spec,
+ const QByteArray &bytes,
+ const QString &format,
+ QGeoTiledMappingManagerEngine::CacheAreas areas)
+{
+ if (areas & QGeoTiledMappingManagerEngine::DiskCache) {
+ QString filename = tileSpecToFilename(spec, format, directory_);
+ QFile file(filename);
+ file.open(QIODevice::WriteOnly);
+ file.write(bytes);
+ file.close();
+
+ addToDiskCache(spec, filename);
+ }
+
+ if (areas & QGeoTiledMappingManagerEngine::MemoryCache) {
+ addToMemoryCache(spec, bytes, format);
+ }
+
+ /* inserts do not hit the texture cache -- this actually reduces overall
+ * cache hit rates because many tiles come too late to be useful
+ * and act as a poison */
+}
+
+void QGeoFileTileCache::evictFromDiskCache(QGeoCachedTileDisk *td)
+{
+ QFile::remove(td->filename);
+}
+
+void QGeoFileTileCache::evictFromMemoryCache(QGeoCachedTileMemory * /* tm */)
+{
+}
+
+QSharedPointer<QGeoCachedTileDisk> QGeoFileTileCache::addToDiskCache(const QGeoTileSpec &spec, const QString &filename)
+{
+ QSharedPointer<QGeoCachedTileDisk> td(new QGeoCachedTileDisk);
+ td->spec = spec;
+ td->filename = filename;
+ td->cache = this;
+
+ QFileInfo fi(filename);
+ int diskCost = fi.size();
+ diskCache_.insert(spec, td, diskCost);
+ return td;
+}
+
+QSharedPointer<QGeoCachedTileMemory> QGeoFileTileCache::addToMemoryCache(const QGeoTileSpec &spec, const QByteArray &bytes, const QString &format)
+{
+ QSharedPointer<QGeoCachedTileMemory> tm(new QGeoCachedTileMemory);
+ tm->spec = spec;
+ tm->cache = this;
+ tm->bytes = bytes;
+ tm->format = format;
+
+ int cost = bytes.size();
+ memoryCache_.insert(spec, tm, cost);
+
+ return tm;
+}
+
+QSharedPointer<QGeoTileTexture> QGeoFileTileCache::addToTextureCache(const QGeoTileSpec &spec, const QPixmap &pixmap)
+{
+ QSharedPointer<QGeoTileTexture> tt(new QGeoTileTexture);
+ tt->spec = spec;
+ tt->image = pixmap.toImage();
+
+ int textureCost = tt->image.width() * tt->image.height() * tt->image.depth() / 8;
+ textureCache_.insert(spec, tt, textureCost);
+
+ return tt;
+}
+
+QString QGeoFileTileCache::tileSpecToFilename(const QGeoTileSpec &spec, const QString &format, const QString &directory)
+{
+ QString filename = spec.plugin();
+ 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;
+
+ QDir dir = QDir(directory);
+
+ return dir.filePath(filename);
+}
+
+QGeoTileSpec QGeoFileTileCache::filenameToTileSpec(const QString &filename)
+{
+ 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 != 5 && length != 6)
+ return emptySpec;
+
+ QList<int> numbers;
+
+ bool ok = false;
+ for (int i = 1; i < length; ++i) {
+ ok = false;
+ int value = fields.at(i).toInt(&ok);
+ if (!ok)
+ return emptySpec;
+ numbers.append(value);
+ }
+
+ //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));
+}
+
+QString QGeoFileTileCache::directory() const
+{
+ return directory_;
+}
+
+QT_END_NAMESPACE