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