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