Moved URL builder and parser to MapFetcher
[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
35 MapFetcher::MapFetcher(QNetworkAccessManager *manager, QObject *parent)
36     : QObject(parent)
37     , m_pendingRequestsSize(0)
38     , m_fetchMapImagesTimerRunning(false)
39     , m_manager(manager)
40 {
41     QNetworkDiskCache *diskCache = new QNetworkDiskCache(this);
42     diskCache->setCacheDirectory(QDesktopServices::storageLocation(
43             QDesktopServices::CacheLocation));
44     m_manager->setCache(diskCache);
45
46     connect(m_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(
47             downloadFinished(QNetworkReply*)));
48 }
49
50 QUrl MapFetcher::buildURL(int zoomLevel, QPoint tileNumbers)
51 {
52     QString url = QString("http://tile.openstreetmap.org/mapnik/%1/%2/%3.png")
53                   .arg(zoomLevel).arg(tileNumbers.x()).arg(tileNumbers.y());
54
55     return QUrl(url);
56 }
57
58 void MapFetcher::checkNextRequestFromCache()
59 {
60     qDebug() << __PRETTY_FUNCTION__;
61
62     int i = newestRequestIndex(false);
63
64     if (i != -1) {
65         if (!m_pendingRequests[i].url.isEmpty() && m_pendingRequests[i].url.isValid()) {
66             if (loadImageFromCache(m_pendingRequests[i].url)) {
67                 // was found, remove from the list
68                 m_pendingRequests.removeAt(i);
69             }
70             else {
71                 // didn't found from cache so mark cache checked and leave to queue
72                 m_pendingRequests[i].cacheChecked = true;
73
74                 if (m_currentDownloads.size() < MAX_PARALLEL_DOWNLOADS)
75                     startNextDownload();
76             }
77         }
78     }
79
80     // schedule checking of the next request if the list is not empty
81     if (newestRequestIndex(false) != -1)
82         QTimer::singleShot(0, this, SLOT(checkNextRequestFromCache()));
83     else
84         m_fetchMapImagesTimerRunning = false;
85 }
86
87 void MapFetcher::downloadFinished(QNetworkReply *reply)
88 {
89     qDebug() << __PRETTY_FUNCTION__;
90
91     if (reply->error() == QNetworkReply::NoError) {
92         QImage image;
93         QUrl url = reply->url();
94
95         if (!image.load(reply, 0))
96             image = QImage();
97
98         int zoomLevel;
99         int x;
100         int y;
101         parseURL(url, zoomLevel, x, y);
102
103         emit mapImageReceived(zoomLevel, x, y, QPixmap::fromImage(image));
104     }
105     else {
106         emit error(reply->errorString());
107     }
108
109     m_currentDownloads.removeAll(reply);
110     reply->deleteLater();
111     startNextDownload();
112 }
113
114 void MapFetcher::enqueueFetchMapImage(int zoomLevel, int x, int y)
115 {
116     QUrl url = buildURL(zoomLevel, QPoint(x, y));
117
118     qDebug() << __PRETTY_FUNCTION__ << "url:" << url.toString();
119
120     // check if new request is already in the list and move it to the begin of the list...
121     bool found = false;
122     for (int i = 0; i < m_pendingRequests.size(); i++) {
123         if (m_pendingRequests[i].url == url) {
124             m_pendingRequests.move(i, 0);
125             found = true;
126             qDebug() << __PRETTY_FUNCTION__ << "URL was already found, moved to begin";
127             break;
128         }
129     }
130     // ...or add new request to the begining of the list
131     if (!found) {
132         qDebug() << __PRETTY_FUNCTION__ << "URL was added";
133         Request request;
134         request.cacheChecked = false;
135         request.url = url;
136         m_pendingRequests.prepend(request);
137     }
138
139     limitPendingRequestsListSize();
140
141     if (!m_fetchMapImagesTimerRunning) {
142         m_fetchMapImagesTimerRunning = true;
143         QTimer::singleShot(0, this, SLOT(checkNextRequestFromCache()));
144     }
145 }
146
147 void MapFetcher::limitPendingRequestsListSize()
148 {
149     qDebug() << __PRETTY_FUNCTION__;
150
151     while (m_pendingRequests.size() > m_pendingRequestsSize) {
152         m_pendingRequests.removeLast();
153     }
154 }
155
156 bool MapFetcher::loadImageFromCache(const QUrl &url)
157 {
158     qDebug() << __PRETTY_FUNCTION__;
159
160     bool imageFound = false;
161
162     QAbstractNetworkCache *cache = m_manager->cache();
163
164     if (cache) {
165
166         QNetworkCacheMetaData metaData = cache->metaData(url);
167
168         if ((metaData.expirationDate().isValid()) && (url.isValid())) {
169
170             if (metaData.expirationDate() < QDateTime::currentDateTime()) {
171                 cache->remove(url);
172                 return false;
173             }
174         }
175
176         QIODevice *cacheImage = cache->data(url);
177
178         if (cacheImage) {
179             QImage image;
180
181             if (image.load(cacheImage, 0)) {
182                 imageFound = true;
183
184                 int zoomLevel;
185                 int x;
186                 int y;
187                 parseURL(url, zoomLevel, x, y);
188
189                 emit mapImageReceived(zoomLevel, x, y, QPixmap::fromImage(image));
190             }
191
192             delete cacheImage;
193         }
194     }
195
196     return imageFound;
197 }
198
199 int MapFetcher::newestRequestIndex(bool cacheChecked)
200 {
201     qDebug() << __PRETTY_FUNCTION__;
202
203     if (!m_pendingRequests.isEmpty()) {
204         for (int i = 0; i < m_pendingRequests.size(); i++) {
205             if (m_pendingRequests[i].cacheChecked == cacheChecked) {
206                 return i;
207             }
208         }
209     }
210
211     return -1;
212 }
213
214 void MapFetcher::parseURL(const QUrl &url, int &zoom, int &x, int &y)
215 {
216     QString path = url.path();
217     QStringList pathParts = path.split("/", QString::SkipEmptyParts);
218
219     int size = pathParts.size();
220
221     if (size >= 3) {
222         zoom = (pathParts.at(size-3)).toInt();
223         x = (pathParts.at(size-2)).toInt();
224         QString yString = pathParts.at(size-1);
225         yString.chop(4);
226         y = yString.toInt();
227     }
228 }
229
230 void MapFetcher::setDownloadQueueSize(int size)
231 {
232     qDebug() << __PRETTY_FUNCTION__ << "size:" << size;
233
234     m_pendingRequestsSize = size;
235     limitPendingRequestsListSize();
236 }
237
238 void MapFetcher::startNextDownload()
239 {
240     qDebug() << __PRETTY_FUNCTION__;
241
242     int i = newestRequestIndex(true);
243
244     if (i != -1) {
245         QUrl url = m_pendingRequests.takeAt(i).url;
246
247         QNetworkRequest request(url);
248         request.setRawHeader("User-Agent", "Situare");
249         QNetworkReply *reply = m_manager->get(request);
250
251         m_currentDownloads.append(reply);
252     }
253 }