Added no caching attribute to ImageFetcher and SituareService requests.
[situare] / src / map / mapfetcher.cpp
1 /*
2    Situare - A location system for Facebook
3    Copyright (C) 2010  Ixonos Plc. Authors:
4
5        Jussi Laitinen - jussi.laitinen@ixonos.com
6        Sami Rämö - sami.ramo@ixonos.com
7
8    Situare is free software; you can redistribute it and/or
9    modify it under the terms of the GNU General Public License
10    version 2 as published by the Free Software Foundation.
11
12    Situare is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with Situare; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
20    USA.
21 */
22
23 #include <QNetworkAccessManager>
24 #include <QNetworkRequest>
25 #include <QNetworkReply>
26 #include <QUrl>
27 #include <QDebug>
28 #include <QPixmap>
29 #include <QNetworkDiskCache>
30 #include <QDesktopServices>
31
32 #include "mapfetcher.h"
33 #include "mapcommon.h"
34 #include "network/networkaccessmanager.h"
35
36 const int MAX_PARALLEL_DOWNLOADS = 2; ///< Max simultaneous parallel downloads
37 const int NOT_FOUND = -1; ///< Return value if matching request is not found from the list
38
39 MapFetcher::MapFetcher(NetworkAccessManager *manager, QObject *parent)
40     : QObject(parent)
41     , m_pendingRequestsSize(0)
42     , m_fetchMapImagesTimerRunning(false)
43     , m_manager(manager)
44 {
45     qDebug() << __PRETTY_FUNCTION__;
46
47     QNetworkDiskCache *diskCache = new QNetworkDiskCache(this);
48     diskCache->setCacheDirectory(QDesktopServices::storageLocation(
49             QDesktopServices::CacheLocation));
50     m_manager->setCache(diskCache);
51
52     connect(m_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(
53             downloadFinished(QNetworkReply*)));
54 }
55
56 QUrl MapFetcher::buildURL(int zoomLevel, QPoint tileNumbers)
57 {
58     qDebug() << __PRETTY_FUNCTION__;
59
60     /**
61     * @brief Map server string for building actual URL
62     *
63     * %1 zoom level
64     * %2 x index
65     * %3 y index
66     *
67     * NOTE: If the URL is changed, then the parseURL method must be changed to match
68     *       the new URL structure
69     *
70     * @var MAP_SERVER_URL
71     */
72     const QString MAP_SERVER_URL = QString("http://tile.openstreetmap.org/mapnik/%1/%2/%3.png");
73
74     return QString(MAP_SERVER_URL)
75             .arg(zoomLevel).arg(tileNumbers.x()).arg(tileNumbers.y());
76 }
77
78 void MapFetcher::checkNextRequestFromCache()
79 {
80     qDebug() << __PRETTY_FUNCTION__;
81
82     int i = newestRequestIndex(false);
83
84     if (i != NOT_FOUND) {
85         QUrl url = m_pendingRequests[i].url;
86         if (!url.isEmpty() && url.isValid()) {
87             if (loadImageFromCache(url)) {
88                 // was found, remove from the list
89                 m_pendingRequests.removeAt(i);
90             }
91             else {
92                 // didn't found from cache so mark cache checked and leave to queue
93                 m_pendingRequests[i].cacheChecked = true;
94
95                 if (m_currentDownloads.size() < MAX_PARALLEL_DOWNLOADS)
96                     startNextDownload();
97             }
98         }
99     }
100
101     // schedule checking of the next request if the list is not empty
102     if (newestRequestIndex(false) != NOT_FOUND)
103         QTimer::singleShot(0, this, SLOT(checkNextRequestFromCache()));
104     else
105         m_fetchMapImagesTimerRunning = false;
106 }
107
108 void MapFetcher::downloadFinished(QNetworkReply *reply)
109 {
110     qDebug() << __PRETTY_FUNCTION__;
111
112     if (m_currentDownloads.contains(reply)) {
113
114         if (reply->error() == QNetworkReply::NoError) {
115             QImage image;
116             QUrl url = reply->url();
117
118             if (!image.load(reply, 0))
119                 image = QImage();
120
121             int zoomLevel;
122             int x;
123             int y;
124             parseURL(url, &zoomLevel, &x, &y);
125
126             emit mapImageReceived(zoomLevel, x, y, QPixmap::fromImage(image));
127         }
128         else {
129             emit error(reply->errorString());
130         }
131
132         m_currentDownloads.removeAll(reply);
133         reply->deleteLater();
134         startNextDownload();
135     }
136 }
137
138 void MapFetcher::enqueueFetchMapImage(int zoomLevel, int x, int y)
139 {
140     QUrl url = buildURL(zoomLevel, QPoint(x, y));
141
142     // check if new request is already in the list and move it to the begin of the list...
143     bool found = false;
144     for (int i = 0; i < m_pendingRequests.size(); i++) {
145         if (m_pendingRequests[i].url == url) {
146             m_pendingRequests.move(i, 0);
147             found = true;
148             break;
149         }
150     }
151     // ...or add new request to the begining of the list
152     if (!found) {
153         MapTileRequest request(url);
154         m_pendingRequests.prepend(request);
155     }
156
157     limitPendingRequestsListSize();
158
159     if (!m_fetchMapImagesTimerRunning) {
160         m_fetchMapImagesTimerRunning = true;
161         QTimer::singleShot(0, this, SLOT(checkNextRequestFromCache()));
162     }
163 }
164
165 void MapFetcher::limitPendingRequestsListSize()
166 {
167     qDebug() << __PRETTY_FUNCTION__;
168
169     while (m_pendingRequests.size() > m_pendingRequestsSize) {
170         m_pendingRequests.removeLast();
171     }
172 }
173
174 bool MapFetcher::loadImageFromCache(const QUrl &url)
175 {
176     qDebug() << __PRETTY_FUNCTION__;
177
178     bool imageFound = false;
179
180     QAbstractNetworkCache *cache = m_manager->cache();
181
182     if (cache) {
183
184         int zoomLevel;
185         int x;
186         int y;
187         parseURL(url, &zoomLevel, &x, &y);
188         int originalZoomLevel = zoomLevel;
189
190         // try to fetch requested and upper level images until found or all levels tried
191         do {
192             QIODevice *cacheImage = cache->data(buildURL(zoomLevel, QPoint(x, y)));
193             if (cacheImage) {
194                 QPixmap pixmap;
195                 if (pixmap.loadFromData(cacheImage->readAll())) {
196                     imageFound = true;
197                     emit mapImageReceived(zoomLevel, x, y, pixmap);
198                 }
199
200                 delete cacheImage;
201             }
202         } while (!imageFound && translateIndexesToUpperLevel(zoomLevel, x, y));
203
204         // check expiration if image was found from requested level
205         if (imageFound && (originalZoomLevel == zoomLevel)) {
206             // check if image is expired
207             QNetworkCacheMetaData metaData = cache->metaData(url);
208             if ((metaData.expirationDate().isValid()) && (url.isValid())) {
209
210                 if (metaData.expirationDate() < QDateTime::currentDateTime()) {
211                     cache->remove(url);
212                     return false;
213                 }
214             }
215         }
216
217         // if image was found, but from upper level, return false
218         if (imageFound && (originalZoomLevel != zoomLevel))
219             return false;
220     }
221
222     return imageFound;
223 }
224
225 bool MapFetcher::translateIndexesToUpperLevel(int &zoomLevel, int &x, int &y)
226 {
227     qDebug() << __PRETTY_FUNCTION__;
228
229     if (zoomLevel > MIN_MAP_ZOOM_LEVEL) {
230         zoomLevel--;
231         x /= 2;
232         y /= 2;
233
234         return true;
235     }
236
237     return false;
238 }
239
240 int MapFetcher::newestRequestIndex(bool cacheChecked)
241 {
242     qDebug() << __PRETTY_FUNCTION__;
243
244     for (int i = 0; i < m_pendingRequests.size(); i++) {
245         if (m_pendingRequests[i].cacheChecked == cacheChecked) {
246             return i;
247         }
248     }
249
250     return NOT_FOUND;
251 }
252
253 void MapFetcher::parseURL(const QUrl &url, int *zoom, int *x, int *y)
254 {
255     qDebug() << __PRETTY_FUNCTION__;
256
257     QString path = url.path();
258     QStringList pathParts = path.split("/", QString::SkipEmptyParts);
259
260     int size = pathParts.size();
261
262     // Example URL: "http://tile.openstreetmap.org/mapnik/14/9354/4263.png"
263     const int MIN_PATH_SPLITTED_PARTS = 4;
264     const int ZOOM_INDEX = size - 3;
265     const int X_INDEX = size - 2;
266     const int Y_INDEX = size - 1;
267     const int FILE_EXTENSION_LENGTH = 4;
268
269     if (size >= MIN_PATH_SPLITTED_PARTS) {
270         *zoom = (pathParts.at(ZOOM_INDEX)).toInt();
271         *x = (pathParts.at(X_INDEX)).toInt();
272         QString yString = pathParts.at(Y_INDEX);
273         yString.chop(FILE_EXTENSION_LENGTH);
274         *y = yString.toInt();
275     }
276 }
277
278 void MapFetcher::setDownloadQueueSize(int size)
279 {
280     qDebug() << __PRETTY_FUNCTION__ << "size:" << size;
281
282     m_pendingRequestsSize = size;
283     limitPendingRequestsListSize();
284 }
285
286 void MapFetcher::startNextDownload()
287 {
288     qDebug() << __PRETTY_FUNCTION__;
289
290     int i = newestRequestIndex(true);
291
292     if (i != NOT_FOUND) {
293         QUrl url = m_pendingRequests.takeAt(i).url;
294
295         QNetworkRequest request(url);
296         request.setRawHeader("User-Agent", "Situare");
297         QNetworkReply *reply = m_manager->get(request);
298
299         m_currentDownloads.append(reply);
300     }
301 }