Fixed projects to build in Simulator
[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 <QtAlgorithms>
25 #include <QDebug>
26 #include <QNetworkReply>
27 #include <QPixmap>
28 #include <QStringList>
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::fetchLocations()
71 {
72     qDebug() << __PRETTY_FUNCTION__;
73
74     QString cookie = formCookie(API_KEY, m_credentials.expires(), m_credentials.userID(),
75                                 m_credentials.sessionKey(), m_credentials.sessionSecret(),
76                                 m_credentials.sig(), EN_LOCALE);
77
78     QUrl url = formUrl(SITUARE_URL, GET_LOCATIONS);
79     sendRequest(url, COOKIE, cookie);
80 }
81
82 void SituareService::reverseGeo(const GeoCoordinate &coordinates)
83 {
84     qDebug() << __PRETTY_FUNCTION__;
85
86     QString cookie = formCookie(API_KEY, m_credentials.expires(),m_credentials.userID(),
87                                 m_credentials.sessionKey(), m_credentials.sessionSecret(),
88                                 m_credentials.sig(), EN_LOCALE);
89
90     QString urlParameters = formUrlParameters(coordinates);
91     urlParameters.append(JSON_FORMAT);
92     QUrl url = formUrl(SITUARE_URL, REVERSE_GEO, urlParameters);
93
94     sendRequest(url, COOKIE, cookie);
95 }
96
97 void SituareService::updateLocation(const GeoCoordinate &coordinates, const QString &status,
98                                     const bool &publish)
99 {
100     qDebug() << __PRETTY_FUNCTION__;
101
102     QString urlParameters = formUrlParameters(coordinates, status, publish);
103     QUrl url = formUrl(SITUARE_URL, UPDATE_LOCATION, urlParameters);
104
105     QString cookie = formCookie(API_KEY, m_credentials.expires(), m_credentials.userID(),
106                                 m_credentials.sessionKey(), m_credentials.sessionSecret(),
107                                 m_credentials.sig(), EN_LOCALE);
108
109     sendRequest(url, COOKIE, cookie);
110 }
111
112 QString SituareService::formCookie(const QString &apiKeyValue, QString expiresValue,
113                                    QString userValue, QString sessionKeyValue,
114                                    QString sessionSecretValue, const QString &signatureValue,
115                                    const QString &localeValue)
116 {
117     qDebug() << __PRETTY_FUNCTION__;
118
119     QString cookie;
120     QString apiKey;
121     QString user;
122     QString expires;
123     QString sessionKey;
124     QString sessionSecret;
125     QString locale;
126     QString variable;
127     QString signature = EQUAL_MARK;
128     QStringList variableList;
129
130     signature.append(signatureValue);
131     apiKey.append(apiKeyValue);
132     apiKey.append(UNDERLINE_MARK);
133
134     user.append(USER);
135     user.append(EQUAL_MARK);
136     expires.append(EXPIRES);
137     expires.append(EQUAL_MARK);
138     sessionKey.append(SESSION_KEY);
139     sessionKey.append(EQUAL_MARK);
140     sessionSecret.append(SESSION_SECRET);
141     sessionSecret.append(EQUAL_MARK);
142     locale.append(LOCALE);
143     locale.append(EQUAL_MARK);
144     locale.append(localeValue);
145
146     variableList.append(expires.append(expiresValue.append(BREAK_MARK)));
147     variableList.append(sessionKey.append(sessionKeyValue.append(BREAK_MARK)));
148     variableList.append(user.append(userValue).append(BREAK_MARK));
149     variableList.append(sessionSecret.append(sessionSecretValue.append(BREAK_MARK)));
150
151     cookie.append(BREAK_MARK);
152
153     foreach(variable, variableList) {
154         cookie.append(apiKey);
155         cookie.append(variable);
156     }
157     apiKey.remove(UNDERLINE_MARK);
158     cookie.append(apiKey);
159     cookie.append(signature);
160     cookie.append(BREAK_MARK);
161     cookie.append(locale);
162
163     qDebug() << cookie;
164
165     return cookie;
166 }
167
168 QUrl SituareService::formUrl(const QString &baseUrl, const QString &phpScript,
169                              QString urlParameters)
170 {
171     qDebug() << __PRETTY_FUNCTION__;
172     QString urlString;
173
174     urlString.append(baseUrl);
175     urlString.append(phpScript);
176     if(!urlParameters.isEmpty())
177         urlString.append(urlParameters);
178
179     QUrl url = QUrl(urlString);
180
181     qDebug() << url;
182
183     return url;
184 }
185
186 QString SituareService::formUrlParameters(const GeoCoordinate &coordinates, QString status,
187                                           bool publish)
188 {
189     qDebug() << __PRETTY_FUNCTION__;
190
191     // one scene pixel is about 5.4e-6 degrees, the integer part is max three digits and one
192     // additional digit is added for maximum precision
193     const int COORDINATE_PRECISION = 10;
194
195     QString parameters;
196
197     parameters.append(QUESTION_MARK);
198     parameters.append(LATITUDE);
199     parameters.append(EQUAL_MARK);
200     parameters.append(QString::number(coordinates.latitude(), 'f', COORDINATE_PRECISION));
201     parameters.append(AMBERSAND_MARK);
202     parameters.append(LONGTITUDE);
203     parameters.append(EQUAL_MARK);
204     parameters.append(QString::number(coordinates.longitude(), 'f', COORDINATE_PRECISION));
205
206     parameters.append(AMBERSAND_MARK);
207     parameters.append(PUBLISH);
208     parameters.append(EQUAL_MARK);
209
210     if(publish)
211         parameters.append(PUBLISH_TRUE);
212     else
213         parameters.append(PUBLISH_FALSE);
214
215     if(!status.isEmpty()) {
216         parameters.append(AMBERSAND_MARK);
217         parameters.append(DATA);
218         parameters.append(EQUAL_MARK);
219         parameters.append(status);
220     }
221
222     return parameters;
223 }
224
225 void SituareService::sendRequest(const QUrl &url, const QString &cookieType, const QString &cookie)
226 {
227     qDebug() << __PRETTY_FUNCTION__;
228
229     QNetworkRequest request;
230
231     request.setUrl(url);
232     request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
233     request.setRawHeader(cookieType.toAscii(), cookie.toUtf8());
234
235     QNetworkReply *reply = m_networkManager->get(request, true);
236
237     m_currentRequests.append(reply);
238 }
239
240 void SituareService::requestFinished(QNetworkReply *reply)
241 {
242     qDebug() << __PRETTY_FUNCTION__;
243
244     //Reply from situare
245     if (m_currentRequests.contains(reply)) {
246
247         qDebug() << "BytesAvailable: " << reply->bytesAvailable();
248
249         if (reply->error()) {
250             emit error(ErrorContext::NETWORK, reply->error());
251         } else {
252             QByteArray replyArray = reply->readAll();
253             qDebug() << "Reply from: " << reply->url() << "reply " << replyArray;
254
255             if(replyArray == ERROR_LAT.toAscii()) {
256                 qDebug() << "Error: " << ERROR_LAT;
257                 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
258             } else if(replyArray == ERROR_LON.toAscii()) {
259                 qDebug() << "Error: " << ERROR_LON;
260                 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
261             } else if(replyArray.contains(ERROR_SESSION.toAscii())) {
262                 qDebug() << "Error: " << ERROR_SESSION;
263                 emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
264             } else if(replyArray.startsWith(OPENING_BRACE_MARK.toAscii())) {
265                 qDebug() << "JSON string";
266                 parseUserData(replyArray);
267             } else if(replyArray.isEmpty()) {
268                 if(reply->url().toString().contains(UPDATE_LOCATION.toAscii())) {
269                     emit updateWasSuccessful();
270                 } else {
271                     // session credentials are invalid
272                     emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
273                 }
274             } else {
275                 // unknown reply
276                 emit error(ErrorContext::SITUARE, SituareError::ERROR_GENERAL);
277             }
278         }
279         m_currentRequests.removeAll(reply);
280         reply->deleteLater();
281     }
282 }
283
284 void SituareService::credentialsReady(const FacebookCredentials &credentials)
285 {
286     qDebug() << __PRETTY_FUNCTION__;
287
288     m_credentials = credentials;
289 }
290
291 void SituareService::parseUserData(const QByteArray &jsonReply)
292 {
293     qDebug() << __PRETTY_FUNCTION__;
294
295     m_defaultImage = false;
296
297     QJson::Parser parser;
298     bool ok;
299
300     QVariantMap result = parser.parse (jsonReply, &ok).toMap();
301     if (!ok) {
302         emit error(ErrorContext::SITUARE, SituareError::INVALID_JSON);
303         return;
304     } else {
305
306         if(result.contains("ErrorCode")) {
307             QVariant errorVariant = result.value("ErrorCode");
308             emit error(ErrorContext::SITUARE, errorVariant.toInt());
309             return;
310         } else if(result.contains("user")) {
311
312             QVariant userVariant = result.value("user");
313             QMap<QString, QVariant> userMap = userVariant.toMap();
314
315             GeoCoordinate coordinates(userMap["latitude"].toReal(), userMap["longitude"].toReal());
316
317             QUrl imageUrl = userMap[NORMAL_SIZE_PROFILE_IMAGE].toUrl();
318
319             if(imageUrl.isEmpty()) {
320                 // user doesn't have profile image, so we need to get him a silhouette image
321                 m_defaultImage = true;
322             }
323
324             QString address = userMap["address"].toString();
325             if(address.isEmpty()) {
326                 QStringList location;
327                 location.append(QString::number(coordinates.latitude()));
328                 location.append(QString::number(coordinates.longitude()));
329                 address = location.join(", ");
330             }
331
332             User user = User(address, coordinates, userMap["name"].toString(),
333                           userMap["note"].toString(), imageUrl, userMap["timestamp"].toString(),
334                           true, userMap["uid"].toString());
335
336             QList<User> tmpFriendsList;
337
338             foreach (QVariant friendsVariant, result["friends"].toList()) {
339               QMap<QString, QVariant> friendMap = friendsVariant.toMap();
340               QVariant distance = friendMap["distance"];
341               QMap<QString, QVariant> distanceMap = distance.toMap();
342
343               GeoCoordinate coordinates(friendMap["latitude"].toReal(),friendMap["longitude"].toReal());
344
345               QUrl imageUrl = friendMap["profile_pic"].toUrl();
346
347               if(imageUrl.isEmpty()) {
348                   // friend doesn't have profile image, so we need to get him a silhouette image
349                   m_defaultImage = true;
350               }
351
352               QString address = friendMap["address"].toString();
353               if(address.isEmpty()) {
354                   QStringList location;
355                   location.append(QString::number(coordinates.latitude()));
356                   location.append(QString::number(coordinates.longitude()));
357                   address = location.join(", ");
358               }
359
360               User buddy = User(address, coordinates, friendMap["name"].toString(),
361                                friendMap["note"].toString(), imageUrl,
362                                friendMap["timestamp"].toString(),
363                                false, friendMap["uid"].toString(), distanceMap["units"].toString(),
364                                distanceMap["value"].toDouble());
365
366               tmpFriendsList.append(buddy);
367             }
368
369             QList<QUrl> imageUrlList; // url list for images
370
371             // set unchanged profile images or add new images to imageUrlList for downloading
372             if(m_user) {
373                 if(m_user->profileImageUrl() != user.profileImageUrl()) {
374                     if(!user.profileImageUrl().isEmpty())
375                         imageUrlList.append(user.profileImageUrl());
376                 } else {
377                     user.setProfileImage(m_user->profileImage());
378                 }
379             } else {
380                 if(!user.profileImageUrl().isEmpty())
381                     imageUrlList.append(user.profileImageUrl());
382             }
383
384             // clear old user object
385             if(m_user) {
386                 delete m_user;
387                 m_user = 0;
388             }
389
390             // create new user object from temporary user object
391             m_user = new User(user);
392
393             // set unchanged profile images or add new images to imageUrlList for downloading
394             if(!m_friendsList.isEmpty()) {
395                 foreach(User tmpBuddy, tmpFriendsList) {
396                     if(!tmpBuddy.profileImageUrl().isEmpty()) {
397                         bool found = false;
398                         foreach(User *buddy, m_friendsList) {
399                             if(tmpBuddy.profileImageUrl() == buddy->profileImageUrl()) {
400                                 tmpBuddy.setProfileImage(buddy->profileImage());
401                                 found = true;
402                                 break;
403                             }
404                         }
405                         if(!found && !tmpBuddy.profileImageUrl().isEmpty())
406                             imageUrlList.append(tmpBuddy.profileImageUrl());
407                     }
408                 }
409             } else {
410                 foreach(User buddy, tmpFriendsList) {
411                     if(!buddy.profileImageUrl().isEmpty())
412                         imageUrlList.append(buddy.profileImageUrl());
413                 }
414             }
415
416             // clear old friendlist
417             qDeleteAll(m_friendsList.begin(), m_friendsList.end());
418             m_friendsList.clear();
419
420             // populate new friendlist with temporary friendlist's data
421             foreach(User tmpFriendItem, tmpFriendsList) {
422                 User *friendItem = new User(tmpFriendItem);
423                 m_friendsList.append(friendItem);
424             }
425             tmpFriendsList.clear();
426
427             emit userDataChanged(m_user, m_friendsList);
428
429             // set silhouette image to imageUrlList for downloading
430             if(m_defaultImage)
431                 imageUrlList.append(QUrl(SILHOUETTE_URL));
432
433             addProfileImages(imageUrlList);
434             imageUrlList.clear();
435         } else {
436             QVariant address = result.value("address");
437             if(!address.toString().isEmpty()) {
438                 emit reverseGeoReady(address.toString());
439             } else {
440                 QStringList coordinates;
441                 coordinates.append(result.value("lat").toString());
442                 coordinates.append(result.value("lon").toString());
443
444                 emit error(ErrorContext::SITUARE, SituareError::ADDRESS_RETRIEVAL_FAILED);
445                 emit reverseGeoReady(coordinates.join(", "));
446             }
447         }
448     }
449 }
450
451 void SituareService::imageReceived(const QUrl &url, const QPixmap &image)
452 {
453     qDebug() << __PRETTY_FUNCTION__;
454     qDebug() << "Image URL: " << url << " size :" << image.size();
455
456     // assign facebook silhouette image to all who doesn't have a profile image
457     if(url == QUrl(SILHOUETTE_URL)) {
458         if(m_user->profileImageUrl().isEmpty()) {
459             m_user->setProfileImage(AvatarImage::create(image, AvatarImage::Large));
460             emit imageReady(m_user);
461         }
462         foreach(User *friendItem, m_friendsList) {
463             if(friendItem->profileImageUrl().isEmpty()) {
464                 friendItem->setProfileImage(AvatarImage::create(image, AvatarImage::Small));
465                 emit imageReady(friendItem);
466             }
467         }
468     }
469
470     if (m_user->profileImageUrl() == url) {
471         m_user->setProfileImage(AvatarImage::create(image, AvatarImage::Large));
472         emit imageReady(m_user);
473     }
474
475     foreach(User *friendItem, m_friendsList) {
476         if(friendItem->profileImageUrl() == url) {
477             friendItem->setProfileImage(AvatarImage::create(image, AvatarImage::Small));
478             emit imageReady(friendItem);
479         }
480     }
481 }
482
483 void SituareService::addProfileImages(const QList<QUrl> &imageUrlList)
484 {
485     qDebug() << __PRETTY_FUNCTION__;
486
487     foreach(QUrl url, imageUrlList) {
488         emit fetchImage(url);
489     }
490 }
491
492 void SituareService::clearUserData()
493 {
494     qDebug() << __PRETTY_FUNCTION__;
495
496     qDeleteAll(m_friendsList.begin(), m_friendsList.end());
497     m_friendsList.clear();
498
499     if(m_user) {
500         delete m_user;
501         m_user = 0;
502     }
503     emit userDataChanged(m_user, m_friendsList);
504 }