1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 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 #include "qgeotilerequestmanager_p.h"
37 #include "qgeotilespec_p.h"
38 #include "qgeotiledmap_p.h"
39 #include "qgeotiledmappingmanagerengine_p.h"
40 #include "qabstractgeotilecache_p.h"
41 #include <QtCore/QPointer>
42
43 QT_BEGIN_NAMESPACE
44
45 class RetryFuture;
46
47 class QGeoTileRequestManagerPrivate
48 {
49 public:
50 explicit QGeoTileRequestManagerPrivate(QGeoTiledMap *map, QGeoTiledMappingManagerEngine *engine);
51 ~QGeoTileRequestManagerPrivate();
52
53 QGeoTiledMap *m_map;
54 QPointer<QGeoTiledMappingManagerEngine> m_engine;
55
56 QMap<QGeoTileSpec, QSharedPointer<QGeoTileTexture> > requestTiles(const QSet<QGeoTileSpec> &tiles);
57 void tileError(const QGeoTileSpec &tile, const QString &errorString);
58
59 QHash<QGeoTileSpec, int> m_retries;
60 QHash<QGeoTileSpec, QSharedPointer<RetryFuture> > m_futures;
61 QSet<QGeoTileSpec> m_requested;
62
63 void tileFetched(const QGeoTileSpec &spec);
64 };
65
QGeoTileRequestManager(QGeoTiledMap * map,QGeoTiledMappingManagerEngine * engine)66 QGeoTileRequestManager::QGeoTileRequestManager(QGeoTiledMap *map, QGeoTiledMappingManagerEngine *engine)
67 : d_ptr(new QGeoTileRequestManagerPrivate(map, engine))
68 {
69
70 }
71
~QGeoTileRequestManager()72 QGeoTileRequestManager::~QGeoTileRequestManager()
73 {
74
75 }
76
requestTiles(const QSet<QGeoTileSpec> & tiles)77 QMap<QGeoTileSpec, QSharedPointer<QGeoTileTexture> > QGeoTileRequestManager::requestTiles(const QSet<QGeoTileSpec> &tiles)
78 {
79 return d_ptr->requestTiles(tiles);
80 }
81
tileFetched(const QGeoTileSpec & spec)82 void QGeoTileRequestManager::tileFetched(const QGeoTileSpec &spec)
83 {
84 d_ptr->tileFetched(spec);
85 }
86
tileTexture(const QGeoTileSpec & spec)87 QSharedPointer<QGeoTileTexture> QGeoTileRequestManager::tileTexture(const QGeoTileSpec &spec)
88 {
89 if (d_ptr->m_engine)
90 return d_ptr->m_engine->getTileTexture(spec);
91 else
92 return QSharedPointer<QGeoTileTexture>();
93 }
94
tileError(const QGeoTileSpec & tile,const QString & errorString)95 void QGeoTileRequestManager::tileError(const QGeoTileSpec &tile, const QString &errorString)
96 {
97 d_ptr->tileError(tile, errorString);
98 }
99
QGeoTileRequestManagerPrivate(QGeoTiledMap * map,QGeoTiledMappingManagerEngine * engine)100 QGeoTileRequestManagerPrivate::QGeoTileRequestManagerPrivate(QGeoTiledMap *map,QGeoTiledMappingManagerEngine *engine)
101 : m_map(map),
102 m_engine(engine)
103 {
104 }
105
~QGeoTileRequestManagerPrivate()106 QGeoTileRequestManagerPrivate::~QGeoTileRequestManagerPrivate()
107 {
108 }
109
requestTiles(const QSet<QGeoTileSpec> & tiles)110 QMap<QGeoTileSpec, QSharedPointer<QGeoTileTexture> > QGeoTileRequestManagerPrivate::requestTiles(const QSet<QGeoTileSpec> &tiles)
111 {
112 QSet<QGeoTileSpec> cancelTiles = m_requested - tiles;
113 QSet<QGeoTileSpec> requestTiles = tiles - m_requested;
114 QSet<QGeoTileSpec> cached;
115 // int tileSize = tiles.size();
116 // int newTiles = requestTiles.size();
117
118 typedef QSet<QGeoTileSpec>::const_iterator iter;
119
120 QMap<QGeoTileSpec, QSharedPointer<QGeoTileTexture> > cachedTex;
121
122 // remove tiles in cache from request tiles
123 if (!m_engine.isNull()) {
124 iter i = requestTiles.constBegin();
125 iter end = requestTiles.constEnd();
126 for (; i != end; ++i) {
127 QGeoTileSpec tile = *i;
128 QSharedPointer<QGeoTileTexture> tex = m_engine->getTileTexture(tile);
129 if (tex) {
130 if (!tex->image.isNull())
131 cachedTex.insert(tile, tex);
132 cached.insert(tile);
133 } else {
134 // Try to use textures from lower zoom levels, but still request the proper tile
135 QGeoTileSpec spec = tile;
136 const int endRange = qMax(0, tile.zoom() - 4); // Using up to 4 zoom levels up. 4 is arbitrary.
137 for (int z = tile.zoom() - 1; z >= endRange; z--) {
138 int denominator = 1 << (tile.zoom() - z);
139 spec.setZoom(z);
140 spec.setX(tile.x() / denominator);
141 spec.setY(tile.y() / denominator);
142 QSharedPointer<QGeoTileTexture> t = m_engine->getTileTexture(spec);
143 if (t && !t->image.isNull()) {
144 cachedTex.insert(tile, t);
145 break;
146 }
147 }
148 }
149 }
150 }
151
152 requestTiles -= cached;
153
154 m_requested -= cancelTiles;
155 m_requested += requestTiles;
156
157 // qDebug() << "required # tiles: " << tileSize << ", new tiles: " << newTiles << ", total server requests: " << requested_.size();
158
159 if (!requestTiles.isEmpty() || !cancelTiles.isEmpty()) {
160 if (!m_engine.isNull()) {
161 // qDebug() << "new server requests: " << requestTiles.size() << ", server cancels: " << cancelTiles.size();
162 m_engine->updateTileRequests(m_map, requestTiles, cancelTiles);
163
164 // Remove any cancelled tiles from the error retry hash to avoid
165 // re-using the numbers for a totally different request cycle.
166 iter i = cancelTiles.constBegin();
167 iter end = cancelTiles.constEnd();
168 for (; i != end; ++i) {
169 m_retries.remove(*i);
170 m_futures.remove(*i);
171 }
172 }
173 }
174
175 return cachedTex;
176 }
177
tileFetched(const QGeoTileSpec & spec)178 void QGeoTileRequestManagerPrivate::tileFetched(const QGeoTileSpec &spec)
179 {
180 m_map->updateTile(spec);
181 m_requested.remove(spec);
182 m_retries.remove(spec);
183 m_futures.remove(spec);
184 }
185
186 // Represents a tile that needs to be retried after a certain period of time
187 class RetryFuture : public QObject
188 {
189 Q_OBJECT
190 public:
191 RetryFuture(const QGeoTileSpec &tile, QGeoTiledMap *map, QGeoTiledMappingManagerEngine* engine, QObject *parent = 0);
192
193 public Q_SLOTS:
194 void retry();
195
196 private:
197 QGeoTileSpec m_tile;
198 QGeoTiledMap *m_map;
199 QPointer<QGeoTiledMappingManagerEngine> m_engine;
200 };
201
RetryFuture(const QGeoTileSpec & tile,QGeoTiledMap * map,QGeoTiledMappingManagerEngine * engine,QObject * parent)202 RetryFuture::RetryFuture(const QGeoTileSpec &tile, QGeoTiledMap *map, QGeoTiledMappingManagerEngine* engine, QObject *parent)
203 : QObject(parent), m_tile(tile), m_map(map), m_engine(engine)
204 {}
205
retry()206 void RetryFuture::retry()
207 {
208 QSet<QGeoTileSpec> requestTiles;
209 QSet<QGeoTileSpec> cancelTiles;
210 requestTiles.insert(m_tile);
211 if (!m_engine.isNull())
212 m_engine->updateTileRequests(m_map, requestTiles, cancelTiles);
213 }
214
tileError(const QGeoTileSpec & tile,const QString & errorString)215 void QGeoTileRequestManagerPrivate::tileError(const QGeoTileSpec &tile, const QString &errorString)
216 {
217 if (m_requested.contains(tile)) {
218 int count = m_retries.value(tile, 0);
219 m_retries.insert(tile, count + 1);
220
221 if (count >= 5) {
222 qWarning("QGeoTileRequestManager: Failed to fetch tile (%d,%d,%d) 5 times, giving up. "
223 "Last error message was: '%s'",
224 tile.x(), tile.y(), tile.zoom(), qPrintable(errorString));
225 m_requested.remove(tile);
226 m_retries.remove(tile);
227 m_futures.remove(tile);
228
229 } else {
230 // Exponential time backoff when retrying
231 int delay = (1 << count) * 500;
232
233 QSharedPointer<RetryFuture> future(new RetryFuture(tile,m_map,m_engine));
234 m_futures.insert(tile, future);
235
236 QTimer::singleShot(delay, future.data(), SLOT(retry()));
237 // Passing .data() to singleShot is ok -- Qt will clean up the
238 // connection if the target qobject is deleted
239 }
240 }
241 }
242
243 QT_END_NAMESPACE
244
245 #include "qgeotilerequestmanager.moc"
246