Added coordinates to sendMessage.
[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                                  const GeoCoordinate &coordinates)
259 {
260     qDebug() << __PRETTY_FUNCTION__;
261
262     qWarning() << __PRETTY_FUNCTION__ << m_database->sendMessage(613374451,
263                                                                  receiverId.toULongLong(), message,
264                                                                  coordinates);
265 }
266
267 void SituareService::sendRequest(const QUrl &url, const QString &cookieType, const QString &cookie)
268 {
269     qDebug() << __PRETTY_FUNCTION__;
270
271     QNetworkRequest request;
272
273     request.setUrl(url);
274     request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
275     request.setRawHeader(cookieType.toAscii(), cookie.toUtf8());
276
277     QNetworkReply *reply = m_networkManager->get(request, true);
278
279     m_currentRequests.append(reply);
280 }
281
282 void SituareService::requestFinished(QNetworkReply *reply)
283 {
284     qDebug() << __PRETTY_FUNCTION__;
285
286     //Reply from situare
287     if (m_currentRequests.contains(reply)) {
288
289         qDebug() << "BytesAvailable: " << reply->bytesAvailable();
290
291         if (reply->error()) {
292             emit error(ErrorContext::NETWORK, reply->error());
293         } else {
294             QByteArray replyArray = reply->readAll();
295             qDebug() << "Reply from: " << reply->url() << "reply " << replyArray;
296
297             if(replyArray == ERROR_LAT.toAscii()) {
298                 qDebug() << "Error: " << ERROR_LAT;
299                 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
300             } else if(replyArray == ERROR_LON.toAscii()) {
301                 qDebug() << "Error: " << ERROR_LON;
302                 emit error(ErrorContext::SITUARE, SituareError::UPDATE_FAILED);
303             } else if(replyArray.contains(ERROR_SESSION.toAscii())) {
304                 qDebug() << "Error: " << ERROR_SESSION;
305                 emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
306             } else if(replyArray.startsWith(OPENING_BRACE_MARK.toAscii())) {
307                 qDebug() << "JSON string";
308                 parseUserData(replyArray);
309             } else if(replyArray.isEmpty()) {
310                 if(reply->url().toString().contains(UPDATE_LOCATION.toAscii())) {
311                     emit updateWasSuccessful(SituareService::SuccessfulUpdateLocation);
312                 } else {
313                     // session credentials are invalid
314                     emit error(ErrorContext::SITUARE, SituareError::SESSION_EXPIRED);
315                 }
316             } else {
317                 // unknown reply
318                 emit error(ErrorContext::SITUARE, SituareError::ERROR_GENERAL);
319             }
320         }
321         m_currentRequests.removeAll(reply);
322         reply->deleteLater();
323     }
324 }
325
326 void SituareService::credentialsReady(const FacebookCredentials &credentials)
327 {
328     qDebug() << __PRETTY_FUNCTION__;
329
330     m_credentials = credentials;
331 }
332
333 void SituareService::parseInterestingPeopleData(const QByteArray &jsonReply)
334 {
335     qDebug() << __PRETTY_FUNCTION__;
336
337     QJson::Parser parser;
338     bool ok;
339
340     QVariantMap result = parser.parse(jsonReply, &ok).toMap();
341
342     if (!ok) {
343         emit error(ErrorContext::SITUARE, SituareError::INVALID_JSON);
344         return;
345     } else {
346         QList<User> interestingPeople;
347
348         foreach (QVariant personVariant, result["people"].toList()) {
349             User user;
350             QMap<QString, QVariant> person = personVariant.toMap();
351             user.setUserId(person["uid"].toString());
352             user.setName(person["name"].toString());
353             user.setProfileImage(AvatarImage::create(
354                     QPixmap(":/res/images/empty_avatar.png"), AvatarImage::Small));
355             user.setProfileImageUrl(person["image_url"].toUrl());
356             user.setTags(person["tags"].toList());
357             interestingPeople.append(user);
358
359             //Remove comment when the actual server is used
360             //emit fetchImage(user.userId(), user.profileImageUrl());
361         }
362
363         emit interestingPeopleReceived(interestingPeople);
364     }
365 }
366
367 void SituareService::parseMessagesData(const QByteArray &jsonReply)
368 {
369     QJson::Parser parser;
370     bool ok;
371
372     QVariantMap result = parser.parse(jsonReply, &ok).toMap();
373
374     if (!ok) {
375         emit error(ErrorContext::SITUARE, SituareError::INVALID_JSON);
376         return;
377     } else {
378         QList<Message> messages;
379
380         foreach (QVariant messageVariant, result["messages"].toList()) {
381             Message message;
382             QMap<QString, QVariant> messageMap = messageVariant.toMap();
383             message.setId(messageMap["id"].toString());
384             message.setSenderId(messageMap["sender_id"].toString());
385             message.setSenderName(messageMap["sender_name"].toString());
386             uint timestampSeconds = messageMap["timestamp"].toUInt();
387             message.setTimestamp(QDateTime::fromTime_t(timestampSeconds));
388             message.setText(messageMap["text"].toString());
389             message.setImage(AvatarImage::create(
390                     QPixmap(":/res/images/empty_avatar.png"), AvatarImage::Small));
391
392             messages.append(message);
393
394             //emit fetchImage(message.id(), messageMap["image_url"].toString());
395         }
396
397         emit messagesReceived(messages);
398     }
399 }
400
401 void SituareService::parseUserData(const QByteArray &jsonReply)
402 {
403     qDebug() << __PRETTY_FUNCTION__;
404
405     m_defaultImage = false;
406
407     QJson::Parser parser;
408     bool ok;
409
410     QVariantMap result = parser.parse (jsonReply, &ok).toMap();
411
412     if (!ok) {
413         emit error(ErrorContext::SITUARE, SituareError::INVALID_JSON);
414         return;
415     } else {
416
417         if(result.contains("ErrorCode")) {
418             QVariant errorVariant = result.value("ErrorCode");
419             emit error(ErrorContext::SITUARE, errorVariant.toInt());
420             return;
421         } else if(result.contains("user")) {
422
423             QVariant userVariant = result.value("user");
424             QMap<QString, QVariant> userMap = userVariant.toMap();
425
426             GeoCoordinate coordinates(userMap["latitude"].toReal(), userMap["longitude"].toReal());
427
428             QUrl imageUrl = userMap[NORMAL_SIZE_PROFILE_IMAGE].toUrl();
429
430             QString address = userMap["address"].toString();
431             if(address.isEmpty()) {
432                 QStringList location;
433                 location.append(QString::number(coordinates.latitude()));
434                 location.append(QString::number(coordinates.longitude()));
435                 address = location.join(", ");
436             }
437
438             User user = User(address, coordinates, userMap["name"].toString(),
439                           userMap["note"].toString(), imageUrl, userMap["timestamp"].toString(),
440                           true, userMap["uid"].toString());
441
442             if(imageUrl.isEmpty()) {
443                 // user doesn't have profile image, so we need to get him a silhouette image
444                 m_defaultImage = true;
445                 user.setProfileImage(AvatarImage::create(
446                         QPixmap(":/res/images/empty_avatar_big.png"), AvatarImage::Large));
447             }
448
449             QList<User> tmpFriendsList;
450
451             foreach (QVariant friendsVariant, result["friends"].toList()) {
452               QMap<QString, QVariant> friendMap = friendsVariant.toMap();
453               QVariant distance = friendMap["distance"];
454               QMap<QString, QVariant> distanceMap = distance.toMap();
455
456               GeoCoordinate coordinates(friendMap["latitude"].toReal(),friendMap["longitude"].toReal());
457
458               QUrl imageUrl = friendMap["profile_pic"].toUrl();
459
460               QString address = friendMap["address"].toString();
461               if(address.isEmpty()) {
462                   QStringList location;
463                   location.append(QString::number(coordinates.latitude()));
464                   location.append(QString::number(coordinates.longitude()));
465                   address = location.join(", ");
466               }
467
468               User buddy = User(address, coordinates, friendMap["name"].toString(),
469                                friendMap["note"].toString(), imageUrl,
470                                friendMap["timestamp"].toString(),
471                                false, friendMap["uid"].toString(), distanceMap["units"].toString(),
472                                distanceMap["value"].toDouble());
473
474               if(imageUrl.isEmpty()) {
475                   // friend doesn't have profile image, so we need to get him a silhouette image
476                   m_defaultImage = true;
477                   buddy.setProfileImage(AvatarImage::create(
478                           QPixmap(":/res/images/empty_avatar.png"), AvatarImage::Small));
479               }
480
481               tmpFriendsList.append(buddy);
482             }
483
484             QHash<QString, QUrl> imageUrlList; // url list for images
485
486             // set unchanged profile images or add new images to imageUrlList for downloading
487             if(m_user) {
488                 if(m_user->profileImageUrl() != user.profileImageUrl()) {
489                     if(!user.profileImageUrl().isEmpty())
490                         imageUrlList.insert(user.userId(), user.profileImageUrl());
491                 } else {
492                     user.setProfileImage(m_user->profileImage());
493                 }
494             } else {
495                 if(!user.profileImageUrl().isEmpty())
496                     imageUrlList.insert(user.userId(), user.profileImageUrl());
497             }
498
499             // clear old user object
500             if(m_user) {
501                 delete m_user;
502                 m_user = 0;
503             }
504
505             // create new user object from temporary user object
506             m_user = new User(user);
507
508             // set unchanged profile images or add new images to imageUrlList for downloading
509             if(!m_friendsList.isEmpty()) {
510                 foreach(User tmpBuddy, tmpFriendsList) {
511                     if(!tmpBuddy.profileImageUrl().isEmpty()) {
512                         bool found = false;
513                         foreach(User *buddy, m_friendsList) {
514                             if(tmpBuddy.profileImageUrl() == buddy->profileImageUrl()) {
515                                 tmpBuddy.setProfileImage(buddy->profileImage());
516                                 found = true;
517                                 break;
518                             }
519                         }
520                         if(!found && !tmpBuddy.profileImageUrl().isEmpty())
521                             imageUrlList.insert(tmpBuddy.userId(), tmpBuddy.profileImageUrl());
522                     }
523                 }
524             } else {
525                 foreach(User buddy, tmpFriendsList) {
526                     if(!buddy.profileImageUrl().isEmpty())
527                         imageUrlList.insert(buddy.userId(), buddy.profileImageUrl());
528                 }
529             }
530
531             // clear old friendlist
532             qDeleteAll(m_friendsList.begin(), m_friendsList.end());
533             m_friendsList.clear();
534
535             // populate new friendlist with temporary friendlist's data
536             foreach(User tmpFriendItem, tmpFriendsList) {
537                 User *friendItem = new User(tmpFriendItem);
538                 m_friendsList.append(friendItem);
539             }
540             tmpFriendsList.clear();
541
542             //REMOVE WHEN NOT NEEDED! get user tags and set tags to the user
543             m_user->setTags(getTags(m_user->userId()));
544
545             emit userDataChanged(m_user, m_friendsList);
546
547             // set silhouette image to imageUrlList for downloading
548             if(m_defaultImage)
549                 imageUrlList.insert("", QUrl(SILHOUETTE_URL));
550
551             addProfileImages(imageUrlList);
552             imageUrlList.clear();
553
554         } else {
555             QVariant address = result.value("address");
556             if(!address.toString().isEmpty()) {
557                 emit reverseGeoReady(address.toString());
558             } else {
559                 QStringList coordinates;
560                 coordinates.append(result.value("lat").toString());
561                 coordinates.append(result.value("lon").toString());
562
563                 emit error(ErrorContext::SITUARE, SituareError::ADDRESS_RETRIEVAL_FAILED);
564                 emit reverseGeoReady(coordinates.join(", "));
565             }
566         }
567     }
568 }
569
570 void SituareService::imageReceived(const QString &id, const QPixmap &image)
571 {
572     qDebug() << __PRETTY_FUNCTION__;
573
574     if (m_user->userId() == id)
575         emit imageReady(id, AvatarImage::create(image, AvatarImage::Large));
576     else
577         emit imageReady(id, AvatarImage::create(image, AvatarImage::Small));
578 }
579
580 void SituareService::addProfileImages(const QHash<QString, QUrl> &imageUrlList)
581 {
582     qDebug() << __PRETTY_FUNCTION__;
583
584     QHashIterator<QString, QUrl> imageUrlListIterator(imageUrlList);
585
586     while (imageUrlListIterator.hasNext()) {
587         imageUrlListIterator.next();
588         emit fetchImage(imageUrlListIterator.key(), imageUrlListIterator.value());
589     }
590 }
591
592 void SituareService::clearUserData()
593 {
594     qDebug() << __PRETTY_FUNCTION__;
595
596     qDeleteAll(m_friendsList.begin(), m_friendsList.end());
597     m_friendsList.clear();
598
599     if(m_user) {
600         delete m_user;
601         m_user = 0;
602     }
603     emit userDataChanged(m_user, m_friendsList);
604 }
605
606 QHash<QString, QString> SituareService::getTags(const QString &userId)
607 {
608     qDebug() << __PRETTY_FUNCTION__;
609
610     return m_database->getTags(userId.toInt());
611 }
612
613 void SituareService::removeTags(const QStringList &tags)
614 {
615     qDebug() << __PRETTY_FUNCTION__;
616
617     m_database->removeTags(613374451, tags);
618 }
619
620 void SituareService::removeMessage(const QString &id)
621 {
622     qDebug() << __PRETTY_FUNCTION__;
623
624     if (m_database->removeMessage(613374451, id))
625         emit updateWasSuccessful(SituareService::SuccessfulRemoveMessage);
626 }
627
628 void SituareService::addTags(const QStringList &tags)
629 {
630     qDebug() << __PRETTY_FUNCTION__;
631
632     foreach (QString tag, tags)
633         m_database->addTag(613374451, tag);
634 }
635
636 void SituareService::searchPeopleByTag(const QString &tag)
637 {
638     qDebug() << __PRETTY_FUNCTION__;
639
640     QByteArray arr = m_database->getInterestingPeopleByTag(613374451, tag);
641
642     parseInterestingPeopleData(arr);
643 }