Cleaned debug prints
[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     qDebug() << __PRETTY_FUNCTION__;
83
84     requestUrl.append(m_session);
85 }
86
87 void SituareService::buildRequest(const QString &script, const QHash<QString, QString> &parameters)
88 {
89     qDebug() << __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     /// @todo BUG: Url parameter strings are not url escaped
115
116 //    qWarning() << __PRETTY_FUNCTION__ << "request url with parameters:" << url;
117
118     if (!m_session.isEmpty()) {
119         appendAccessToken(url);
120         sendRequest(url);
121     } else {
122         emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
123     }
124 }
125
126 void SituareService::clearUserData()
127 {
128     qDebug() << __PRETTY_FUNCTION__;
129
130     qDeleteAll(m_friendsList.begin(), m_friendsList.end());
131     m_friendsList.clear();
132
133     if(m_user) {
134         delete m_user;
135         m_user = 0;
136     }
137     emit userDataChanged(m_user, m_friendsList);
138 }
139
140 QString SituareService::degreesToString(double degrees)
141 {
142     qDebug() << __PRETTY_FUNCTION__;
143
144     // one scene pixel is about 5.4e-6 degrees, the integer part is max three digits and one
145     // additional digit is added for maximum precision
146     const int PRECISION = 10;
147
148     return QString::number(degrees, 'f', PRECISION);
149 }
150
151 void SituareService::fetchLocations()
152 {
153     qDebug() << __PRETTY_FUNCTION__;
154
155     QHash<QString, QString> parameters;
156     parameters.insert("extra_user_data", NORMAL_SIZE_PROFILE_IMAGE);
157
158     buildRequest(GET_LOCATIONS, parameters);
159 }
160
161 void SituareService::imageReceived(const QUrl &url, const QPixmap &image)
162 {
163     qDebug() << __PRETTY_FUNCTION__;
164     qDebug() << "Image URL: " << url << " size :" << image.size();
165
166     // assign facebook silhouette image to all who doesn't have a profile image
167     if(url == QUrl(SILHOUETTE_URL)) {
168         if(m_user->profileImageUrl().isEmpty()) {
169             m_user->setProfileImage(AvatarImage::create(image, AvatarImage::Large));
170             emit imageReady(m_user);
171         }
172         foreach(User *friendItem, m_friendsList) {
173             if(friendItem->profileImageUrl().isEmpty()) {
174                 friendItem->setProfileImage(AvatarImage::create(image, AvatarImage::Small));
175                 emit imageReady(friendItem);
176             }
177         }
178     }
179
180     if (m_user->profileImageUrl() == url) {
181         m_user->setProfileImage(AvatarImage::create(image, AvatarImage::Large));
182         emit imageReady(m_user);
183     }
184
185     foreach(User *friendItem, m_friendsList) {
186         if(friendItem->profileImageUrl() == url) {
187             friendItem->setProfileImage(AvatarImage::create(image, AvatarImage::Small));
188             emit imageReady(friendItem);
189         }
190     }
191 }
192
193 void SituareService::parseUserData(const QByteArray &jsonReply)
194 {
195     qDebug() << __PRETTY_FUNCTION__;
196
197     m_defaultImage = false;
198
199     QJson::Parser parser;
200     bool ok;
201
202     QVariantMap result = parser.parse (jsonReply, &ok).toMap();
203     if (!ok) {
204         emit error(ErrorContext::SITUARE, SituareError::INVALID_JSON);
205         return;
206     } else {
207
208         if(result.contains("ErrorCode")) {
209             QVariant errorVariant = result.value("ErrorCode");
210             emit error(ErrorContext::SITUARE, errorVariant.toInt());
211             return;
212         } else if(result.contains("user")) {
213
214             QVariant userVariant = result.value("user");
215             QMap<QString, QVariant> userMap = userVariant.toMap();
216
217             GeoCoordinate coordinates(userMap["latitude"].toReal(), userMap["longitude"].toReal());
218
219             QUrl imageUrl = userMap[NORMAL_SIZE_PROFILE_IMAGE].toUrl();
220
221             if(imageUrl.isEmpty()) {
222                 // user doesn't have profile image, so we need to get him a silhouette image
223                 m_defaultImage = true;
224             }
225
226             QString address = userMap["address"].toString();
227             if(address.isEmpty()) {
228                 QStringList location;
229                 location.append(QString::number(coordinates.latitude()));
230                 location.append(QString::number(coordinates.longitude()));
231                 address = location.join(", ");
232             }
233
234             User user = User(address, coordinates, userMap["name"].toString(),
235                           userMap["note"].toString(), imageUrl, userMap["timestamp"].toString(),
236                           true, userMap["uid"].toString());
237
238             QList<User> tmpFriendsList;
239
240             foreach (QVariant friendsVariant, result["friends"].toList()) {
241               QMap<QString, QVariant> friendMap = friendsVariant.toMap();
242               QVariant distance = friendMap["distance"];
243               QMap<QString, QVariant> distanceMap = distance.toMap();
244
245               GeoCoordinate coordinates(friendMap["latitude"].toReal(),friendMap["longitude"].toReal());
246
247               QUrl imageUrl = friendMap["profile_pic"].toUrl();
248
249               if(imageUrl.isEmpty()) {
250                   // friend doesn't have profile image, so we need to get him a silhouette image
251                   m_defaultImage = true;
252               }
253
254               QString address = friendMap["address"].toString();
255               if(address.isEmpty()) {
256                   QStringList location;
257                   location.append(QString::number(coordinates.latitude()));
258                   location.append(QString::number(coordinates.longitude()));
259                   address = location.join(", ");
260               }
261
262               User buddy = User(address, coordinates, friendMap["name"].toString(),
263                                friendMap["note"].toString(), imageUrl,
264                                friendMap["timestamp"].toString(),
265                                false, friendMap["uid"].toString(), distanceMap["units"].toString(),
266                                distanceMap["value"].toDouble());
267
268               tmpFriendsList.append(buddy);
269             }
270
271             QList<QUrl> imageUrlList; // url list for images
272
273             // set unchanged profile images or add new images to imageUrlList for downloading
274             if(m_user) {
275                 if(m_user->profileImageUrl() != user.profileImageUrl()) {
276                     if(!user.profileImageUrl().isEmpty())
277                         imageUrlList.append(user.profileImageUrl());
278                 } else {
279                     user.setProfileImage(m_user->profileImage());
280                 }
281             } else {
282                 if(!user.profileImageUrl().isEmpty())
283                     imageUrlList.append(user.profileImageUrl());
284             }
285
286             // clear old user object
287             if(m_user) {
288                 delete m_user;
289                 m_user = 0;
290             }
291
292             // create new user object from temporary user object
293             m_user = new User(user);
294
295             // set unchanged profile images or add new images to imageUrlList for downloading
296             if(!m_friendsList.isEmpty()) {
297                 foreach(User tmpBuddy, tmpFriendsList) {
298                     if(!tmpBuddy.profileImageUrl().isEmpty()) {
299                         bool found = false;
300                         foreach(User *buddy, m_friendsList) {
301                             if(tmpBuddy.profileImageUrl() == buddy->profileImageUrl()) {
302                                 tmpBuddy.setProfileImage(buddy->profileImage());
303                                 found = true;
304                                 break;
305                             }
306                         }
307                         if(!found && !tmpBuddy.profileImageUrl().isEmpty())
308                             imageUrlList.append(tmpBuddy.profileImageUrl());
309                     }
310                 }
311             } else {
312                 foreach(User buddy, tmpFriendsList) {
313                     if(!buddy.profileImageUrl().isEmpty())
314                         imageUrlList.append(buddy.profileImageUrl());
315                 }
316             }
317
318             // clear old friendlist
319             qDeleteAll(m_friendsList.begin(), m_friendsList.end());
320             m_friendsList.clear();
321
322             // populate new friendlist with temporary friendlist's data
323             foreach(User tmpFriendItem, tmpFriendsList) {
324                 User *friendItem = new User(tmpFriendItem);
325                 m_friendsList.append(friendItem);
326             }
327             tmpFriendsList.clear();
328
329             emit userDataChanged(m_user, m_friendsList);
330
331             // set silhouette image to imageUrlList for downloading
332             if(m_defaultImage)
333                 imageUrlList.append(QUrl(SILHOUETTE_URL));
334
335             addProfileImages(imageUrlList);
336             imageUrlList.clear();
337         } else {
338             QVariant address = result.value("address");
339             if(!address.toString().isEmpty()) {
340                 emit reverseGeoReady(address.toString());
341             } else {
342                 QStringList coordinates;
343                 coordinates.append(result.value("lat").toString());
344                 coordinates.append(result.value("lon").toString());
345
346                 emit error(ErrorContext::SITUARE, SituareError::ADDRESS_RETRIEVAL_FAILED);
347                 emit reverseGeoReady(coordinates.join(", "));
348             }
349         }
350     }
351 }
352
353 void SituareService::requestFinished(QNetworkReply *reply)
354 {
355     qDebug() << __PRETTY_FUNCTION__;
356
357     //Reply from situare
358     if (m_currentRequests.contains(reply)) {
359
360         qDebug() << "BytesAvailable: " << reply->bytesAvailable();
361
362         if (reply->error()) {
363             emit error(ErrorContext::NETWORK, reply->error());
364         } else {
365             QByteArray replyArray = reply->readAll();
366             qDebug() << "Reply from: " << reply->url() << "reply " << replyArray;
367
368             if(replyArray == ERROR_LAT.toAscii()) {
369                 qDebug() << "Error: " << ERROR_LAT;
370                 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
371             } else if(replyArray == ERROR_LON.toAscii()) {
372                 qDebug() << "Error: " << ERROR_LON;
373                 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
374             } else if(replyArray.contains(ERROR_SESSION.toAscii())) {
375                 qDebug() << "Error: " << ERROR_SESSION;
376                 emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
377             } else if(replyArray.startsWith(OPENING_BRACE_MARK.toAscii())) {
378                 qDebug() << "JSON string";
379                 parseUserData(replyArray);
380             } else if(replyArray.isEmpty()) {
381                 if(reply->url().toString().contains(UPDATE_LOCATION.toAscii())) {
382                     emit updateWasSuccessful();
383                 } else {
384                     // session credentials are invalid
385                     emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
386                 }
387             } else {
388                 // unknown reply
389                 emit error(ErrorContext::SITUARE, SituareError::ERROR_GENERAL);
390             }
391         }
392         m_currentRequests.removeAll(reply);
393         reply->deleteLater();
394     }
395 }
396
397 void SituareService::reverseGeo(const GeoCoordinate &coordinates)
398 {
399     qDebug() << __PRETTY_FUNCTION__;
400
401     QHash<QString, QString> parameters;
402     parameters.insert("lat", degreesToString(coordinates.latitude()));
403     parameters.insert("lon", degreesToString(coordinates.longitude()));
404     parameters.insert("format", "json");
405
406     buildRequest(REVERSE_GEO, parameters);
407 }
408
409 void SituareService::sendRequest(const QString &requestUrl)
410 {
411     qDebug() << __PRETTY_FUNCTION__ << "requestUrl" << requestUrl;
412
413     // make and send the request
414     QNetworkRequest request;
415     request.setUrl(QUrl(requestUrl));
416     request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
417     QNetworkReply *reply = m_networkManager->get(request, true);
418     m_currentRequests.append(reply);
419 }
420
421 void SituareService::updateSession(const QString &session)
422 {
423     qDebug() << __PRETTY_FUNCTION__;
424
425     m_session = session;
426
427     if (m_session.isEmpty())
428         clearUserData();
429 }
430
431 void SituareService::updateLocation(const GeoCoordinate &coordinates, const QString &status,
432                                     const bool &publish)
433 {
434     qDebug() << __PRETTY_FUNCTION__;
435
436     QHash<QString, QString> parameters;
437     parameters.insert("lat", degreesToString(coordinates.latitude()));
438     parameters.insert("lon", degreesToString(coordinates.longitude()));
439     parameters.insert("publish", publish ? "true" : "false");
440     parameters.insert("data", status); ///< @todo if !empty ???
441
442     buildRequest(UPDATE_LOCATION, parameters);
443 }