Emit error instead of queuing requests when session is empty
[situare] / src / situareservice / situareservice.cpp
1 /*
2    Situare - A location system for Facebook
3    Copyright (C) 2010  Ixonos Plc. Authors:
4
5       Henri Lampela - henri.lampela@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 <qjson/parser.h>
24
25 #include <QDebug>
26 #include <QNetworkReply>
27 #include <QPixmap>
28 #include <QStringList>
29 #include <QtAlgorithms>
30 #include <QtGlobal>
31
32 #include "../error.h"
33 #include "network/networkaccessmanager.h"
34 #include "situarecommon.h"
35 #include "ui/avatarimage.h"
36
37 #include "situareservice.h"
38
39 SituareService::SituareService(QObject *parent)
40         : QObject(parent),
41         m_user(0)
42 {
43     qDebug() << __PRETTY_FUNCTION__;
44
45     m_networkManager = new NetworkAccessManager(this);
46     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)),
47             this, SLOT(requestFinished(QNetworkReply*)), Qt::QueuedConnection);
48
49     m_imageFetcher = new ImageFetcher(new NetworkAccessManager(this), this);
50     connect(this, SIGNAL(fetchImage(QUrl)),
51             m_imageFetcher, SLOT(fetchImage(QUrl)));
52     connect(m_imageFetcher, SIGNAL(imageReceived(QUrl,QPixmap)),
53             this, SLOT(imageReceived(QUrl, QPixmap)));
54     connect(m_imageFetcher, SIGNAL(error(int, int)),
55             this, SIGNAL(error(int, int)));
56 }
57
58 SituareService::~SituareService()
59 {
60     qDebug() << __PRETTY_FUNCTION__;
61
62     if(m_user) {
63         delete m_user;
64         m_user = 0;
65     }
66
67     qDeleteAll(m_friendsList.begin(), m_friendsList.end());
68     m_friendsList.clear();
69 }
70
71 void SituareService::addProfileImages(const QList<QUrl> &imageUrlList)
72 {
73     qDebug() << __PRETTY_FUNCTION__;
74
75     foreach(QUrl url, imageUrlList) {
76         emit fetchImage(url);
77     }
78 }
79
80 void SituareService::appendAccessToken(QString &requestUrl)
81 {
82     qWarning() << __PRETTY_FUNCTION__;
83
84     requestUrl.append(m_session);
85 }
86
87 void SituareService::buildRequest(const QString &script, const QHash<QString, QString> &parameters)
88 {
89     qWarning() << __PRETTY_FUNCTION__;
90
91     const QString PARAMETER_KEY_API = "api";
92     const QString PARAMETER_VALUE_API = "2.0";
93
94     QString url = SITUARE_URL;
95     url.append(script);
96     url.append("?");
97
98     // append default api version parameter if not yet specified
99 //    if (!parameters.contains(PARAMETER_KEY_API))
100 //        url.append(PARAMETER_KEY_API + "=" + PARAMETER_VALUE_API + "&");
101
102     // append parameters
103     if (!parameters.isEmpty()) {
104         QHash<QString, QString>::const_iterator i = parameters.constBegin();
105         while (i != parameters.constEnd()) {
106             url.append(i.key());
107             url.append("=");
108             url.append(i.value());
109             url.append("&");
110             i++;
111         }
112     }
113
114 //    qWarning() << __PRETTY_FUNCTION__ << "request url with parameters:" << url;
115
116     if (!m_session.isEmpty()) {
117         appendAccessToken(url);
118         sendRequest(url);
119     } else {
120         emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
121     }
122 }
123
124 void SituareService::clearUserData()
125 {
126     qDebug() << __PRETTY_FUNCTION__;
127
128     qDeleteAll(m_friendsList.begin(), m_friendsList.end());
129     m_friendsList.clear();
130
131     if(m_user) {
132         delete m_user;
133         m_user = 0;
134     }
135     emit userDataChanged(m_user, m_friendsList);
136 }
137
138 QString SituareService::degreesToString(double degrees)
139 {
140     qDebug() << __PRETTY_FUNCTION__;
141
142     // one scene pixel is about 5.4e-6 degrees, the integer part is max three digits and one
143     // additional digit is added for maximum precision
144     const int PRECISION = 10;
145
146     return QString::number(degrees, 'f', PRECISION);
147 }
148
149 void SituareService::fetchLocations()
150 {
151     qDebug() << __PRETTY_FUNCTION__;
152
153     QHash<QString, QString> parameters;
154     parameters.insert("extra_user_data", NORMAL_SIZE_PROFILE_IMAGE);
155
156     buildRequest(GET_LOCATIONS, parameters);
157 }
158
159 void SituareService::imageReceived(const QUrl &url, const QPixmap &image)
160 {
161     qDebug() << __PRETTY_FUNCTION__;
162     qDebug() << "Image URL: " << url << " size :" << image.size();
163
164     // assign facebook silhouette image to all who doesn't have a profile image
165     if(url == QUrl(SILHOUETTE_URL)) {
166         if(m_user->profileImageUrl().isEmpty()) {
167             m_user->setProfileImage(AvatarImage::create(image, AvatarImage::Large));
168             emit imageReady(m_user);
169         }
170         foreach(User *friendItem, m_friendsList) {
171             if(friendItem->profileImageUrl().isEmpty()) {
172                 friendItem->setProfileImage(AvatarImage::create(image, AvatarImage::Small));
173                 emit imageReady(friendItem);
174             }
175         }
176     }
177
178     if (m_user->profileImageUrl() == url) {
179         m_user->setProfileImage(AvatarImage::create(image, AvatarImage::Large));
180         emit imageReady(m_user);
181     }
182
183     foreach(User *friendItem, m_friendsList) {
184         if(friendItem->profileImageUrl() == url) {
185             friendItem->setProfileImage(AvatarImage::create(image, AvatarImage::Small));
186             emit imageReady(friendItem);
187         }
188     }
189 }
190
191 void SituareService::parseUserData(const QByteArray &jsonReply)
192 {
193     qDebug() << __PRETTY_FUNCTION__;
194
195     m_defaultImage = false;
196
197     QJson::Parser parser;
198     bool ok;
199
200     QVariantMap result = parser.parse (jsonReply, &ok).toMap();
201     if (!ok) {
202         emit error(ErrorContext::SITUARE, SituareError::INVALID_JSON);
203         return;
204     } else {
205
206         if(result.contains("ErrorCode")) {
207             QVariant errorVariant = result.value("ErrorCode");
208             emit error(ErrorContext::SITUARE, errorVariant.toInt());
209             return;
210         } else if(result.contains("user")) {
211
212             QVariant userVariant = result.value("user");
213             QMap<QString, QVariant> userMap = userVariant.toMap();
214
215             GeoCoordinate coordinates(userMap["latitude"].toReal(), userMap["longitude"].toReal());
216
217             QUrl imageUrl = userMap[NORMAL_SIZE_PROFILE_IMAGE].toUrl();
218
219             if(imageUrl.isEmpty()) {
220                 // user doesn't have profile image, so we need to get him a silhouette image
221                 m_defaultImage = true;
222             }
223
224             QString address = userMap["address"].toString();
225             if(address.isEmpty()) {
226                 QStringList location;
227                 location.append(QString::number(coordinates.latitude()));
228                 location.append(QString::number(coordinates.longitude()));
229                 address = location.join(", ");
230             }
231
232             User user = User(address, coordinates, userMap["name"].toString(),
233                           userMap["note"].toString(), imageUrl, userMap["timestamp"].toString(),
234                           true, userMap["uid"].toString());
235
236             QList<User> tmpFriendsList;
237
238             foreach (QVariant friendsVariant, result["friends"].toList()) {
239               QMap<QString, QVariant> friendMap = friendsVariant.toMap();
240               QVariant distance = friendMap["distance"];
241               QMap<QString, QVariant> distanceMap = distance.toMap();
242
243               GeoCoordinate coordinates(friendMap["latitude"].toReal(),friendMap["longitude"].toReal());
244
245               QUrl imageUrl = friendMap["profile_pic"].toUrl();
246
247               if(imageUrl.isEmpty()) {
248                   // friend doesn't have profile image, so we need to get him a silhouette image
249                   m_defaultImage = true;
250               }
251
252               QString address = friendMap["address"].toString();
253               if(address.isEmpty()) {
254                   QStringList location;
255                   location.append(QString::number(coordinates.latitude()));
256                   location.append(QString::number(coordinates.longitude()));
257                   address = location.join(", ");
258               }
259
260               User buddy = User(address, coordinates, friendMap["name"].toString(),
261                                friendMap["note"].toString(), imageUrl,
262                                friendMap["timestamp"].toString(),
263                                false, friendMap["uid"].toString(), distanceMap["units"].toString(),
264                                distanceMap["value"].toDouble());
265
266               tmpFriendsList.append(buddy);
267             }
268
269             QList<QUrl> imageUrlList; // url list for images
270
271             // set unchanged profile images or add new images to imageUrlList for downloading
272             if(m_user) {
273                 if(m_user->profileImageUrl() != user.profileImageUrl()) {
274                     if(!user.profileImageUrl().isEmpty())
275                         imageUrlList.append(user.profileImageUrl());
276                 } else {
277                     user.setProfileImage(m_user->profileImage());
278                 }
279             } else {
280                 if(!user.profileImageUrl().isEmpty())
281                     imageUrlList.append(user.profileImageUrl());
282             }
283
284             // clear old user object
285             if(m_user) {
286                 delete m_user;
287                 m_user = 0;
288             }
289
290             // create new user object from temporary user object
291             m_user = new User(user);
292
293             // set unchanged profile images or add new images to imageUrlList for downloading
294             if(!m_friendsList.isEmpty()) {
295                 foreach(User tmpBuddy, tmpFriendsList) {
296                     if(!tmpBuddy.profileImageUrl().isEmpty()) {
297                         bool found = false;
298                         foreach(User *buddy, m_friendsList) {
299                             if(tmpBuddy.profileImageUrl() == buddy->profileImageUrl()) {
300                                 tmpBuddy.setProfileImage(buddy->profileImage());
301                                 found = true;
302                                 break;
303                             }
304                         }
305                         if(!found && !tmpBuddy.profileImageUrl().isEmpty())
306                             imageUrlList.append(tmpBuddy.profileImageUrl());
307                     }
308                 }
309             } else {
310                 foreach(User buddy, tmpFriendsList) {
311                     if(!buddy.profileImageUrl().isEmpty())
312                         imageUrlList.append(buddy.profileImageUrl());
313                 }
314             }
315
316             // clear old friendlist
317             qDeleteAll(m_friendsList.begin(), m_friendsList.end());
318             m_friendsList.clear();
319
320             // populate new friendlist with temporary friendlist's data
321             foreach(User tmpFriendItem, tmpFriendsList) {
322                 User *friendItem = new User(tmpFriendItem);
323                 m_friendsList.append(friendItem);
324             }
325             tmpFriendsList.clear();
326
327             emit userDataChanged(m_user, m_friendsList);
328
329             // set silhouette image to imageUrlList for downloading
330             if(m_defaultImage)
331                 imageUrlList.append(QUrl(SILHOUETTE_URL));
332
333             addProfileImages(imageUrlList);
334             imageUrlList.clear();
335         } else {
336             QVariant address = result.value("address");
337             if(!address.toString().isEmpty()) {
338                 emit reverseGeoReady(address.toString());
339             } else {
340                 QStringList coordinates;
341                 coordinates.append(result.value("lat").toString());
342                 coordinates.append(result.value("lon").toString());
343
344                 emit error(ErrorContext::SITUARE, SituareError::ADDRESS_RETRIEVAL_FAILED);
345                 emit reverseGeoReady(coordinates.join(", "));
346             }
347         }
348     }
349 }
350
351 void SituareService::requestFinished(QNetworkReply *reply)
352 {
353     qDebug() << __PRETTY_FUNCTION__;
354
355     //Reply from situare
356     if (m_currentRequests.contains(reply)) {
357
358         qDebug() << "BytesAvailable: " << reply->bytesAvailable();
359
360         if (reply->error()) {
361             emit error(ErrorContext::NETWORK, reply->error());
362         } else {
363             QByteArray replyArray = reply->readAll();
364             qDebug() << "Reply from: " << reply->url() << "reply " << replyArray;
365
366             if(replyArray == ERROR_LAT.toAscii()) {
367                 qDebug() << "Error: " << ERROR_LAT;
368                 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
369             } else if(replyArray == ERROR_LON.toAscii()) {
370                 qDebug() << "Error: " << ERROR_LON;
371                 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
372             } else if(replyArray.contains(ERROR_SESSION.toAscii())) {
373                 qDebug() << "Error: " << ERROR_SESSION;
374                 emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
375             } else if(replyArray.startsWith(OPENING_BRACE_MARK.toAscii())) {
376                 qDebug() << "JSON string";
377                 parseUserData(replyArray);
378             } else if(replyArray.isEmpty()) {
379                 if(reply->url().toString().contains(UPDATE_LOCATION.toAscii())) {
380                     emit updateWasSuccessful();
381                 } else {
382                     // session credentials are invalid
383                     emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
384                 }
385             } else {
386                 // unknown reply
387                 emit error(ErrorContext::SITUARE, SituareError::ERROR_GENERAL);
388             }
389         }
390         m_currentRequests.removeAll(reply);
391         reply->deleteLater();
392     }
393 }
394
395 void SituareService::reverseGeo(const GeoCoordinate &coordinates)
396 {
397     qDebug() << __PRETTY_FUNCTION__;
398
399     QHash<QString, QString> parameters;
400     parameters.insert("lat", degreesToString(coordinates.latitude()));
401     parameters.insert("lon", degreesToString(coordinates.longitude()));
402     parameters.insert("format", "json");
403
404     buildRequest(REVERSE_GEO, parameters);
405 }
406
407 void SituareService::sendRequest(const QString &requestUrl)
408 {
409     qWarning() << __PRETTY_FUNCTION__ << "requestUrl" << requestUrl;
410
411     // make and send the request
412     QNetworkRequest request;
413     request.setUrl(QUrl(requestUrl));
414     request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
415     QNetworkReply *reply = m_networkManager->get(request, true);
416     m_currentRequests.append(reply);
417 }
418
419 void SituareService::updateSession(const QString &session)
420 {
421     qWarning() << __PRETTY_FUNCTION__;
422
423     m_session = session;
424
425     if (m_session.isEmpty())
426         clearUserData();
427 }
428
429 void SituareService::updateLocation(const GeoCoordinate &coordinates, const QString &status,
430                                     const bool &publish)
431 {
432     qDebug() << __PRETTY_FUNCTION__;
433
434     QHash<QString, QString> parameters;
435     parameters.insert("lat", degreesToString(coordinates.latitude()));
436     parameters.insert("lon", degreesToString(coordinates.longitude()));
437     parameters.insert("publish", publish ? "true" : "false");
438     parameters.insert("data", status); ///< @todo if !empty ???
439
440     buildRequest(UPDATE_LOCATION, parameters);
441 }