87847c4adf9fdc434415ed1c886d1ad72f0f7371
[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
7    Situare is free software; you can redistribute it and/or
8    modify it under the terms of the GNU General Public License
9    version 2 as published by the Free Software Foundation.
10
11    Situare is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with Situare; if not, write to the Free Software
18    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
19    USA.
20 */
21
22 #include <qjson/parser.h>
23
24 #include <QDebug>
25 #include <QNetworkReply>
26 #include <QPixmap>
27 #include <QStringList>
28 #include <QtAlgorithms>
29 #include <QtGlobal>
30
31 #include "error.h"
32 #include "network/networkaccessmanager.h"
33 #include "situarecommon.h"
34 #include "ui/avatarimage.h"
35
36 #include "situareservice.h"
37
38 SituareService::SituareService(QObject *parent)
39         : QObject(parent),
40         m_user(0)
41 {
42     qDebug() << __PRETTY_FUNCTION__;
43
44     m_networkManager = new NetworkAccessManager(this);
45     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)),
46             this, SLOT(requestFinished(QNetworkReply*)), Qt::QueuedConnection);
47
48     m_imageFetcher = new ImageFetcher(new NetworkAccessManager(this), this);
49     connect(this, SIGNAL(fetchImage(QUrl)),
50             m_imageFetcher, SLOT(fetchImage(QUrl)));
51     connect(m_imageFetcher, SIGNAL(imageReceived(QUrl,QPixmap)),
52             this, SLOT(imageReceived(QUrl, QPixmap)));
53     connect(m_imageFetcher, SIGNAL(error(int, int)),
54             this, SIGNAL(error(int, int)));
55 }
56
57 SituareService::~SituareService()
58 {
59     qDebug() << __PRETTY_FUNCTION__;
60
61     if(m_user) {
62         delete m_user;
63         m_user = 0;
64     }
65
66     qDeleteAll(m_friendsList.begin(), m_friendsList.end());
67     m_friendsList.clear();
68 }
69
70 void SituareService::addProfileImages(const QList<QUrl> &imageUrlList)
71 {
72     qDebug() << __PRETTY_FUNCTION__;
73
74     foreach(QUrl url, imageUrlList) {
75         emit fetchImage(url);
76     }
77 }
78
79 void SituareService::appendAccessToken(QString &requestUrl)
80 {
81     qWarning() << __PRETTY_FUNCTION__;
82
83 //    requestUrl.append("access_token=");
84     requestUrl.append(m_session);
85
86 //    qWarning() << __PRETTY_FUNCTION__ << "request url with parameters and access token:" << requestUrl;
87 }
88
89 void SituareService::buildRequest(const QString &script, const QHash<QString, QString> &parameters)
90 {
91     qWarning() << __PRETTY_FUNCTION__;
92
93     const QString PARAMETER_KEY_API = "api";
94     const QString PARAMETER_VALUE_API = "2.0";
95
96     QString url = SITUARE_URL;
97     url.append(script);
98     url.append("?");
99
100     // append default api version parameter if not yet specified
101     if (!parameters.contains(PARAMETER_KEY_API))
102         url.append(PARAMETER_KEY_API + "=" + PARAMETER_VALUE_API + "&");
103
104     // append parameters
105     if (!parameters.isEmpty()) {
106         QHash<QString, QString>::const_iterator i = parameters.constBegin();
107         while (i != parameters.constEnd()) {
108             url.append(i.key());
109             url.append("=");
110             url.append(i.value());
111             url.append("&");
112             i++;
113         }
114     }
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         m_requestsWaitingAccessToken.append(url);
123         ///< @todo emit login request
124     }
125 }
126
127 void SituareService::clearUserData()
128 {
129     qDebug() << __PRETTY_FUNCTION__;
130
131     qDeleteAll(m_friendsList.begin(), m_friendsList.end());
132     m_friendsList.clear();
133
134     if(m_user) {
135         delete m_user;
136         m_user = 0;
137     }
138     emit userDataChanged(m_user, m_friendsList);
139 }
140
141 QString SituareService::degreesToString(double degrees)
142 {
143     qDebug() << __PRETTY_FUNCTION__;
144
145     // one scene pixel is about 5.4e-6 degrees, the integer part is max three digits and one
146     // additional digit is added for maximum precision
147     const int PRECISION = 10;
148
149     return QString::number(degrees, 'f', PRECISION);
150 }
151
152 void SituareService::fetchLocations()
153 {
154     qDebug() << __PRETTY_FUNCTION__;
155
156     QHash<QString, QString> parameters;
157     parameters.insert("extra_user_data", NORMAL_SIZE_PROFILE_IMAGE);
158
159     buildRequest(GET_LOCATIONS, parameters);
160 }
161
162 QString SituareService::formCookie(const QString &apiKeyValue, QString expiresValue,
163                                    QString userValue, QString sessionKeyValue,
164                                    QString sessionSecretValue, const QString &signatureValue,
165                                    const QString &localeValue)
166 {
167     qDebug() << __PRETTY_FUNCTION__;
168
169     QString cookie;
170     QString apiKey;
171     QString user;
172     QString expires;
173     QString sessionKey;
174     QString sessionSecret;
175     QString locale;
176     QString variable;
177     QString signature = EQUAL_MARK;
178     QStringList variableList;
179
180     signature.append(signatureValue);
181     apiKey.append(apiKeyValue);
182     apiKey.append(UNDERLINE_MARK);
183
184     user.append(USER);
185     user.append(EQUAL_MARK);
186     expires.append(EXPIRES);
187     expires.append(EQUAL_MARK);
188     sessionKey.append(SESSION_KEY);
189     sessionKey.append(EQUAL_MARK);
190     sessionSecret.append(SESSION_SECRET);
191     sessionSecret.append(EQUAL_MARK);
192     locale.append(LOCALE);
193     locale.append(EQUAL_MARK);
194     locale.append(localeValue);
195
196     variableList.append(expires.append(expiresValue.append(BREAK_MARK)));
197     variableList.append(sessionKey.append(sessionKeyValue.append(BREAK_MARK)));
198     variableList.append(user.append(userValue).append(BREAK_MARK));
199     variableList.append(sessionSecret.append(sessionSecretValue.append(BREAK_MARK)));
200
201     cookie.append(BREAK_MARK);
202
203     foreach(variable, variableList) {
204         cookie.append(apiKey);
205         cookie.append(variable);
206     }
207     apiKey.remove(UNDERLINE_MARK);
208     cookie.append(apiKey);
209     cookie.append(signature);
210     cookie.append(BREAK_MARK);
211     cookie.append(locale);
212
213     qDebug() << cookie;
214
215     return cookie;
216 }
217
218 QUrl SituareService::formUrl(const QString &baseUrl, const QString &phpScript,
219                              QString urlParameters)
220 {
221     qDebug() << __PRETTY_FUNCTION__;
222     QString urlString;
223
224     urlString.append(baseUrl);
225     urlString.append(phpScript);
226     if(!urlParameters.isEmpty())
227         urlString.append(urlParameters);
228
229     QUrl url = QUrl(urlString);
230
231     qDebug() << url;
232
233     return url;
234 }
235
236 QString SituareService::formUrlParameters(const GeoCoordinate &coordinates, QString status,
237                                           bool publish)
238 {
239     qDebug() << __PRETTY_FUNCTION__;
240
241     // one scene pixel is about 5.4e-6 degrees, the integer part is max three digits and one
242     // additional digit is added for maximum precision
243     const int COORDINATE_PRECISION = 10;
244
245     QString parameters;
246
247     parameters.append(QUESTION_MARK);
248     parameters.append(LATITUDE);
249     parameters.append(EQUAL_MARK);
250     parameters.append(QString::number(coordinates.latitude(), 'f', COORDINATE_PRECISION));
251     parameters.append(AMBERSAND_MARK);
252     parameters.append(LONGTITUDE);
253     parameters.append(EQUAL_MARK);
254     parameters.append(QString::number(coordinates.longitude(), 'f', COORDINATE_PRECISION));
255
256     parameters.append(AMBERSAND_MARK);
257     parameters.append(PUBLISH);
258     parameters.append(EQUAL_MARK);
259
260     if(publish)
261         parameters.append(PUBLISH_TRUE);
262     else
263         parameters.append(PUBLISH_FALSE);
264
265     if(!status.isEmpty()) {
266         parameters.append(AMBERSAND_MARK);
267         parameters.append(DATA);
268         parameters.append(EQUAL_MARK);
269         parameters.append(status);
270     }
271
272     return parameters;
273 }
274
275 void SituareService::imageReceived(const QUrl &url, const QPixmap &image)
276 {
277     qDebug() << __PRETTY_FUNCTION__;
278     qDebug() << "Image URL: " << url << " size :" << image.size();
279
280     // assign facebook silhouette image to all who doesn't have a profile image
281     if(url == QUrl(SILHOUETTE_URL)) {
282         if(m_user->profileImageUrl().isEmpty()) {
283             m_user->setProfileImage(AvatarImage::create(image, AvatarImage::Large));
284             emit imageReady(m_user);
285         }
286         foreach(User *friendItem, m_friendsList) {
287             if(friendItem->profileImageUrl().isEmpty()) {
288                 friendItem->setProfileImage(AvatarImage::create(image, AvatarImage::Small));
289                 emit imageReady(friendItem);
290             }
291         }
292     }
293
294     if (m_user->profileImageUrl() == url) {
295         m_user->setProfileImage(AvatarImage::create(image, AvatarImage::Large));
296         emit imageReady(m_user);
297     }
298
299     foreach(User *friendItem, m_friendsList) {
300         if(friendItem->profileImageUrl() == url) {
301             friendItem->setProfileImage(AvatarImage::create(image, AvatarImage::Small));
302             emit imageReady(friendItem);
303         }
304     }
305 }
306
307 void SituareService::parseUserData(const QByteArray &jsonReply)
308 {
309     qDebug() << __PRETTY_FUNCTION__;
310
311     qWarning() << __PRETTY_FUNCTION__ << "Server reply:" << jsonReply;
312
313     m_defaultImage = false;
314
315     QJson::Parser parser;
316     bool ok;
317
318     QVariantMap result = parser.parse (jsonReply, &ok).toMap();
319     if (!ok) {
320         emit error(ErrorContext::SITUARE, SituareError::INVALID_JSON);
321         return;
322     } else {
323
324         if(result.contains("ErrorCode")) {
325             QVariant errorVariant = result.value("ErrorCode");
326             emit error(ErrorContext::SITUARE, errorVariant.toInt());
327             return;
328         } else if(result.contains("user")) {
329
330             QVariant userVariant = result.value("user");
331             QMap<QString, QVariant> userMap = userVariant.toMap();
332
333             GeoCoordinate coordinates(userMap["latitude"].toReal(), userMap["longitude"].toReal());
334
335             QUrl imageUrl = userMap[NORMAL_SIZE_PROFILE_IMAGE].toUrl();
336
337             if(imageUrl.isEmpty()) {
338                 // user doesn't have profile image, so we need to get him a silhouette image
339                 m_defaultImage = true;
340             }
341
342             QString address = userMap["address"].toString();
343             if(address.isEmpty()) {
344                 QStringList location;
345                 location.append(QString::number(coordinates.latitude()));
346                 location.append(QString::number(coordinates.longitude()));
347                 address = location.join(", ");
348             }
349
350             User user = User(address, coordinates, userMap["name"].toString(),
351                           userMap["note"].toString(), imageUrl, userMap["timestamp"].toString(),
352                           true, userMap["uid"].toString());
353
354             QList<User> tmpFriendsList;
355
356             foreach (QVariant friendsVariant, result["friends"].toList()) {
357               QMap<QString, QVariant> friendMap = friendsVariant.toMap();
358               QVariant distance = friendMap["distance"];
359               QMap<QString, QVariant> distanceMap = distance.toMap();
360
361               GeoCoordinate coordinates(friendMap["latitude"].toReal(),friendMap["longitude"].toReal());
362
363               QUrl imageUrl = friendMap["profile_pic"].toUrl();
364
365               if(imageUrl.isEmpty()) {
366                   // friend doesn't have profile image, so we need to get him a silhouette image
367                   m_defaultImage = true;
368               }
369
370               QString address = friendMap["address"].toString();
371               if(address.isEmpty()) {
372                   QStringList location;
373                   location.append(QString::number(coordinates.latitude()));
374                   location.append(QString::number(coordinates.longitude()));
375                   address = location.join(", ");
376               }
377
378               User buddy = User(address, coordinates, friendMap["name"].toString(),
379                                friendMap["note"].toString(), imageUrl,
380                                friendMap["timestamp"].toString(),
381                                false, friendMap["uid"].toString(), distanceMap["units"].toString(),
382                                distanceMap["value"].toDouble());
383
384               tmpFriendsList.append(buddy);
385             }
386
387             QList<QUrl> imageUrlList; // url list for images
388
389             // set unchanged profile images or add new images to imageUrlList for downloading
390             if(m_user) {
391                 if(m_user->profileImageUrl() != user.profileImageUrl()) {
392                     if(!user.profileImageUrl().isEmpty())
393                         imageUrlList.append(user.profileImageUrl());
394                 } else {
395                     user.setProfileImage(m_user->profileImage());
396                 }
397             } else {
398                 if(!user.profileImageUrl().isEmpty())
399                     imageUrlList.append(user.profileImageUrl());
400             }
401
402             // clear old user object
403             if(m_user) {
404                 delete m_user;
405                 m_user = 0;
406             }
407
408             // create new user object from temporary user object
409             m_user = new User(user);
410
411             // set unchanged profile images or add new images to imageUrlList for downloading
412             if(!m_friendsList.isEmpty()) {
413                 foreach(User tmpBuddy, tmpFriendsList) {
414                     if(!tmpBuddy.profileImageUrl().isEmpty()) {
415                         bool found = false;
416                         foreach(User *buddy, m_friendsList) {
417                             if(tmpBuddy.profileImageUrl() == buddy->profileImageUrl()) {
418                                 tmpBuddy.setProfileImage(buddy->profileImage());
419                                 found = true;
420                                 break;
421                             }
422                         }
423                         if(!found && !tmpBuddy.profileImageUrl().isEmpty())
424                             imageUrlList.append(tmpBuddy.profileImageUrl());
425                     }
426                 }
427             } else {
428                 foreach(User buddy, tmpFriendsList) {
429                     if(!buddy.profileImageUrl().isEmpty())
430                         imageUrlList.append(buddy.profileImageUrl());
431                 }
432             }
433
434             // clear old friendlist
435             qDeleteAll(m_friendsList.begin(), m_friendsList.end());
436             m_friendsList.clear();
437
438             // populate new friendlist with temporary friendlist's data
439             foreach(User tmpFriendItem, tmpFriendsList) {
440                 User *friendItem = new User(tmpFriendItem);
441                 m_friendsList.append(friendItem);
442             }
443             tmpFriendsList.clear();
444
445             emit userDataChanged(m_user, m_friendsList);
446
447             // set silhouette image to imageUrlList for downloading
448             if(m_defaultImage)
449                 imageUrlList.append(QUrl(SILHOUETTE_URL));
450
451             addProfileImages(imageUrlList);
452             imageUrlList.clear();
453         } else {
454             QVariant address = result.value("address");
455             if(!address.toString().isEmpty()) {
456                 emit reverseGeoReady(address.toString());
457             } else {
458                 QStringList coordinates;
459                 coordinates.append(result.value("lat").toString());
460                 coordinates.append(result.value("lon").toString());
461
462                 emit error(ErrorContext::SITUARE, SituareError::ADDRESS_RETRIEVAL_FAILED);
463                 emit reverseGeoReady(coordinates.join(", "));
464             }
465         }
466     }
467 }
468
469 void SituareService::requestFinished(QNetworkReply *reply)
470 {
471     qDebug() << __PRETTY_FUNCTION__;
472
473     //Reply from situare
474     if (m_currentRequests.contains(reply)) {
475
476         qDebug() << "BytesAvailable: " << reply->bytesAvailable();
477
478         if (reply->error()) {
479             emit error(ErrorContext::NETWORK, reply->error());
480         } else {
481             QByteArray replyArray = reply->readAll();
482             qDebug() << "Reply from: " << reply->url() << "reply " << replyArray;
483
484             if(replyArray == ERROR_LAT.toAscii()) {
485                 qDebug() << "Error: " << ERROR_LAT;
486                 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
487             } else if(replyArray == ERROR_LON.toAscii()) {
488                 qDebug() << "Error: " << ERROR_LON;
489                 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
490             } else if(replyArray.contains(ERROR_SESSION.toAscii())) {
491                 qDebug() << "Error: " << ERROR_SESSION;
492                 emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
493             } else if(replyArray.startsWith(OPENING_BRACE_MARK.toAscii())) {
494                 qDebug() << "JSON string";
495                 parseUserData(replyArray);
496             } else if(replyArray.isEmpty()) {
497                 if(reply->url().toString().contains(UPDATE_LOCATION.toAscii())) {
498                     emit updateWasSuccessful();
499                 } else {
500                     // session credentials are invalid
501                     emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
502                 }
503             } else {
504                 // unknown reply
505                 emit error(ErrorContext::SITUARE, SituareError::ERROR_GENERAL);
506             }
507         }
508         m_currentRequests.removeAll(reply);
509         reply->deleteLater();
510     }
511 }
512
513 void SituareService::reverseGeo(const GeoCoordinate &coordinates)
514 {
515     qDebug() << __PRETTY_FUNCTION__;
516
517     QHash<QString, QString> parameters;
518     parameters.insert("lat", degreesToString(coordinates.latitude()));
519     parameters.insert("lon", degreesToString(coordinates.longitude()));
520     parameters.insert("format", "json");
521
522     buildRequest(REVERSE_GEO, parameters);
523 }
524
525 void SituareService::sendRequest(const QUrl &url, const QString &cookieType, const QString &cookie)
526 {
527     qDebug() << __PRETTY_FUNCTION__;
528
529     QNetworkRequest request;
530
531     request.setUrl(url);
532     request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
533     request.setRawHeader(cookieType.toAscii(), cookie.toUtf8());
534
535     QNetworkReply *reply = m_networkManager->get(request, true);
536
537     m_currentRequests.append(reply);
538 }
539
540 void SituareService::sendRequest(const QString &requestUrl)
541 {
542     qWarning() << __PRETTY_FUNCTION__ << "requestUrl" << requestUrl;
543
544     // make and send the request
545     QNetworkRequest request;
546     request.setUrl(QUrl(requestUrl));
547     request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
548     QNetworkReply *reply = m_networkManager->get(request, true);
549     m_currentRequests.append(reply);
550 }
551
552 void SituareService::updateSession(const QString &session)
553 {
554     qWarning() << __PRETTY_FUNCTION__;
555
556     m_session = session;
557
558     foreach (QString request, m_requestsWaitingAccessToken) {
559         appendAccessToken(request);
560         sendRequest(request);
561     }
562
563     m_requestsWaitingAccessToken.clear();
564 }
565
566 void SituareService::updateLocation(const GeoCoordinate &coordinates, const QString &status,
567                                     const bool &publish)
568 {
569     qDebug() << __PRETTY_FUNCTION__;
570
571     QHash<QString, QString> parameters;
572     parameters.insert("lat", degreesToString(coordinates.latitude()));
573     parameters.insert("lon", degreesToString(coordinates.longitude()));
574     parameters.insert("publish", publish ? "true" : "false");
575     parameters.insert("data", status); ///< @todo if !empty ???
576
577     buildRequest(UPDATE_LOCATION, parameters);
578 }