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