18ed17a3011b9f893041b1abe439e8afd1fe6641
[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 "parser.h"
23
24 #include <QtAlgorithms>
25 #include <QDebug>
26 #include <QNetworkReply>
27 #include <QPixmap>
28 #include <QStringList>
29 #include <QtGlobal>
30
31 #include "database.h"
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(QString, QUrl)),
51             m_imageFetcher, SLOT(fetchImage(QString, QUrl)));
52     connect(m_imageFetcher, SIGNAL(imageReceived(QString,QPixmap)),
53             this, SLOT(imageReceived(QString,QPixmap)));
54     connect(m_imageFetcher, SIGNAL(error(int, int)),
55             this, SIGNAL(error(int, int)));
56
57     m_database = new Database(this);
58     m_database->openDatabase();
59     m_database->createNotificationTable();
60     m_database->createUserTable();
61     m_database->createTagTable();
62     m_database->createUserTagTable();
63 }
64
65 SituareService::~SituareService()
66 {
67     qDebug() << __PRETTY_FUNCTION__;
68
69     if(m_user) {
70         delete m_user;
71         m_user = 0;
72     }
73
74     qDeleteAll(m_friendsList.begin(), m_friendsList.end());
75     m_friendsList.clear();
76 }
77
78 void SituareService::fetchNotifications()
79 {
80     qDebug() << __PRETTY_FUNCTION__;
81
82     QByteArray arr = m_database->getNotifications(m_user->userId().toULongLong());
83
84     parseNotificationsData(arr);
85 }
86
87 void SituareService::fetchPeopleWithSimilarInterest(const GeoCoordinate &southWestCoordinates,
88                                                     const GeoCoordinate &northEastCoordinates)
89 {
90     qDebug() << __PRETTY_FUNCTION__;
91
92     QByteArray arr = m_database->getInterestingPeople(m_user->userId().toULongLong(),
93                                                       southWestCoordinates,
94                                                       northEastCoordinates);
95
96     parseInterestingPeopleData(arr);
97 }
98
99 void SituareService::fetchLocations()
100 {
101     qDebug() << __PRETTY_FUNCTION__;
102
103     QString cookie = formCookie(API_KEY, m_credentials.expires(), m_credentials.userID(),
104                                 m_credentials.sessionKey(), m_credentials.sessionSecret(),
105                                 m_credentials.sig(), EN_LOCALE);
106
107     QUrl url = formUrl(SITUARE_URL, GET_LOCATIONS);
108     sendRequest(url, COOKIE, cookie);
109 }
110
111 void SituareService::reverseGeo(const GeoCoordinate &coordinates)
112 {
113     qDebug() << __PRETTY_FUNCTION__;
114
115     QString cookie = formCookie(API_KEY, m_credentials.expires(),m_credentials.userID(),
116                                 m_credentials.sessionKey(), m_credentials.sessionSecret(),
117                                 m_credentials.sig(), EN_LOCALE);
118
119     QString urlParameters = formUrlParameters(coordinates);
120     urlParameters.append(JSON_FORMAT);
121     QUrl url = formUrl(SITUARE_URL, REVERSE_GEO, urlParameters);
122
123     sendRequest(url, COOKIE, cookie);
124 }
125
126 void SituareService::updateLocation(const GeoCoordinate &coordinates, const QString &status,
127                                     const bool &publish)
128 {
129     qDebug() << __PRETTY_FUNCTION__;
130
131     QString urlParameters = formUrlParameters(coordinates, status, publish);
132     QUrl url = formUrl(SITUARE_URL, UPDATE_LOCATION, urlParameters);
133
134     QString cookie = formCookie(API_KEY, m_credentials.expires(), m_credentials.userID(),
135                                 m_credentials.sessionKey(), m_credentials.sessionSecret(),
136                                 m_credentials.sig(), EN_LOCALE);
137
138     sendRequest(url, COOKIE, cookie);
139 }
140
141 QString SituareService::formCookie(const QString &apiKeyValue, QString expiresValue,
142                                    QString userValue, QString sessionKeyValue,
143                                    QString sessionSecretValue, const QString &signatureValue,
144                                    const QString &localeValue)
145 {
146     qDebug() << __PRETTY_FUNCTION__;
147
148     QString cookie;
149     QString apiKey;
150     QString user;
151     QString expires;
152     QString sessionKey;
153     QString sessionSecret;
154     QString locale;
155     QString variable;
156     QString signature = EQUAL_MARK;
157     QStringList variableList;
158
159     signature.append(signatureValue);
160     apiKey.append(apiKeyValue);
161     apiKey.append(UNDERLINE_MARK);
162
163     user.append(USER);
164     user.append(EQUAL_MARK);
165     expires.append(EXPIRES);
166     expires.append(EQUAL_MARK);
167     sessionKey.append(SESSION_KEY);
168     sessionKey.append(EQUAL_MARK);
169     sessionSecret.append(SESSION_SECRET);
170     sessionSecret.append(EQUAL_MARK);
171     locale.append(LOCALE);
172     locale.append(EQUAL_MARK);
173     locale.append(localeValue);
174
175     variableList.append(expires.append(expiresValue.append(BREAK_MARK)));
176     variableList.append(sessionKey.append(sessionKeyValue.append(BREAK_MARK)));
177     variableList.append(user.append(userValue).append(BREAK_MARK));
178     variableList.append(sessionSecret.append(sessionSecretValue.append(BREAK_MARK)));
179
180     cookie.append(BREAK_MARK);
181
182     foreach(variable, variableList) {
183         cookie.append(apiKey);
184         cookie.append(variable);
185     }
186     apiKey.remove(UNDERLINE_MARK);
187     cookie.append(apiKey);
188     cookie.append(signature);
189     cookie.append(BREAK_MARK);
190     cookie.append(locale);
191
192     qDebug() << cookie;
193
194     return cookie;
195 }
196
197 QUrl SituareService::formUrl(const QString &baseUrl, const QString &phpScript,
198                              QString urlParameters)
199 {
200     qDebug() << __PRETTY_FUNCTION__;
201     QString urlString;
202
203     urlString.append(baseUrl);
204     urlString.append(phpScript);
205     if(!urlParameters.isEmpty())
206         urlString.append(urlParameters);
207
208     QUrl url = QUrl(urlString);
209
210     qDebug() << url;
211
212     return url;
213 }
214
215 QString SituareService::formUrlParameters(const GeoCoordinate &coordinates, QString status,
216                                           bool publish)
217 {
218     qDebug() << __PRETTY_FUNCTION__;
219
220     // one scene pixel is about 5.4e-6 degrees, the integer part is max three digits and one
221     // additional digit is added for maximum precision
222     const int COORDINATE_PRECISION = 10;
223
224     QString parameters;
225
226     parameters.append(QUESTION_MARK);
227     parameters.append(LATITUDE);
228     parameters.append(EQUAL_MARK);
229     parameters.append(QString::number(coordinates.latitude(), 'f', COORDINATE_PRECISION));
230     parameters.append(AMBERSAND_MARK);
231     parameters.append(LONGTITUDE);
232     parameters.append(EQUAL_MARK);
233     parameters.append(QString::number(coordinates.longitude(), 'f', COORDINATE_PRECISION));
234
235     parameters.append(AMBERSAND_MARK);
236     parameters.append(PUBLISH);
237     parameters.append(EQUAL_MARK);
238
239     if(publish)
240         parameters.append(PUBLISH_TRUE);
241     else
242         parameters.append(PUBLISH_FALSE);
243
244     if(!status.isEmpty()) {
245         parameters.append(AMBERSAND_MARK);
246         parameters.append(DATA);
247         parameters.append(EQUAL_MARK);
248         parameters.append(status);
249     }
250
251     return parameters;
252 }
253
254 void SituareService::sendRequest(const QUrl &url, const QString &cookieType, const QString &cookie)
255 {
256     qDebug() << __PRETTY_FUNCTION__;
257
258     QNetworkRequest request;
259
260     request.setUrl(url);
261     request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
262     request.setRawHeader(cookieType.toAscii(), cookie.toUtf8());
263
264     QNetworkReply *reply = m_networkManager->get(request, true);
265
266     m_currentRequests.append(reply);
267 }
268
269 void SituareService::requestFinished(QNetworkReply *reply)
270 {
271     qDebug() << __PRETTY_FUNCTION__;
272
273     //Reply from situare
274     if (m_currentRequests.contains(reply)) {
275
276         qDebug() << "BytesAvailable: " << reply->bytesAvailable();
277
278         if (reply->error()) {
279             emit error(ErrorContext::NETWORK, reply->error());
280         } else {
281             QByteArray replyArray = reply->readAll();
282             qDebug() << "Reply from: " << reply->url() << "reply " << replyArray;
283
284             if(replyArray == ERROR_LAT.toAscii()) {
285                 qDebug() << "Error: " << ERROR_LAT;
286                 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
287             } else if(replyArray == ERROR_LON.toAscii()) {
288                 qDebug() << "Error: " << ERROR_LON;
289                 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
290             } else if(replyArray.contains(ERROR_SESSION.toAscii())) {
291                 qDebug() << "Error: " << ERROR_SESSION;
292                 emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
293             } else if(replyArray.startsWith(OPENING_BRACE_MARK.toAscii())) {
294                 qDebug() << "JSON string";
295                 parseUserData(replyArray);
296             } else if(replyArray.isEmpty()) {
297                 if(reply->url().toString().contains(UPDATE_LOCATION.toAscii())) {
298                     emit updateWasSuccessful();
299                 } else {
300                     // session credentials are invalid
301                     emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
302                 }
303             } else {
304                 // unknown reply
305                 emit error(ErrorContext::SITUARE, SituareError::ERROR_GENERAL);
306             }
307         }
308         m_currentRequests.removeAll(reply);
309         reply->deleteLater();
310     }
311 }
312
313 void SituareService::credentialsReady(const FacebookCredentials &credentials)
314 {
315     qDebug() << __PRETTY_FUNCTION__;
316
317     m_credentials = credentials;
318 }
319
320 void SituareService::parseInterestingPeopleData(const QByteArray &jsonReply)
321 {
322     qDebug() << __PRETTY_FUNCTION__;
323
324     QJson::Parser parser;
325     bool ok;
326
327     QVariantMap result = parser.parse(jsonReply, &ok).toMap();
328
329     if (!ok) {
330         emit error(ErrorContext::SITUARE, SituareError::INVALID_JSON);
331         return;
332     } else {
333         QList<User> interestingPeople;
334
335         foreach (QVariant personVariant, result["people"].toList()) {
336             User user;
337             QMap<QString, QVariant> person = personVariant.toMap();
338             user.setUserId(person["uid"].toString());
339             user.setName(person["name"].toString());
340             user.setProfileImage(AvatarImage::create(
341                     QPixmap(":/res/images/empty_avatar.png"), AvatarImage::Small));
342             user.setProfileImageUrl(person["image_url"].toUrl());
343
344             interestingPeople.append(user);
345
346             emit fetchImage(user.userId(), user.profileImageUrl());
347         }
348
349         emit interestingPeopleReceived(interestingPeople);
350     }
351 }
352
353 void SituareService::parseNotificationsData(const QByteArray &jsonReply)
354 {
355     QJson::Parser parser;
356     bool ok;
357
358     QVariantMap result = parser.parse(jsonReply, &ok).toMap();
359
360     if (!ok) {
361         emit error(ErrorContext::SITUARE, SituareError::INVALID_JSON);
362         return;
363     } else {
364         QList<Notification> notifications;
365
366         foreach (QVariant notificationVariant, result["notifications"].toList()) {
367             Notification notification;
368             QMap<QString, QVariant> notificationMap = notificationVariant.toMap();
369             notification.setId(notificationMap["id"].toString());
370             notification.setSenderId(notificationMap["sender_id"].toString());
371             notification.setSenderName(notificationMap["sender_name"].toString());
372             uint timestampSeconds = notificationMap["timestamp"].toUInt();
373             notification.setTimestamp(QDateTime::fromTime_t(timestampSeconds));
374             notification.setText(notificationMap["text"].toString());
375             notification.setImage(AvatarImage::create(
376                     QPixmap(":/res/images/empty_avatar.png"), AvatarImage::Small));
377
378             notifications.append(notification);
379
380             emit fetchImage(notification.id(), notificationMap["image_url"].toString());
381         }
382
383         emit notificationsReceived(notifications);
384     }
385 }
386
387 void SituareService::parseUserData(const QByteArray &jsonReply)
388 {
389     qDebug() << __PRETTY_FUNCTION__;
390
391     m_defaultImage = false;
392
393     QJson::Parser parser;
394     bool ok;
395
396     QVariantMap result = parser.parse (jsonReply, &ok).toMap();
397
398     if (!ok) {
399         emit error(ErrorContext::SITUARE, SituareError::INVALID_JSON);
400         return;
401     } else {
402
403         if(result.contains("ErrorCode")) {
404             QVariant errorVariant = result.value("ErrorCode");
405             emit error(ErrorContext::SITUARE, errorVariant.toInt());
406             return;
407         } else if(result.contains("user")) {
408
409             QVariant userVariant = result.value("user");
410             QMap<QString, QVariant> userMap = userVariant.toMap();
411
412             GeoCoordinate coordinates(userMap["latitude"].toReal(), userMap["longitude"].toReal());
413
414             QUrl imageUrl = userMap[NORMAL_SIZE_PROFILE_IMAGE].toUrl();
415
416             QString address = userMap["address"].toString();
417             if(address.isEmpty()) {
418                 QStringList location;
419                 location.append(QString::number(coordinates.latitude()));
420                 location.append(QString::number(coordinates.longitude()));
421                 address = location.join(", ");
422             }
423
424             User user = User(address, coordinates, userMap["name"].toString(),
425                           userMap["note"].toString(), imageUrl, userMap["timestamp"].toString(),
426                           true, userMap["uid"].toString());
427
428             if(imageUrl.isEmpty()) {
429                 // user doesn't have profile image, so we need to get him a silhouette image
430                 m_defaultImage = true;
431                 user.setProfileImage(AvatarImage::create(
432                         QPixmap(":/res/images/empty_avatar_big.png"), AvatarImage::Large));
433             }
434
435             QList<User> tmpFriendsList;
436
437             foreach (QVariant friendsVariant, result["friends"].toList()) {
438               QMap<QString, QVariant> friendMap = friendsVariant.toMap();
439               QVariant distance = friendMap["distance"];
440               QMap<QString, QVariant> distanceMap = distance.toMap();
441
442               GeoCoordinate coordinates(friendMap["latitude"].toReal(),friendMap["longitude"].toReal());
443
444               QUrl imageUrl = friendMap["profile_pic"].toUrl();
445
446               QString address = friendMap["address"].toString();
447               if(address.isEmpty()) {
448                   QStringList location;
449                   location.append(QString::number(coordinates.latitude()));
450                   location.append(QString::number(coordinates.longitude()));
451                   address = location.join(", ");
452               }
453
454               User buddy = User(address, coordinates, friendMap["name"].toString(),
455                                friendMap["note"].toString(), imageUrl,
456                                friendMap["timestamp"].toString(),
457                                false, friendMap["uid"].toString(), distanceMap["units"].toString(),
458                                distanceMap["value"].toDouble());
459
460               if(imageUrl.isEmpty()) {
461                   // friend doesn't have profile image, so we need to get him a silhouette image
462                   m_defaultImage = true;
463                   buddy.setProfileImage(AvatarImage::create(
464                           QPixmap(":/res/images/empty_avatar.png"), AvatarImage::Small));
465               }
466
467               tmpFriendsList.append(buddy);
468             }
469
470             QHash<QString, QUrl> imageUrlList; // url list for images
471
472             // set unchanged profile images or add new images to imageUrlList for downloading
473             if(m_user) {
474                 if(m_user->profileImageUrl() != user.profileImageUrl()) {
475                     if(!user.profileImageUrl().isEmpty())
476                         imageUrlList.insert(user.userId(), user.profileImageUrl());
477                 } else {
478                     user.setProfileImage(m_user->profileImage());
479                 }
480             } else {
481                 if(!user.profileImageUrl().isEmpty())
482                     imageUrlList.insert(user.userId(), user.profileImageUrl());
483             }
484
485             // clear old user object
486             if(m_user) {
487                 delete m_user;
488                 m_user = 0;
489             }
490
491             // create new user object from temporary user object
492             m_user = new User(user);
493
494             // set unchanged profile images or add new images to imageUrlList for downloading
495             if(!m_friendsList.isEmpty()) {
496                 foreach(User tmpBuddy, tmpFriendsList) {
497                     if(!tmpBuddy.profileImageUrl().isEmpty()) {
498                         bool found = false;
499                         foreach(User *buddy, m_friendsList) {
500                             if(tmpBuddy.profileImageUrl() == buddy->profileImageUrl()) {
501                                 tmpBuddy.setProfileImage(buddy->profileImage());
502                                 found = true;
503                                 break;
504                             }
505                         }
506                         if(!found && !tmpBuddy.profileImageUrl().isEmpty())
507                             imageUrlList.insert(tmpBuddy.userId(), tmpBuddy.profileImageUrl());
508                     }
509                 }
510             } else {
511                 foreach(User buddy, tmpFriendsList) {
512                     if(!buddy.profileImageUrl().isEmpty())
513                         imageUrlList.insert(buddy.userId(), buddy.profileImageUrl());
514                 }
515             }
516
517             // clear old friendlist
518             qDeleteAll(m_friendsList.begin(), m_friendsList.end());
519             m_friendsList.clear();
520
521             // populate new friendlist with temporary friendlist's data
522             foreach(User tmpFriendItem, tmpFriendsList) {
523                 User *friendItem = new User(tmpFriendItem);
524                 m_friendsList.append(friendItem);
525             }
526             tmpFriendsList.clear();
527
528             //REMOVE WHEN NOT NEEDED! get user tags and set tags to the user
529             m_user->setTags(getTags(m_user->userId()));
530
531             emit userDataChanged(m_user, m_friendsList);
532
533             // set silhouette image to imageUrlList for downloading
534             if(m_defaultImage)
535                 imageUrlList.insert("", QUrl(SILHOUETTE_URL));
536
537             addProfileImages(imageUrlList);
538             imageUrlList.clear();
539
540         } else {
541             QVariant address = result.value("address");
542             if(!address.toString().isEmpty()) {
543                 emit reverseGeoReady(address.toString());
544             } else {
545                 QStringList coordinates;
546                 coordinates.append(result.value("lat").toString());
547                 coordinates.append(result.value("lon").toString());
548
549                 emit error(ErrorContext::SITUARE, SituareError::ADDRESS_RETRIEVAL_FAILED);
550                 emit reverseGeoReady(coordinates.join(", "));
551             }
552         }
553     }
554 }
555
556 void SituareService::imageReceived(const QString &id, const QPixmap &image)
557 {
558     qDebug() << __PRETTY_FUNCTION__;
559
560     if (m_user->userId() == id)
561         emit imageReady(id, AvatarImage::create(image, AvatarImage::Large));
562     else
563         emit imageReady(id, AvatarImage::create(image, AvatarImage::Small));
564 }
565
566 void SituareService::addProfileImages(const QHash<QString, QUrl> &imageUrlList)
567 {
568     qDebug() << __PRETTY_FUNCTION__;
569
570     QHashIterator<QString, QUrl> imageUrlListIterator(imageUrlList);
571
572     while (imageUrlListIterator.hasNext()) {
573         imageUrlListIterator.next();
574         emit fetchImage(imageUrlListIterator.key(), imageUrlListIterator.value());
575     }
576 }
577
578 void SituareService::clearUserData()
579 {
580     qDebug() << __PRETTY_FUNCTION__;
581
582     qDeleteAll(m_friendsList.begin(), m_friendsList.end());
583     m_friendsList.clear();
584
585     if(m_user) {
586         delete m_user;
587         m_user = 0;
588     }
589     emit userDataChanged(m_user, m_friendsList);
590 }
591
592 QStringList SituareService::getTags(const QString &userId)
593 {
594     qDebug() << __PRETTY_FUNCTION__;
595
596     return m_database->getTags(userId.toInt());
597 }
598
599 void SituareService::updateTags(const QString &userId, const QStringList &tags)
600 {
601     qDebug() << __PRETTY_FUNCTION__;
602
603     foreach (QString tag, tags)
604         m_database->addTag(userId.toInt(), tag);
605 }